from json import loads
from typing import List
from enum import Enum
from ._MapInfo import MapInfo


class ObjectMapper(object):
    def __init__(self, maps: List[MapInfo], hide_from=None, ignore_properties=None):
        self.from_type_dict = {}
        for m in maps:
            self.from_type_dict[m.from_type] = m
        self.hide_source = hide_from

        self.ignore_properties = {}
        if ignore_properties is not None:
            for prop_name in ignore_properties:
                self.ignore_properties[prop_name] = prop_name

    def map(self, from_object):
        if from_object is None:
            return None
        map_info = self.from_type_dict[type(from_object)]
        args = []
        for arg_name in map_info.to_type_init_params:
            if arg_name == "self":
                continue

            if hasattr(from_object, arg_name):
                value = getattr(from_object, arg_name)
            else:
                value = None

            if arg_name in self.ignore_properties:
                if isinstance(value, list):
                    args.append([])
                else:
                    args.append(None)
                continue

            value_cls = type(value)
            if value_cls in self.from_type_dict:
                args.append(self.map(value))
            elif isinstance(value, str) and self._is_json(value):
                args.append(loads(value))
            elif isinstance(value, list):
                args.append(self.map_list(value))
            elif isinstance(value, dict):
                args.append(self.map_dict(value))
            else:
                args.append(value)
        model = map_info.to_type(*args)
        if self.hide_source:
            setattr(model, self.hide_source, from_object)
        return model

    def map_list(self, from_list):
        if from_list is None:
            return None
        ret = []
        for from_object in from_list:
            if isinstance(from_object, (str, int)):
                ret.append(from_object)
            else:
                ret.append(self.map(from_object))
        return ret

    def map_dict(self, from_dict):
        if from_dict is None:
            return None
        ret = {}
        for key in from_dict:
            value = from_dict[key]
            if isinstance(value, (str, int)):
                ret[key] = value
            else:
                if isinstance(value, list):
                    ret[key] = self.map_list(value)
                else:
                    ret[key] = self.map(value)
        return ret

    def copy_fields(self, from_object, to_object):
        map_info = self.from_type_dict[type(from_object)]
        for arg_name in map_info.to_type_init_params:
            if arg_name == "self":
                continue

            from_value = getattr(from_object, arg_name)
            value_type = type(from_value)
            if value_type in self.from_type_dict:
                self.copy_fields(from_value, getattr(to_object, arg_name))
            else:
                setattr(to_object, arg_name, from_value)

    def create_object(self, from_object, **kwargs):
        if from_object is None:
            return None
        map_info = self.from_type_dict[type(from_object)]

        inner_kwargs = {}
        for arg_name in map_info.to_type_init_params:
            if arg_name == "self":
                continue
            if kwargs.get("ignore_missing_fields", False) and not hasattr(from_object, arg_name):
                continue

            from_value = getattr(from_object, arg_name)
            value_type = type(from_value)

            if value_type in self.from_type_dict:
                inner_kwargs[arg_name] = self.create_object(from_value)
            elif isinstance(from_value, list):
                inner_kwargs[arg_name] = []
                for value_item in from_value:
                    if value_type in self.from_type_dict:
                        inner_kwargs[arg_name].append(self.create_object(value_item))
                    else:
                        inner_kwargs[arg_name].append(value_item)
            else:
                inner_kwargs[arg_name] = from_value

        return map_info.to_type(**inner_kwargs)

    def _is_json(self, json_test: str) -> bool:
        try:
            json_object = loads(json_test)

            if isinstance(json_object, dict):
                return True
        except ValueError:
            return False

        return False
