import os
import json
from pathlib import Path
from collections import namedtuple

support_types = ('str', 'int', 'bool', 'float', 'none')


def convert_param(original_lists):
    assert isinstance(original_lists, list), 'The type is not right : {:}'.format(original_lists)
    ctype, value = original_lists[0], original_lists[1]
    assert ctype in support_types, 'Ctype={:}, support={:}'.format(ctype, support_types)
    is_list = isinstance(value, list)
    if not is_list: value = [value]
    outs = []
    for x in value:
        if ctype == 'int':
            x = int(x)
        elif ctype == 'str':
            x = str(x)
        elif ctype == 'bool':
            x = bool(int(x))
        elif ctype == 'float':
            x = float(x)
        elif ctype == 'none':
            if x.lower() != 'none':
                raise ValueError('For the none type, the value must be none instead of {:}'.format(x))
            x = None
        else:
            raise TypeError('Does not know this type : {:}'.format(ctype))
        outs.append(x)
    if not is_list: outs = outs[0]
    return outs


def load_config_dict(path):
    path = str(path)
    assert os.path.exists(path), 'Can not find {:}'.format(path)
    # Reading data back
    with open(path, 'r') as f:
        data = json.load(f)
    content = {k: convert_param(v) for k, v in data.items()}
    return content


def merge_config_dict(config_list):
    for config in config_list:
        assert isinstance(config, dict), 'invalid type of config: {:}'.format(type(config))
    content = config_list[0]
    for config in config_list[1:]:
        content = {**content, **config}
    return content


def load_config(path, extra, logger=None):
    if hasattr(logger, 'log'):
        logger.log(path)
    content = load_config_dict(path)
    assert extra is None or isinstance(
        extra, dict), 'invalid type of extra : {:}'.format(extra)
    if isinstance(extra, dict):
        content = {**content, **extra}
    Arguments = namedtuple('Configure', ' '.join(content.keys()))
    content = Arguments(**content)
    if hasattr(logger, 'log'):
        logger.log('{:}'.format(content))
    return content


def configure2str(config, xpath=None):
    if not isinstance(config, dict):
        config = config._asdict()
    def cstring(x):
        return "\"{:}\"".format(x)
    def gtype(x):
        if isinstance(x, list): x = x[0]
        if isinstance(x, str)  : return 'str'
        elif isinstance(x, bool) : return 'bool'
        elif isinstance(x, int): return 'int'
        elif isinstance(x, float): return 'float'
        elif x is None           : return 'none'
        else: raise ValueError('invalid : {:}'.format(x))
    def cvalue(x, xtype):
        if isinstance(x, list): is_list = True
        else:
            is_list, x = False, [x]
        temps = []
        for temp in x:
            if xtype == 'bool'  : temp = cstring(int(temp))
            elif xtype == 'none': temp = cstring('None')
            else                : temp = cstring(temp)
            temps.append( temp )
        if is_list:
            return "[{:}]".format( ', '.join( temps ) )
        else:
            return temps[0]

    xstrings = []
    for key, value in config.items():
        xtype  = gtype(value)
        string = '  {:20s} : [{:8s}, {:}]'.format(cstring(key), cstring(xtype), cvalue(value, xtype))
        xstrings.append(string)
    Fstring = '{\n' + ',\n'.join(xstrings) + '\n}'
    if xpath is not None:
        parent = Path(xpath).resolve().parent
        parent.mkdir(parents=True, exist_ok=True)
        if os.path.isfile(xpath): os.remove(xpath)
        with open(xpath, "w") as text_file:
            text_file.write('{:}'.format(Fstring))
    return Fstring


def dict2config(xdict, logger=None):
    assert isinstance(xdict, dict), 'invalid type : {:}'.format(type(xdict))
    Arguments = namedtuple('Configure', ' '.join(xdict.keys()))
    content = Arguments(**xdict)
    if hasattr(logger, 'log'):
        logger.log('{:}'.format(content))
    return content
