"""
Funtions:
    1. Update `sys.path`.
    2. Locate and check out target module.
    3. Passing args and kwargs to target function.

Placeholders:
    PROJ_LIB_DIR        dir
    ADD_PYWIN32_SUPPORT bool[False]
    MODULE_PATHS        list[dir]
    *DEFAULT_CONF*      file[*.pkl]
        TARGET_DIR      dir
        TARGET_PKG      str
        TARGET_MOD      str
        TARGET_FUNC     str
        TARGET_ARGS     list[val]
        TARGET_KWARGS   dict[var, val]
"""
import os
import sys
import traceback

from importlib import import_module
from os.path import abspath

# add current dir to `sys.path` (this is necessary for embed python)
os.chdir(os.path.dirname(__file__))
sys.path.insert(0, abspath('.'))

# add project lib to environment
sys.path.insert(1, abspath('{PROJ_LIB_DIR}'))

module_paths = {MODULE_PATHS}
if module_paths:
    sys.path.extend(map(abspath, module_paths))

add_pywin32_support = {ADD_PYWIN32_SUPPORT}
if add_pywin32_support:
    try:
        # ref: https://stackoverflow.com/questions/58612306/how-to-fix
        #      -importerror-dll-load-failed-while-importing-win32api
        #      (see comment from @ocroquette)
        import pywin32_system32
        _pywin32_dir = pywin32_system32.__path__._path[0]  # noqa
        # -> ~/lib/site-packages/pywin32_system32
        os.add_dll_directory(_pywin32_dir)
        #   FIXME: this function is introduced since python 3.8.

        _site_packages_dir = os.path.dirname(_pywin32_dir)
        sys.path.extend((
            _site_packages_dir + '/pythonwin',
            _site_packages_dir + '/win32',
            _site_packages_dir + '/win32/lib',
        ))
    except ImportError:
        print('Adding pywin32 support failed')


# ------------------------------------------------------------------------------

def parse_target_conf(args):
    assert len(args) >= 2, ('Invalid args format', args)
    #   the first arg is python filename to launch, the second is preset
    #   configuration file (i.e. which we want to parse in this function.)

    def _remove_exe_file_from_args():
        target_exe_dir = os.path.dirname(os.getcwd())
        if (len(args) > 2
                and args[2].endswith('.exe')
                and os.path.dirname(args[2]) == target_exe_dir):
            args.pop(2)

    _remove_exe_file_from_args()

    conf_file = args.pop(1)
    assert conf_file.endswith(('.json', '.pkl')), (
        'Unknown file type to load config!', conf_file
    )

    if conf_file.endswith('.json'):
        from json import load
        with open(conf_file, 'r', encoding='utf-8') as f:
            return load(f)
    else:
        from pickle import load
        with open(conf_file, 'rb') as f:
            return load(f)


def launch(main_func, *args, **kwargs):
    try:
        main_func(*args, **kwargs)
    except Exception:
        _show_error_info(traceback.format_exc())
        input('Press enter to leave...')
    sys.exit()


def _show_error_info(err_msg, title='Runtime Exception', terminal='console'):
    """
    Args:
        err_msg: suggest passing `traceback.format_exc()`.
        title:
        terminal:

    Rerferences:
        https://stackoverflow.com/questions/1278705/when-i-catch-an-exception
            -how-do-i-get-the-type-file-and-line-number
        https://stackoverflow.com/questions/17280637/tkinter-messagebox-without
            -window
        https://www.cnblogs.com/freeweb/p/5048833.html
    """
    if terminal == 'console':
        print(title + ':', err_msg)
    elif terminal == 'tkinter':
        from tkinter import Tk, messagebox
        root = Tk()
        root.withdraw()
        messagebox.showerror(title=title, message=err_msg)
    elif terminal == 'vbsbox':
        return os.popen(
            'echo msgbox "{{msg}}", 64, "{{title}}" > '
            'alert.vbs && start '
            'alert.vbs && ping -n 2 127.1 > '
            'nul && del alert.vbs'.format(title=title, msg=err_msg)
        ).read()


if __name__ == '__main__':
    # try:
    #     from lk_logger import lk
    #     lk.enable_lite_mode()
    # except Exception:
    #     lk = None

    try:
        conf = parse_target_conf(sys.argv)
        # from lk_logger import lk
        # lk.logp(conf)

        # check in target dir to make sure all sequent relative paths
        # references are based on the target dir.
        os.chdir(conf['TARGET_DIR'])
        sys.path.append(abspath('.'))

        mod = import_module(conf['TARGET_MOD'], conf['TARGET_PKG'])
        if conf['TARGET_FUNC']:
            main = getattr(mod, conf['TARGET_FUNC'])
            launch(main, *conf['TARGET_ARGS'], **conf['TARGET_KWARGS'])

    except Exception:
        _show_error_info(traceback.format_exc())
        input('Press enter to leave...')

    # finally:
    #     if lk:
    #         lk.enable()
