from typing import Dict, Type, Set, Optional, Any

from dependencyinjection import DependencyBuilder
from ._types import AnyType
from ._utils import get_class_parent_types


class DependencyMap:
    def __init__(
        self,
        classes: Optional[Dict[Type[AnyType], Type[AnyType]]] = None,
        classes_by_parent: Optional[Dict[Type, Set[Type]]] = None,
        builders: Optional[Dict[Type, Type[DependencyBuilder]]] = None,
        extra_parameters: Optional[Dict[Optional[Type], Dict[str, Any]]] = None,
        dependency_instances: Optional[Dict[Type[AnyType], AnyType]] = None
    ):
        self.classes = classes or {}
        self.classes_by_parent = classes_by_parent or {}
        self.builders = builders or {}
        self.extra_parameters = extra_parameters or {}
        self.dependency_instances = dependency_instances or {}

    def add_dependency_instance(self, dependency_instance: Any):
        self.dependency_instances[type(dependency_instance)] = dependency_instance
        for base_class in get_class_parent_types(type(dependency_instance)):
            self.dependency_instances[base_class] = dependency_instance

    def has_instance(self, instance_cls: Type) -> bool:
        return instance_cls in self.dependency_instances

    def get_instance(self, instance_cls: Type[AnyType]) -> AnyType:
        return self.dependency_instances[instance_cls]

    def get_dependencies_by_parent(self, parent_type: Type[AnyType]) -> Set[Type[AnyType]]:
        return self.classes_by_parent.get(parent_type, set())

    def add_dependency_extra_parameter(
        self,
        parameter_name: str,
        parameter_value: Any,
        for_type: Optional[Type] = None
    ):
        if for_type not in self.extra_parameters:
            self.extra_parameters[for_type] = {}
        self.extra_parameters[for_type][parameter_name] = parameter_value

    def has_extra_parameter(self, parameter_name: str, for_type: Optional[Type] = None) -> bool:
        if for_type in self.extra_parameters and parameter_name in self.extra_parameters[for_type]:
            return True
        if None in self.extra_parameters and parameter_name in self.extra_parameters[None]:
            return True

        return False

    def get_extra_parameter(self, parameter_name: str, for_type: Optional[Type] = None) -> Any:
        if for_type in self.extra_parameters and parameter_name in self.extra_parameters[for_type]:
            return self.extra_parameters[for_type][parameter_name]
        elif None in self.extra_parameters and parameter_name in self.extra_parameters[None]:
            return self.extra_parameters[None][parameter_name]
        return None

    def has_dependency_class(self, dependency_cls: Type) -> bool:
        return dependency_cls in self.classes

    def get_dependency_class(self, dependency_cls: Type[AnyType]) -> Type[AnyType]:
        return self.classes[dependency_cls]

    def has_dependency_builder(self, builder_cls: Type) -> bool:
        return builder_cls in self.builders

    def get_dependency_builder(self, builder_cls: Type[AnyType]) -> Type[DependencyBuilder]:
        return self.builders[builder_cls]

    def add_dependency_extra_parameters(
        self,
        parameters: Any,
        for_type: Optional[Type] = None,
    ):
        if parameters is None:
            return
        parameters_dict = parameters
        if not isinstance(parameters_dict, dict) and hasattr(parameters_dict, '__dict__'):
            parameters_dict = parameters_dict.__dict__
        if isinstance(parameters_dict, dict):
            for parameter_name, parameter_value in parameters_dict.items():
                self.add_dependency_extra_parameter(
                    parameter_name=parameter_name,
                    parameter_value=parameter_value,
                    for_type=for_type,
                )

    def add_class(self, dep_cls: Type[AnyType]):
        if dep_cls not in self.classes:
            self.classes[dep_cls] = dep_cls
            for parent in get_class_parent_types(dep_cls):
                self.classes[parent] = dep_cls

    def add_class_by_parent(self, dep_cls: Type[AnyType]):
        for base_type in get_class_parent_types(dep_cls):
            if base_type not in self.classes_by_parent:
                self.classes_by_parent[base_type] = set()
            self.classes_by_parent[base_type].add(dep_cls)

    def add_builder(self, builder_cls: Type[DependencyBuilder], builder_return_cls: Type[AnyType]):
        self.builders[builder_return_cls] = builder_cls
        for child_type in get_class_parent_types(builder_return_cls):
            self.builders[child_type] = builder_cls

    def clone(self):
        return DependencyMap(
            classes={**self.classes},
            classes_by_parent={**self.classes_by_parent},
            builders={**self.builders},
            extra_parameters={**self.extra_parameters},
            dependency_instances={**self.dependency_instances},
        )
