"""
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]
        # -> ~/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):
    if len(args) == 2 and args[1].endswith('.exe'):
        args.pop(1)

    if len(args) == 0:
        raise Exception(args)
    elif len(args) == 1:
        conf = './.pylauncher_conf/main.pkl'
        #   see `pyportable_installer.main_flow.step3.step3_3.create_launcher
        #       ._create_launcher > vars:conf_name`
    elif len(args) == 2:
        conf = args[1]
    else:
        # TODO: see https://www.cnblogs.com/techflow/p/13631509.html
        # from argparse import ArgumentParser
        # parser = ArgumentParser()
        # parser.add_argument('file')
        # parser.add_argument('...')
        raise NotImplemented('PyPortable-Installer doesn\'t support parsing '
                             'custom `sys.argv` for now', args)
    if conf.endswith('.json'):
        from json import load
        with open(conf, 'r', encoding='utf-8') as f:
            return load(f)
    elif conf.endswith('.pkl'):
        from pickle import load
        with open(conf, 'rb') as f:
            return load(f)
    else:
        raise Exception('Unknown file type to load config!', conf)


def launch(main_func, *args, **kwargs):
    try:
        main_func(*args, **kwargs)
    except Exception:
        _show_error_info(traceback.format_exc())
    finally:
        input('Press enter or close the window 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 or close the window to leave...')

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