# Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.

from datetime import datetime, timezone
from typing import TypeVar, Dict, List, Any, Type, Union


# Type variable used in the parse methods below, which should be a subclass of BaseModel.
T = TypeVar("T", bound="BaseModel")


class BaseModel:

    def __init__(self, **kwargs) -> None:
        self.__dict__.update(kwargs)
        self.validate()

    # Validates the model, raising an exception if the model is invalid.
    def validate(self) -> None:
        pass

    def __eq__(self, other):
        """Checks whether the two models are equal.

        :param other: The other model.
        :return: True if they are equal, False if they are different.
        """
        return type(self) == type(other) and self.toDict() == other.toDict()

    def __ne__(self, other) -> bool:
        """Checks whether the two models are different.

        :param other: The other model.
        :return: True if they are different, False if they are the same.
        """
        return type(self) != type(other) or self.toDict() != other.toDict()

    def toDict(self) -> Dict[str, Any]:
        """Converts the model into a serializable dictionary"""

        return self.__dict__

    @staticmethod
    def parseModel(model_class: Type[T], values: Union[T, Dict[str, Any]]) -> T:
        """Parses a single model.

        :param model_class: The model class.
        :param values: The value of the model, which is usually a dictionary, but may also be already parsed.
        :return: An instance of the model_class given.
        """
        if isinstance(values, dict):
            return model_class(**values)
        return values

    @classmethod
    def parseModels(cls, model_class: Type[T], values: List[Union[T, Dict[str, Any]]]) -> List[T]:
        """Parses a list of models.

        :param model_class: The model class.
        :param values: The value of the list. Each value is usually a dictionary, but may also be already parsed.
        :return: A list of instances of the model_class given.
        """
        return [cls.parseModel(model_class, value) for value in values]

    @staticmethod
    def parseDate(date: Union[str, datetime]) -> datetime:
        """Parses the given date string.

        :param date: The date to parse.
        :return: The parsed date.
        """
        if isinstance(date, datetime):
            return date
        return datetime.strptime(date, "%Y-%m-%dT%H:%M:%S.%fZ").replace(tzinfo=timezone.utc)
