import os, sys
from pathlib import Path
from easydict import EasyDict
import functools
import collections
import copy
import re
from . import misc


class PyConfig(collections.UserDict):
    def __init__(self, cfg_file=None, name=None, root_path=None):
        super(PyConfig, self).__init__()
        self.root_path = root_path
        # self.root_path = root_path if root_path else f'{pydir.parent.parent}'  # 上上级路径
        self._name = name if name else f'{cfg_file}'  # 其实是path
        self._items = dict()
        self.init_config(cfg_file)  # 根据配置文件把内容和属性注册到items里

        self.check_items()  # TODO

    def __len__(self):
        return len(self._items)

    def __dict__(self):
        return self._items

    def __getitem__(self, item):
        return self._items[item]

    def __repr__(self):
        format_str = f'<class {self.__class__.__name__}> ' \
                     f'(path="{self._name}", ' \
                     f'items={self.items}'
                     # f'keys={self._items.keys()})'

        return format_str

    def __missing__(self, key):
        if isinstance(key, str):
            raise KeyError(key)
        return self[str(key)]

    def __contains__(self, key):
        return str(key) in self.data

    def __setitem__(self, key, item):
        self.data[str(key)] = item

    # def __getattr__(self, key):
    #    return self.data[str(key)]

    def info(self):
        """查看当前所有配置信息"""
        # info_str = f'{self.__repr__()}\n'
        info_str = f'Configs:\n'
        info_dict = self.items
        info_str += misc.dict2info(info_dict)
        return info_str

    def init_config(self, cfg_file):
        if cfg_file is None:
            return
        cp = Path(os.path.abspath(cfg_file))  # config path
        # 根据sys.path的环境路径和cp的绝对路径，获取module_dir
        valid_sys_path = [x for x in sys.path if x in f'{cp}']
        if len(valid_sys_path) >= 1:
            rp = valid_sys_path[0]  # root_path
        else:
            sys.path.append(f'{cp.parent}')
            rp = f'{cp.parent}'
        # 提取模块路径：如：modules.detection.seyolov5.config
        module_dir = f'{str(cp.parent).replace(f"{rp}/", "").replace("/", ".")}'
        # print(module_dir)
        # print(f'rp: {rp} \ncp: {cp}\nm dir: {module_dir}')
        code = f'from {module_dir} import {cp.stem}'
        # print(cfg_file, code)
        exec(code)
        cfg = eval(cp.stem)  # 模块名

        # 读取文件获取顶格写并且不以#开头的属性
        with open(cfg_file, 'r') as f:
            data = f.readlines()
        data = [x for x in data if not (x.startswith(' ') or x.startswith('#'))]  # 非顶格的不要，# 开头的不要
        data = '\n'.join(data)
        attrs = re.findall(pattern=r"\s[a-z]\w+ ?= ?", string=data)
        attrs = [x.split('=')[0].replace('\n', '').strip() for x in attrs]

        # 注册属性到_items里，注册属性到self的属性里。
        for attr in attrs:
            if hasattr(cfg, attr):
                exec(f"self._items[attr] = cfg.{attr}")
                attr_value = self._items[attr]
                if isinstance(attr_value, dict):
                    setattr(self, attr, EasyDict(self._items[attr]))
                else:
                    setattr(self, attr, attr_value)
            else:
                pass

    def check_items(self):
        """初始化末，检查所有配置项，处理：缩略语~转为真实路径。
        递归方法，如果v类型是str,判断替换，如果循环完了，结束，如果v的类型是dict，继续循环
        """
        # print(self._items)
        for k, v in self._items.items():
            new_v = self.recursive_func(v)
            self._items[k] = new_v
            # 更新属性
            if hasattr(self, k):
                delattr(self, k)
            setattr(self, k, EasyDict(new_v)) if isinstance(new_v, dict) else setattr(self, k, new_v)

    def recursive_func(self, value):
        """递归函数,如果v类型是str,判断替换，如果循环完了，结束，如果v的类型是dict，继续循环"""
        if value is None:
            return None
        elif isinstance(value, str):
            return value.replace('~', os.environ['HOME'])
        elif isinstance(value, int):
            return value
        elif isinstance(value, float):
            return value
        elif isinstance(value, bool):
            return value
        elif isinstance(value, list):
            new_list = [self.recursive_func(x) for x in value]
            return new_list
        elif isinstance(value, dict):
            new_dict = {}
            for k, v in value.items():
                new_dict[k] = self.recursive_func(v)
            return new_dict
        else:
            raise TypeError(f'Config file only support basic python types but the value type is: {type(value)}.')

    def update_item(self, attrs, value):
        """更新配置条目
        attrs: 长度为0时代表，更新全部value，长度为1时，代表第1个元素是第一层，
        """
        if len(attrs) == 0:
            self.merge(value)
        elif len(attrs) == 1:
            self._items[attrs[0]] = value
        elif len(attrs) == 2:
            self._items[attrs[0]][attrs[1]] = value
        elif len(attrs) == 3:
            self._items[attrs[0]][attrs[1]][attrs[2]] = value
        else:
            self._items[attrs[0]][attrs[1]][attrs[2]][attrs[3]] = value
        self.check_items()

    def merge(self, cfg2):
        """合并另一个配置文件到内部，是inplace的"""
        if cfg2 is None:
            return self
        for k2, v2 in cfg2._items.items():
            merged_v = self.recursive_func2(v2, k2=k2, sub_items=self._items)
            self._items[k2] = merged_v
        # print(self.items)
        self.check_items()
        return self

    def recursive_func2(self, v2, k2=None, sub_items=None):
        """v1是之前的值，v2是现在的值"""
        if k2 and (k2 not in list(sub_items.keys())):
            return v2
        else:
            v1 = sub_items[k2]

            if type(v1) != type(v2):
                return v2
            f = functools.partial(isinstance, v1)
            if f(str) or f(int) or f(float) or f(bool):
                return v2
            elif f(list):
                tmp = [x for x in v2 if x not in v1]
                return v1 + tmp
            elif f(dict):  # v1v2都是dict
                new_dict = v1  # 保留v1
                for kk, vv in v2.items():
                    new_dict[kk] = self.recursive_func2(vv, k2=kk, sub_items=v1)
                return new_dict

    @property
    def items(self):
        return self._items

    def keys(self):
        return self._items.keys()

    def get(self, key, default=None):
        if key in list(self._items.keys()):
            return self._items[key]
        else:
            return default







