import ruamel.yaml as yaml
from collections import namedtuple
import os
import json
import numpy as np
from collections import defaultdict


dataset_dir = '../data'


def _change_data(data):
    if isinstance(data, dict):
        return {k: _change_data(v) for k, v in data.items()}
    if isinstance(data, list):
        return [_change_data(item) for item in data]
    if isinstance(data, np.ndarray) or isinstance(data, np.number):
        return data.tolist()
    return data


def dict_to_namedtuple(dic: dict):
    dicts = namedtuple('tuple', dic.keys())
    result = {}

    for k, v in dic.items():
        if isinstance(v, dict):
            v = dict_to_namedtuple(v)
        elif v in ['True', 'true']:
            v = True
        elif v in ['False', 'false']:
            v = False
        elif v in ['None']:
            v = None
        else:
            pass
        result[k] = v
    return dicts(**result)


class Config(object):
    def __init__(self):
        self.setting = None
        self._files = None

    def load_config(self, path):
        with open(path, 'r') as f:
            setting = yaml.load(f, Loader=yaml.RoundTripLoader)
        self.setting = dict_to_namedtuple(setting)

        with open(path, 'r') as f:
            self._files = ''.join(f.readlines())

    def __getattr__(self, item):
        return self.setting.__getattribute__(item)
    
    def get(self, name, default=None):
        root = self.setting
        for part_name in name.split('.'):
            try:
                root = root.__getattribute__(part_name)
            except Exception:
                return default
        return root
        
    def save(self, path):
        parent_dir = os.path.split(path)[0]
        if not os.path.exists(parent_dir):
            os.makedirs(parent_dir)
        with open(path, 'w') as f:
            f.write(self._files)


config = Config()


class NumpyEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, np.ndarray):
            return obj.tolist()
        if isinstance(obj, np.number):
            return obj.tolist()
        return json.JSONEncoder.default(self, obj)


class ResultLogging(object):
    def __init__(self, path):
        self.path = path
        self.data = {}
        self.load()

    def load(self):
        if not os.path.exists(self.path):
            self.data = {}
        else:
            with open(self.path, 'r') as f:
                self.data = yaml.safe_load(f)

    def save(self):
        with open(self.path, 'w') as f:
            yaml.safe_dump(_change_data(self.data), f)

    def update(self, dataset: str, method: str, value: dict):
        self.load()
        if dataset not in self.data:
            self.data[dataset] = {}
        if method not in self.data[dataset]:
            self.data[dataset][method] = {}
        self.data[dataset][method].update(value)
        self.save()


class YamlLoader(object):

    def __init__(self, path):
        self.path = path
        dir = os.path.split(path)[0]
        if not os.path.exists(dir):
            os.makedirs(dir)
        if os.path.exists(path):
            with open(self.path, 'r') as f:
                self.data = yaml.safe_load(f)
        else:
            self.data = None

    def __getitem__(self, index):
        return self.data[index]

    def exists(self):
        return self.data is not None

    def save(self):
        with open(self.path, 'w') as f:
            yaml.safe_dump(_change_data(self.data), f)

    def update(self, data=None):
        if data is not None:
            self.data = data
        self.save()


