import uuid
from typing import List, Dict, Optional, Iterable

from cord.project_ontology.classification_attribute import ClassificationAttribute
from cord.project_ontology.classification_option import ClassificationOption
from cord.project_ontology.classification_type import ClassificationType
from cord.project_ontology.object_type import ObjectShape
from cord.project_ontology.ontology_classification import OntologyClassification
from cord.project_ontology.ontology_object import OntologyObject


# removal may require calling label/delete/object and label/delete/attribute
def generate_feature_node_hash() -> str:
    return str(uuid.uuid4())[0:8]


class Ontology:
    COLORS = (
        '#D33115',
        '#E27300',
        '#16406C',
        '#FE9200',
        '#FCDC00',
        '#DBDF00',
        '#A4DD00',
        '#68CCCA',
        '#73D8FF',
        '#AEA1FF',
        '#FCC400',
        '#B0BC00',
        '#68BC00',
        '#16A5A5',
        '#009CE0',
        '#7B64FF',
        '#FA28FF',
        '#B3B3B3',
        '#9F0500',
        '#C45100',
        '#FB9E00',
        '#808900',
        '#194D33',
        '#0C797D',
        '#0062B1',
        '#653294',
        '#AB149E',
    )

    def __init__(self):
        self.ontology_objects: List[OntologyObject] = []
        self.ontology_classifications: List[OntologyClassification] = []
        self.color_index = 0

    def __str__(self):
        return str(self.to_dict())

    def __current_object_id(self):
        return len(self.ontology_objects) + 1

    def __current_classification_id(self):
        return len(self.ontology_classifications) + 1

    @classmethod
    def from_dict(cls, ontology_dict: Dict):
        ontology = Ontology()
        ontology.ontology_objects = cls.__build_ontology_objects(ontology_dict['objects'])
        ontology.ontology_classifications = cls.__build_ontology_classifications(ontology_dict['classifications'])
        ontology.color_index = len(ontology.ontology_objects) % len(cls.COLORS)
        return ontology

    @classmethod
    def __build_ontology_objects(cls, ontology_objects_dict: List[Dict]):
        ontology_objects = []
        for ontology_object_dict in ontology_objects_dict:
            ontology_object = OntologyObject(ontology_object_dict['id'],
                                             ontology_object_dict['color'],
                                             ontology_object_dict['name'],
                                             ObjectShape(ontology_object_dict['shape']),
                                             ontology_object_dict['featureNodeHash'])
            ontology_objects.append(ontology_object)
        return ontology_objects

    @classmethod
    def __build_ontology_classifications(cls, ontology_classifications_dict: List[Dict]):
        ontology_classifications = []
        for classification_object_dict in ontology_classifications_dict:
            classification_attributes = []
            for attribute in classification_object_dict['attributes']:
                classification_attribute = ClassificationAttribute(attribute['id'],
                                                                   attribute['name'],
                                                                   ClassificationType(attribute['type']),
                                                                   attribute['required'],
                                                                   attribute['featureNodeHash'],
                                                                   )
                options_dict = attribute.get('options')
                if options_dict is not None:
                    options = cls.__build_classification_options(attribute['options'])
                    classification_attribute.options = options
                classification_attributes.append(classification_attribute)
            ontology_classification = OntologyClassification(classification_object_dict['id'],
                                                             classification_object_dict['featureNodeHash'],
                                                             classification_attributes)
            ontology_classifications.append(ontology_classification)
        return ontology_classifications

    @classmethod
    def __build_classification_options(cls, options_dict: List[Dict]):
        options = []
        for option_dict in options_dict:
            option = ClassificationOption(option_dict['id'],
                                          option_dict['label'],
                                          option_dict['value'],
                                          option_dict['featureNodeHash'])
            options.append(option)
        return options

    def add_object(self, name: str, shape: ObjectShape) -> None:
        ontology_object = OntologyObject(str(self.__current_object_id()),
                                         self.COLORS[self.color_index],
                                         name,
                                         shape,
                                         generate_feature_node_hash())
        self.ontology_objects.append(ontology_object)
        self.color_index = (self.color_index + 1) % len(self.COLORS)

    def add_classification(self, name: str, classification_type: ClassificationType, required: bool,
                           options: Optional[Iterable[str]] = None) -> None:
        ontology_classification = OntologyClassification(str(self.__current_classification_id()),
                                                         generate_feature_node_hash(),
                                                         [self.__create_classification_attributes(name,
                                                                                                  classification_type,
                                                                                                  required,
                                                                                                  options)])
        self.ontology_classifications.append(ontology_classification)

    def __create_classification_attributes(self,
                                           name: str,
                                           classification_type: ClassificationType,
                                           required: bool,
                                           options: Optional[Iterable[str]]) -> ClassificationAttribute:
        attribute_id = 1
        classification_attribute = ClassificationAttribute(f"{self.__current_classification_id()}.{attribute_id}",
                                                           name,
                                                           classification_type,
                                                           required,
                                                           generate_feature_node_hash())
        if options:
            classification_options = []
            for option_id, option in enumerate(options, 1):
                classification_option = ClassificationOption(
                    f"{self.__current_classification_id()}.{attribute_id}.{option_id}",
                    option,
                    self.__format_option_value(option),
                    generate_feature_node_hash())
                classification_options.append(classification_option)
            classification_attribute.options = classification_options

        return classification_attribute

    def to_dict(self) -> dict:
        objects = []
        classifications = []

        for ontology_object in self.ontology_objects:
            objects.append(self.ontology_object_to_dict(ontology_object))

        for ontology_classification in self.ontology_classifications:
            classifications.append(self.ontology_classification_to_dict(ontology_classification))

        return {'objects': objects, 'classifications': classifications}

    def ontology_object_to_dict(self, ontology_object: OntologyObject) -> Dict:
        return {
            'id': ontology_object.id,
            'color': ontology_object.color,
            'name': ontology_object.name,
            'shape': ontology_object.shape.value,
            'featureNodeHash': ontology_object.feature_node_hash
        }

    def ontology_classification_to_dict(self, ontology_classification: OntologyClassification) -> Dict:
        classification = {
            'id': ontology_classification.id,
            'featureNodeHash': ontology_classification.feature_node_hash,
        }
        attributes = []
        for classification_attribute in ontology_classification.attributes:
            attribute = {
                'id': classification_attribute.id,
                'name': classification_attribute.name,
                'type': classification_attribute.classification_type.value,
                'required': classification_attribute.required,
                'featureNodeHash': classification_attribute.feature_node_hash
            }
            if classification_attribute.options:
                options = []
                for classification_option in classification_attribute.options:
                    option = {
                        'id': classification_option.id,
                        'label': classification_option.label,
                        'value': classification_option.value,
                        'featureNodeHash': classification_option.feature_node_hash
                    }
                    options.append(option)
                attribute['options'] = options
            attributes.append(attribute)

        classification['attributes'] = attributes
        return classification

    def __format_option_value(self, label: str):
        return label.replace(" ", "_")
