# -*- coding: utf-8 -*-
import sys
import os
import time
import json
import codecs
import locale
import dataclasses


def format_frame(s: bytes, linelen: int = 0) -> str:
    if not linelen:
        result = ' '.join(['%.2X' % a for a in s])
    else:
        lines = []
        while s:
            para, s = s[:linelen], s[linelen:]
            line = ' '.join(['%.2X' % a for a in para])
            lines.append(line)
        result = '\n'.join(lines)
    return result


def isoformat_date(gmt: int = None) -> str:
    return time.strftime('%Y-%m-%d', time.localtime(gmt))


def isoformat_time(gmt: int = None) -> str:
    return time.strftime('%H:%M:%S', time.localtime(gmt))


def isoformat_datetime(gmt: int = None) -> str:
    return time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(gmt))


def dhms(seconds: int) -> str:
    minutes = seconds / 60
    d = int(minutes / (24 * 60))
    h = int(minutes % (24 * 60) / 60)
    m = int(minutes % 60)
    s = ''
    if d:
        s += '%d天' % d
    if h:
        s += '%d小时' % h
    if m:
        s += '%d分钟' % m
    sec = seconds % 60
    if sec or not s:
        s += '%d秒' % sec
    return s


def changefileext(filename: str, ext: str) -> str:
    return '.'.join(list(os.path.splitext(filename)[:-1]) + [ext])


change_file_ext = changefileext


def we_are_frozen() -> bool:
    """Returns whether we are frozen via py2exe.
    This will affect how we find out where we are located."""
    return hasattr(sys, 'frozen')


def isdebugging() -> bool:
    # http://stackoverflow.com/questions/333995/how-to-detect-that-python-code-is-being-executed-through-the-debugger/26605963
    import inspect

    for frame in inspect.stack():
        if frame[1].endswith('pydevd.py'):
            return True
    return False


def is_forking() -> bool:
    """ Returns whether the process is a child process generated by the multiprocessing module.
    refer to `multiprocessing.forking.is_forking`
    """
    return len(sys.argv) == 3 and sys.argv[1] == '--multiprocessing-fork'


def module_path(module_name: str = '__main__', module=None, filename: str = '') -> str:
    """ This will get us the program's directory,
    even if we are frozen using py2exe"""

    if not filename:
        filename = module_file(module_name, module)

    result = os.path.abspath(os.path.dirname(filename))

    if result and result[-1] != os.sep:
        result += os.sep
    return result


def module_file(module_name: str = '__main__', module=None) -> str:
    result = ''
    if we_are_frozen():
        result = sys.executable
    else:
        if not module and module_name:
            module = sys.modules.get(module_name)

        if module:
            import inspect
            try:
                result = inspect.getfile(module)
            except TypeError:  # TypeError: <module '__main__' (built-in)> is a built-in module
                result = sys.argv[0]

        if not result:
            if sys._getframe().f_back:
                result = sys._getframe().f_back.f_code.co_filename
            else:
                result = __file__

    result = os.path.abspath(result)
    return result


def intval(s: str, default: int = 0) -> int:
    try:
        v = int(s)
    except ValueError:
        v = default
    return v


def defaultencoding():
    return codecs.lookup(locale.getpreferredencoding()).name


SYS_ENCODING = defaultencoding()


def nullfile() -> str:
    import platform
    return '/dev/null' if platform.system() == 'Linux' else 'nul'


SYS_NULLFILE = nullfile()


def makesure_dirpathexists(dir_or_path_or_file: str) -> bool:
    result = False
    try:
        dir_, file_ = os.path.split(dir_or_path_or_file)
        if '.' not in file_:
            dir_ = os.path.join(dir_, file_)
        os.makedirs(dir_)
        result = True
    except:  # WindowsError: [Error 183] (路径已存在)
        pass
    return result


def filename_add_tail(filename, tail):
    base, ext = os.path.splitext(filename)
    return ''.join([base, tail, ext])


def disable_deprecationwarnings():
    import warnings
    warnings.filterwarnings('ignore', category=DeprecationWarning)


def set_exit_handler(func: callable):
    if os.name == 'nt':
        try:
            import win32api
            win32api.SetConsoleCtrlHandler(func, True)
        except ImportError:
            version = '.'.join(map(str, sys.version_info[:2]))
            raise Exception('pywin32 not installed for Python ' + version)
    else:
        import signal
        signal.signal(signal.SIGTERM, func)


def flatten(l) -> list:
    y = []
    for x in l:
        if isinstance(x, (list, tuple)):
            y += flatten(x)
        else:
            y += [x]
    return y


def chunk(seq, chunk_len: int):
    return (seq[i:i + chunk_len] for i in range(0, len(seq), chunk_len))


def decode_json(s):
    """
    Compatible with JSON format that incorrectly uses '\' escape character.
    Such as \&quot; \&#39;
    See http://stackoverflow.com/questions/7921164/syntax-error-when-parsing-json-string for details.
    JSON online verification：http://jsonlint.com/
    """
    result = None
    if s:
        s = s.replace(r'\&', '')
        try:
            result = json.loads(s)
        except ValueError:
            pass
    return result


def json_get(s, key, default=None):
    """
    Parse the JSON string into a dictionary and then get the key value by a . separated path.
    """
    if isinstance(s, str):
        d = decode_json(s)
    else:
        d = s
    val = dget(d, key, default=default, separator='.')
    return val


def dget(d: dict, path: str, default=None, separator: str = '.') -> str:
    if isinstance(path, str):
        keys = path.split(separator)
    else:  # iterable
        keys = path
    for key in keys:
        d = d.get(key)
        if d is None:
            break
    if d is None:
        d = default
    return d


def dget_int(d: dict, path: str, default: int = 0, separator: str = '.') -> int:
    v = dget(d, path, default, separator)
    if isinstance(v, int):
        return v
    if isinstance(v, str) and v.isdigit():
        return int(v)
    return default


def dataclass_load_from_env(dataclazz, prefix=''):
    name_value_map = {name: os.getenv((prefix + name).upper(), _field) for name, _field in
                      dataclazz.__dataclass_fields__.items()}
    field_kw = {}
    for name, value in name_value_map.items():
        if isinstance(value, dataclasses.Field):
            if not value.init:
                continue
            if not (value.default is dataclasses.MISSING and value.default_factory is dataclasses.MISSING):
                continue
            value = None
        field_kw[name] = value
    return dataclazz(**field_kw)
