# Copyright (c) Facebook, Inc. and its affiliates.
# All rights reserved.
#
# This source code is licensed under the license found in the
# LICENSE file in the root directory of this source tree.
#
import os, json
from os import path as osp
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(path, extra, logger):
    path = str(path)
    if hasattr(logger, 'log'): logger.log(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()}
    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 osp.isfile(xpath): os.remove(xpath)
        with open(xpath, "w") as text_file:
            text_file.write('{:}'.format(Fstring))
    return Fstring


def dict2config(xdict, logger):
    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
