#  SPDX-FileCopyrightText: 2023 easyCrystallography contributors <crystallography@easyscience.software>
#  SPDX-License-Identifier: BSD-3-Clause
#  © 2022-2023  Contributors to the easyCore project <https://github.com/easyScience/easyCrystallography>


__author__ = "github.com/wardsimon"
__version__ = "0.1.0"

from typing import List, Dict, Union, Any
from copy import deepcopy
from easyCore.Objects.ObjectClasses import Descriptor
from easyCore.Utils.classTools import addProp, removeProp
import periodictable as pt
import re

_SPECIE_DETAILS = {
    "type_symbol": {
        "description": "A code to identify the atom species occupying this site.",
        "url":         "https://www.iucr.org/__data/iucr/cifdic_html/1/cif_core.dic/Iatom_site_type_symbol.html",
    },
}

_REDIRECT = deepcopy(Descriptor._REDIRECT)
_REDIRECT['specie'] = lambda obj: obj._raw_data['str']


class Specie(Descriptor):

    _REDIRECT = _REDIRECT

    def __init__(self, specie: str = "H", **kwargs):
        if "value" in kwargs.keys():
            specie = kwargs.pop("value")

        self._raw_data: Dict[str, Union[str, int]] = {}
        self._props: Dict[str, Any] = {}

        self._reset_data()
        super(Specie, self).__init__("specie", self.__gen_data(specie), **_SPECIE_DETAILS["type_symbol"])

        # Monkey patch the unit and the value to take into account the new type situation
        self.__previous_set = self.__class__.value.fset

        addProp(
            self,
            "value",
            fget=self.__class__.value.fget,
            fset=lambda obj, val: self.__previous_set(obj, obj.__gen_data(val)),
            fdel=self.__class__.value.fdel,
        )

    def _reset_data(self):
        self._raw_data = dict.fromkeys(["str", "observed", "element", "isotope", "oxi_state", "spin"])
        self._props = {}

    def __gen_data(self, value_str: str):
        s = re.search(r"([0-9.]*)([A-Z][a-z]*)([0-9.]*)([+\-]*)", value_str)
        # group(1) = Isotope
        # group(2) = Element
        # group(3) = Oxi state
        # group(4) = Oxi +/2

        if s is None:
            raise ValueError("Invalid specie string")
        element_str = s.group(2)
        element = getattr(pt, element_str, None)
        if element is None:
            raise ValueError(f"Element ({s.group(2)}) not found in periodictable")

        if self._props is not None:
            for k in self._props.keys():
                removeProp(self, k)

        self._reset_data()
        self._raw_data["str"] = value_str
        self._raw_data["element"] = element
        self._raw_data["observed"] = element

        props = {s: getattr(self._raw_data["observed"], s)
                 for s in self._raw_data["observed"].__dir__()
                 if not s.startswith('_') and hasattr(self._raw_data["observed"], s)
                 }
        isotope_str = s.group(1)
        self._raw_data["isotope"] = None
        if isotope_str:
            self._raw_data["isotope"] = int(isotope_str)
            self._raw_data["observed"] = pt.core.Isotope(self._raw_data["observed"], self._raw_data["isotope"])
            props.update(
                {s: getattr(self._raw_data["observed"], s)
                 for s in self._raw_data["observed"].__dir__()
                 if not s.startswith('_') and hasattr(self._raw_data["observed"], s)
                 }
            )
        oxi_state_str = s.group(3)
        oxi_pm_str = s.group(4)
        self._raw_data["oxi_state"] = None
        if oxi_state_str:
            self._raw_data["oxi_state"] = int(oxi_pm_str + oxi_state_str)
            self._raw_data["observed"] = pt.core.Ion(self._raw_data["observed"], self._raw_data["oxi_state"])
            props.update(
                {s: getattr(self._raw_data["observed"], s)
                 for s in self._raw_data["observed"].__dir__()
                 if not s.startswith('_') and hasattr(self._raw_data["observed"], s)
                 }
            )
        props["common_name"] = props.pop("name")
        self._props = props
        for k, v in props.items():
            addProp(self, k, fget=self.__getter_periodic(k))
        rep = f"{self._raw_data['element']}"
        if self.is_ion:
            rep += f"{self._raw_data['oxi_state']}"
            if self._raw_data["oxi_state"] > 0:
                rep += "+"
        return rep

    def __repr__(self) -> str:
        rep = f"<{self.__class__.__name__} \'{self.name}\': "
        if self.is_isotope:
            rep += f"{self._raw_data['isotope']}"
        rep += f"{self._raw_data['element']}"
        if self.is_ion:
            if self._raw_data["oxi_state"] > 0:
                rep += f"{self._raw_data['oxi_state']}+"
            else:
                rep += f"{self._raw_data['oxi_state']}-"
        rep += ">"
        return rep

    def __str__(self) -> str:
        rep = ''
        if self.is_isotope:
            rep += f"{self._raw_data['isotope']}"
        rep += f"{self._raw_data['element']}"
        if self.is_ion:
            if self._raw_data["oxi_state"] > 0:
                rep += f"{self._raw_data['oxi_state']}+"
            else:
                rep += f"{str(self._raw_data['oxi_state'])[1:]}-"
        return rep

    @staticmethod
    def __getter_periodic(key: str):
        def getter(obj):
            return obj._props.get(key)

        return getter

    @property
    def is_ion(self):
        return self._raw_data["oxi_state"] is not None

    @property
    def is_isotope(self):
        return self._raw_data["isotope"] is not None