# 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