"""
Defines the flag diacritics base class, as well as all subclasses.
"""

from typing import Dict

from .symbol import Symbol

__all__ = ["FlagDiacritic", "Clear", "Disallow", "Positive"]


class FlagDiacritic(Symbol):
    """
    Base class for all flag diacritics
    """

    __slots__ = ("feature",)

    opcode = "!!INVALID!!"

    # By setting this, we avoid an isinstance() check:
    is_flag_diacritic = True

    def __init__(self, feature: str) -> None:
        self.feature = feature

    def __eq__(self, other) -> bool:
        if not isinstance(other, type(self)):
            return False
        else:
            return self.feature == other.feature

    def __hash__(self) -> int:
        return hash((self.opcode, self.feature))

    def __repr__(self) -> str:  # pragma: no cover
        return "{:s}({!r})".format(type(self).__name__, self.feature)

    def __str__(self) -> str:
        return "@{}.{}@".format(self.opcode, self.feature)

    def test(self, flags: Dict[str, str]) -> bool:  # pragma: no cover
        """
        Test the flag against the current values.
        """
        raise NotImplementedError

    def apply(self, flags: Dict[str, str]) -> None:
        """
        Destructivley modifies the flags in some way.
        """


class FlagDiacriticWithValue(FlagDiacritic):
    __slots__ = ("value",)

    def __init__(self, feature: str, value: str = None) -> None:
        super().__init__(feature)
        if value is not None:
            self.value = value

    def __eq__(self, other) -> bool:
        return super().__eq__(other) and self.value == other.value

    def __hash__(self) -> int:
        return hash((self.opcode, self.feature, self.value))

    def __repr__(self) -> str:  # pragma: no cover
        return "{:s}({!r}, {!r})".format(type(self).__name__, self.feature, self.value)

    def __str__(self) -> str:
        return "@{}.{}.{}@".format(self.opcode, self.feature, self.value)


class Unify(FlagDiacriticWithValue):
    """
    Unification flag.

    If a feature is unset, set it to the given value.
    If a feature IS set, allow only when it is equal to the given value.
    """

    opcode = "U"

    def test(self, flags: Dict[str, str]) -> bool:
        if self.feature not in flags:
            return True
        else:
            return flags[self.feature] == self.value

    def apply(self, flags: Dict[str, str]) -> None:
        assert self.feature not in flags or flags[self.feature] == self.value
        flags[self.feature] = self.value


class Clear(FlagDiacritic):
    opcode = "C"

    def test(self, flags: Dict[str, str]) -> bool:
        """
        Unconditionally accept!
        """
        return True

    def apply(self, flags: Dict[str, str]):
        flags.pop(self.feature, None)


class DisallowFeature(FlagDiacritic):
    opcode = "D"

    def test(self, flags: Dict[str, str]):
        return self.feature not in flags


class DisallowValue(FlagDiacriticWithValue):
    """
    Accepts ONLY if the feature is not set to the given value.
    That is, rejects if the feature is set to the given value.
    """

    opcode = "D"

    def test(self, flags: Dict[str, str]) -> bool:
        return flags.get(self.feature) != self.value


class Positive(FlagDiacriticWithValue):
    opcode = "P"

    def test(self, flags: Dict[str, str]) -> bool:
        return True

    def apply(self, flags: Dict[str, str]) -> None:
        flags[self.feature] = self.value


class RequireValue(FlagDiacriticWithValue):
    """
    Accepts ONLY if the feature is set to the given value.
    """

    opcode = "R"

    def test(self, flags: Dict[str, str]) -> bool:
        return flags.get(self.feature) == self.value


class RequireFeature(FlagDiacritic):
    """
    Accepts if the feature is set to any value.
    """

    opcode = "R"

    def test(self, flags: Dict[str, str]) -> bool:
        return self.feature in flags


# TODO: implement @N.feat.val@
# TODO: implement @E.feat.val@ (makes sense when values have a negative)
# See: https://github.com/mhulden/foma/blob/4e98d61ce67babe1555e1e1b04d5a65bc3b5a03a/foma/apply.c#L1380-L1489
