from __future__ import annotations

import re
from typing import Any, Tuple, Union

from .abstract import (
    FifthsScalar,
    Interval,
    IntervalClass,
    IntType,
    OctaveNumber,
    P,
    Pitch,
    PitchClass,
    SemitonesScalar,
    StepsScalar,
)
from .errors import InvalidInitValue, StepUnknown
from .interval import (
    EnharmonicInterval,
    EnharmonicIntervalClass,
    GenericInterval,
    GenericIntervalClass,
    SpecificInterval,
    SpecificIntervalClass,
)
from .utils import (
    fifths2note_name,
    general_tuple,
    interval_tuple_from_sd,
    parse_generic_pitch,
    semitone_difference,
    specific_tuple,
    split_pitch_name,
    step_difference,
    steps2note_name,
)


class EnharmonicPitchClass(SemitonesScalar, PitchClass):
    """Enharmonic Pitch Class (equivalent to the PC of pitch-class set theory).
    An EPC value is always within [0, 11] and can be interpreted as an enumeration of piano
    keys from C up to B within the same octave.

    Initialized from an integer within [0, 11]. Other values will be converted. For instance:

        * integers outside this interval are transformed with modulo 12, e.g. EPC(-3) => EPC(9)
        * note names are mapped to their piano key, e.g. EPC("G#") => EPC(8)
        * floats are converted to an integer (i.e., rounded)

    Integers represented as string are accepted, too.
    """

    def __str__(self):
        return f"EPC({self.semitones})"

    def __repr__(self):
        return f"EPC({self.semitones})"

    def _repr_html_(self):
        from flexohr.core.html_colors import PITCHSPACE_COLORS

        c = PITCHSPACE_COLORS["EPC"]
        return f'<span style="font-family:monospace;color:{c}"><b>EPC</b>(<i>{self.semitones}</i>)</span>'

    def __add__(self, other: Union[Interval, IntervalClass, int, IntType]) -> Union[PitchClass]:
        if isinstance(
            other,
            EnharmonicInterval | EnharmonicIntervalClass | SpecificInterval | SpecificIntervalClass,
        ):
            return EnharmonicPitchClass((self.semitones + other.semitones) % 12)
        elif isinstance(other, GenericInterval | GenericIntervalClass):
            raise StepUnknown(f"{type(self)} + {type(other)}")

        return super().__add__(other)

    def __sub__(
        self, other: Union[Pitch, Interval, PitchClass, IntervalClass, int, IntType]
    ) -> Union[PitchClass, IntervalClass]:
        if isinstance(
            other,
            EnharmonicPitch | EnharmonicPitchClass | SpecificPitch | SpecificPitchClass,
        ):
            return EnharmonicIntervalClass((self.semitones - other.semitones) % 12)
        elif isinstance(
            other,
            EnharmonicInterval | EnharmonicIntervalClass | SpecificInterval | SpecificIntervalClass,
        ):
            return EnharmonicPitchClass((self.semitones - other.semitones) % 12)
        elif isinstance(
            other,
            GenericPitch | GenericPitchClass | GenericInterval | GenericIntervalClass,
        ):
            raise StepUnknown(f"{type(self)} - {type(other)}")

        return super().__sub__(other)

    def to_vector(self) -> "EnharmonicIntervalClass":
        return EnharmonicIntervalClass(self.semitones)


class SpecificPitchClass(FifthsScalar, PitchClass):
    """Specific pitch class, also called tonal pitch class, or simply 'note name'. An SPC is
    numerically represented as a stack of perfect fifths over C, e.g. SPC("F")._value == -1

    Initialized from a note name (e.g. "G#" or "Ab") or from an integer representing a stack of
    perfect fifths over C. Integers represented as string are accepted, too. Floats are
    converted to an integer (i.e., rounded).
    """

    @classmethod
    def from_fifths(cls, fifths: int):
        instance = super().__new__(cls, fifths)
        return instance

    @property
    def name(self):
        return fifths2note_name(self.fifths)

    def __str__(self):
        return f"SPC('{self.name}')"

    def __repr__(self):
        return f"SPC({self.name})"

    def _repr_html_(self):
        from flexohr.core.html_colors import PITCHSPACE_COLORS

        c = PITCHSPACE_COLORS["SPC"]
        return f'<span style="font-family:monospace;color:{c}"><b>SPC</b>(<i>{self.name}</i>)</span>'

    def __add__(self, other: Union[Interval, IntervalClass, int, IntType]) -> Union[Pitch, PitchClass]:
        if isinstance(other, EnharmonicInterval | EnharmonicIntervalClass):
            return EnharmonicPitchClass((self.semitones + other.semitones) % 12)
        elif isinstance(other, SpecificInterval | SpecificIntervalClass):
            return SpecificPitchClass(self.fifths + other.fifths)
        elif isinstance(other, GenericInterval | GenericIntervalClass):
            return GenericPitchClass((self.steps + other.steps) % 7)

        return super().__add__(other)

    def __sub__(
        self, other: Union[Pitch, Interval, PitchClass, IntervalClass, int, IntType]
    ) -> Union[Pitch, Interval, PitchClass, IntervalClass]:
        if isinstance(other, EnharmonicPitch | EnharmonicPitchClass):
            return EnharmonicIntervalClass((self.semitones - other.semitones) % 12)
        elif isinstance(other, EnharmonicInterval | EnharmonicIntervalClass):
            return EnharmonicPitchClass((self.semitones - other.semitones) % 12)

        elif isinstance(other, SpecificPitch | SpecificPitchClass):
            return SpecificIntervalClass(self.fifths - other.fifths)
        elif isinstance(other, SpecificInterval | SpecificIntervalClass):
            return SpecificPitchClass(self.fifths - other.fifths)

        elif isinstance(other, GenericPitch | GenericPitchClass):
            return GenericIntervalClass((self.steps - other.steps) % 7)
        elif isinstance(other, GenericInterval | GenericIntervalClass):
            return GenericPitchClass((self.steps - other.steps) % 7)

        return super().__sub__(other)

    def to_interval(self) -> "SpecificIntervalClass":
        return SpecificIntervalClass(self.fifths)


class GenericPitchClass(StepsScalar, PitchClass):
    """Generic Pitch Class, also called step. A GPC value is always within [0, 6] and can be
    interpreted as an enumeration of the white piano keys from C to B within the same octave.

    Initialized from a note name (e.g., "G"), optionally preceded by lowercase "g" (e.g., "gG"),
    or from an integer within [0, 6]. Other values will be converted. For instance:

        * integers outside this interval are transformed with modulo 7, e.g. EPC(-3) => EPC(4)
        * note names are mapped to their white key, e.g. EPC("G#") => EPC("gG")
        * floats are converted to an integer (i.e., rounded)

    Integers represented as string are accepted, too.
    """

    @staticmethod
    def convert_init_value(value: Union[int, str, IntType]) -> int:
        try:
            return StepsScalar.convert_init_value(value)
        except ValueError:
            if isinstance(value, str) and re.match("^g[A-Ga-g]$", value):
                return StepsScalar.convert_init_value(value[1:])
            raise

    @property
    def name(self):
        return steps2note_name(self.steps)

    def __str__(self):
        return f"GPC(g{self.name})"

    def __repr__(self):
        return f"GPC(g{self.name})"

    def _repr_html_(self):
        from flexohr.core.html_colors import PITCHSPACE_COLORS

        c = PITCHSPACE_COLORS["GPC"]
        return f'<span style="font-family:monospace;color:{c}"><b>GPC</b>(<i>g{self.name}</i>)</span>'

    def __add__(self, other: Union[Interval, IntervalClass, int, IntType]) -> Union[PitchClass]:
        if isinstance(other, EnharmonicInterval | EnharmonicIntervalClass):
            raise StepUnknown(f"{type(self)} + {type(other)}")
        elif isinstance(
            other,
            SpecificInterval | SpecificIntervalClass | GenericInterval | GenericIntervalClass,
        ):
            return GenericPitchClass((self.steps + other.steps) % 7)

        return super().__add__(other)

    def __sub__(
        self, other: Union[Pitch, Interval, PitchClass, IntervalClass, int, IntType]
    ) -> Union[PitchClass, IntervalClass]:
        if isinstance(
            other,
            EnharmonicPitch | EnharmonicPitchClass | EnharmonicInterval | EnharmonicIntervalClass,
        ):
            raise StepUnknown(f"{type(self)} - {type(other)}")

        elif isinstance(other, SpecificPitch | SpecificPitchClass | GenericPitch | GenericPitchClass):
            return GenericIntervalClass((self.steps - other.steps) % 7)
        elif isinstance(
            other,
            SpecificInterval | SpecificIntervalClass | GenericInterval | GenericIntervalClass,
        ):
            return GenericPitchClass((self.steps - other.steps) % 7)

        return super().__sub__(other)


class EnharmonicPitch(Pitch[EnharmonicPitchClass, OctaveNumber]):
    """An Enharmonic Pitch is an :class:`EnharmonicPitchClass` paired with an octave number and
    equivalent to a MIDI pitch (i.e., step and accidentals are not known).

    Initialized from a pair of two values (EPC, Octave) or from an integer representing a MIDI
    number (not bounded within [0, 127]). When initialized from a pair, the first value can be
    any value that can be used for initializing an EPC. Integers represented as string are
    accepted, too, including those ending on ":", e.g. EP("60:")
    """

    @staticmethod
    def convert_init_value(
        value: Union[
            Tuple[EnharmonicPitchClass | int, OctaveNumber | int],
            str,
            int,
            "SpecificPitch",
        ],
    ) -> Tuple[EnharmonicPitchClass, OctaveNumber]:
        """Used by the constructor to convert the initial value into a pair of IntType."""
        if isinstance(value, tuple):
            pitch_class, octave = value
            if isinstance(pitch_class, int):
                while pitch_class < 0:
                    pitch_class += 12
                    octave -= 1
                while pitch_class >= 12:
                    pitch_class -= 12
                    octave += 1
            # else: pitch_class will be converted to EnharmonicPitchClass at the bottom
        elif (isinstance(value, str) and (value.isdigit() or value.endswith(":"))) or isinstance(value, int):
            if isinstance(value, str) and value.endswith(":") and value[:-1].isdigit():
                value = int(value[:-1])
            value_int = int(value)
            pitch_class = value_int
            octave = value_int // 12 - 1
        elif isinstance(value, str):
            pitch_class, octave = split_pitch_name(value)
        else:
            raise InvalidInitValue(
                f"An EnharmonicPitch cannot be expressed as a {type(value)}. Pass a pitch name as a string "
                f"or a (PitchClass, Octave) tuple."
            )
        return EnharmonicPitchClass(pitch_class), OctaveNumber(octave)

    def __new__(
        cls,
        value: Union[Tuple[EnharmonicPitchClass | int, OctaveNumber | int], str, int],
    ):
        pitch_class, octave = cls.convert_init_value(value)
        return super().__new__(cls, (pitch_class, octave))

    @property
    def midi(self) -> int:
        """Alias of the semitones property, representing the MIDI number of the pitch."""
        if self.semitones in [0, 127]:
            return self.semitones
        raise ValueError(f"Cannot convert {self} to MIDI number.")

    @property
    def name(self) -> str:
        return str(self.midi)

    @property
    def semitones(self) -> int:
        """For an EP, the value of the semitones property correspond to the MIDI number."""
        return int(self.octave + 1) * 12 + self.pitch_class.semitones

    def __repr__(self):
        return f"EP({self.pitch_class.semitones}, {self.octave})"

    def __str__(self):
        return f"EP({self.pitch_class.semitones}, {self.octave})"

    def _repr_html_(self):
        from flexohr.core.html_colors import PITCHSPACE_COLORS

        c = PITCHSPACE_COLORS["EP"]
        return (
            f'<span style="font-family:monospace;color:{c}">'
            f"<b>EP</b>(<i>{self.pitch_class.semitones}, {self.octave}</i>)</span>"
        )

    def __add__(
        self,
        other: Union[
            Interval,
            IntervalClass,
            int,
            IntType,
            Tuple[EnharmonicIntervalClass | int, OctaveNumber | int],
        ],
    ) -> (
        P
        | EnharmonicPitchClass
        | EnharmonicPitch
        | tuple[EnharmonicIntervalClass, OctaveNumber]
        | tuple[PitchClass | Any, OctaveNumber | Any]
    ):
        if isinstance(other, EnharmonicInterval | SpecificInterval):
            return EnharmonicPitch(
                general_tuple(
                    self.pitch_class.semitones,
                    self.octave,
                    other.interval_class.semitones,
                    other.octaves,
                    12,
                    1,
                )
            )
        elif isinstance(other, EnharmonicIntervalClass | SpecificIntervalClass):
            return EnharmonicPitchClass((self.pitch_class.semitones + other.semitones) % 12)
        elif isinstance(other, GenericInterval | GenericIntervalClass):
            raise StepUnknown(f"{type(self)} - {type(other)}")

        elif Tuple == type(other):
            return self + EnharmonicInterval(other)
        return super().__add__(other)

    def __sub__(
        self,
        other: Union[
            Pitch,
            Interval,
            PitchClass,
            IntervalClass,
            int,
            IntType,
            Tuple[EnharmonicIntervalClass | int, OctaveNumber | int],
        ],
    ) -> (
        EnharmonicInterval
        | P
        | EnharmonicIntervalClass
        | EnharmonicPitchClass
        | EnharmonicPitch
        | tuple[EnharmonicIntervalClass, OctaveNumber]
        | tuple[PitchClass | Any, OctaveNumber | Any]
    ):
        if isinstance(other, EnharmonicPitch | SpecificPitch):
            sd = semitone_difference(
                self.pitch_class.semitones,
                self.octave,
                other.pitch_class.semitones,
                other.octave,
            )
            return EnharmonicInterval(interval_tuple_from_sd(sd, 12))

        elif isinstance(other, EnharmonicInterval | SpecificInterval):
            sd = semitone_difference(
                self.pitch_class.semitones,
                self.octave,
                other.interval_class.semitones,
                other.octaves,
            )
            p_tuple = (sd % 12, sd // 12)
            return EnharmonicPitch(p_tuple)

        elif isinstance(other, EnharmonicPitchClass | SpecificPitchClass):
            return EnharmonicIntervalClass((self.pitch_class.semitones - other.semitones) % 12)

        elif isinstance(other, EnharmonicIntervalClass | SpecificIntervalClass):
            return EnharmonicPitchClass((self.pitch_class.semitones - other.semitones) % 12)
        elif isinstance(
            other,
            GenericPitch | GenericPitchClass | GenericInterval | GenericIntervalClass,
        ):
            raise StepUnknown(f"{type(self)} - {type(other)}")
        elif Tuple == type(other):
            return self - EnharmonicInterval(other)

        return super().__sub__(other)

    def __eq__(self, other):
        if isinstance(other, EnharmonicPitch):
            return self.pitch_class.semitones == other.pitch_class.semitones and self.octave == other.octave
        return False


class SpecificPitch(Pitch[SpecificPitchClass, OctaveNumber]):
    """A Specific Pitch, also called "spelled pitch", is a :class:`SpecificPitchClass`
    paired with an octave number. It is the most complete pitch representation because step,
    accidentals, and octave are known.

    Initialized from a pair of two values (SPC, Octave) or from a pitch name such as "C#4".
    When initialized from a pair, the first value can be any value that can be used for
    initializing an SPC.
    """

    @staticmethod
    def convert_init_value(
        value: Union[Tuple[SpecificPitchClass | int, OctaveNumber | int], str, int],
    ) -> Tuple[SpecificPitchClass, OctaveNumber]:
        """Used by the constructor to convert the initial value into a pair of IntType."""
        if isinstance(value, tuple):
            pitch_class, octave = value
        elif isinstance(value, str):
            pitch_class, octave = split_pitch_name(value)
        else:
            raise InvalidInitValue(
                f"A SpecificPitch cannot be expressed as a {type(value)}. Pass a pitch name as a string "
                f"or a (PitchClass, Octave) tuple."
            )
        return SpecificPitchClass(pitch_class), OctaveNumber(octave)

    @property
    def fifths(self) -> int:
        return self.pitch_class.fifths

    @property
    def midi(self) -> int:
        """Alias of the semitones property, representing the MIDI number of the pitch."""
        if self.semitones in [0, 127]:
            return self.semitones
        raise ValueError(f"Cannot convert {self} to MIDI number.")

    @property
    def name(self) -> str:
        return f"{self.pitch_class.name}{int(self.octave)}"

    @property
    def semitones(self) -> int:
        """For an EP, the value of the semitones property correspond to the MIDI number."""
        return int(self.octave + 1) * 12 + self.pitch_class.semitones

    @property
    def steps(self) -> int:
        return self.pitch_class.steps

    def __repr__(self):
        return f"SP({self.name})"

    def __str__(self):
        return f"SP({self.name})"

    def _repr_html_(self):
        from flexohr.core.html_colors import PITCHSPACE_COLORS

        c = PITCHSPACE_COLORS["SP"]
        return f'<span style="font-family:monospace;color:{c}"><b>SP</b>(<i>{self.name}</i>)</span>'

    def __add__(
        self,
        other: Union[
            Interval,
            IntervalClass,
            int,
            IntType,
            Tuple[SpecificIntervalClass | int, OctaveNumber | int],
        ],
    ) -> (
        P
        | SpecificPitch
        | GenericPitch
        | EnharmonicPitchClass
        | SpecificPitchClass
        | GenericPitchClass
        | Pitch
        | PitchClass
        | tuple[SpecificIntervalClass, OctaveNumber]
        | tuple[PitchClass | Any, OctaveNumber | Any]
    ):
        if isinstance(other, EnharmonicInterval):
            return EnharmonicPitch(
                general_tuple(
                    self.pitch_class.semitones,
                    self.octave,
                    other.interval_class.semitones,
                    other.octaves,
                    12,
                    1,
                )
            )
        # SpecificPitch + SpecificInterval = SpecificPitch
        elif isinstance(other, SpecificInterval):
            return SpecificPitch(
                specific_tuple(
                    self.fifths,
                    self.pitch_class.semitones,
                    self.octave,
                    other.fifths,
                    other.interval_class.semitones,
                    other.octaves,
                    1,
                )
            )
        # SpecificPitch + GenericInterval = GenericPitch
        elif isinstance(other, GenericInterval):
            return GenericPitch(
                general_tuple(
                    self.pitch_class.steps,
                    self.octave,
                    other.interval_class.steps,
                    other.octaves,
                    7,
                    1,
                )
            )
        elif isinstance(other, EnharmonicIntervalClass):
            return EnharmonicPitchClass((self.pitch_class.semitones + other.semitones) % 12)
        elif isinstance(other, SpecificIntervalClass):
            return SpecificPitchClass(self.fifths + other.fifths)
        elif isinstance(other, GenericIntervalClass):
            return GenericPitchClass((self.pitch_class.steps + other.steps) % 7)

        elif Tuple == type(other):
            return self + SpecificInterval(other)
        return super().__add__(other)

    def __sub__(
        self,
        other: Union[
            Pitch,
            Interval,
            PitchClass,
            IntervalClass,
            int,
            IntType,
            Tuple[SpecificIntervalClass | int, OctaveNumber | int],
        ],
    ) -> (
        P
        | Pitch
        | Interval
        | PitchClass
        | IntervalClass
        | tuple[SpecificIntervalClass, OctaveNumber]
        | tuple[PitchClass | Any, OctaveNumber | Any]
    ):
        if isinstance(other, EnharmonicPitch):
            sd = semitone_difference(
                self.pitch_class.semitones,
                self.octave,
                other.pitch_class.semitones,
                other.octave,
            )
            return EnharmonicInterval(interval_tuple_from_sd(sd, 12))
        elif isinstance(other, SpecificPitch):
            iv_tuple = (
                self.fifths - other.fifths,
                self.octave - other.octave + (self.pitch_class.semitones - other.pitch_class.semitones) // 12,
            )
            return SpecificInterval(iv_tuple)
        elif isinstance(other, GenericPitch):
            sd = step_difference(
                self.pitch_class.steps,
                self.octave,
                other.pitch_class.steps,
                other.octave,
            )
            return GenericInterval(interval_tuple_from_sd(sd, 7))

        elif isinstance(other, EnharmonicInterval):
            sd = semitone_difference(
                self.pitch_class.semitones,
                self.octave,
                other.interval_class.semitones,
                other.octaves,
            )
            p_tuple = (sd % 12, sd // 12)
            return EnharmonicPitch(p_tuple)
        elif isinstance(other, SpecificInterval):
            p_tuple = (
                self.fifths - other.fifths,
                self.octave - other.octaves + (self.pitch_class.semitones - other.interval_class.semitones) // 12,
            )
            return SpecificPitch(p_tuple)
        elif isinstance(other, GenericInterval):
            sd = step_difference(
                self.pitch_class.steps,
                self.octave,
                other.interval_class.steps,
                other.octaves,
            )
            p_tuple = ((sd % 7), sd // 7)
            return GenericPitch(p_tuple)

        elif isinstance(other, EnharmonicPitchClass):
            return EnharmonicIntervalClass((self.pitch_class.semitones - other.semitones) % 12)
        elif isinstance(other, SpecificPitchClass):
            return SpecificIntervalClass(self.fifths - other.fifths)
        elif isinstance(other, GenericPitchClass):
            return GenericIntervalClass((self.pitch_class.steps - other.steps) % 7)

        elif isinstance(other, EnharmonicIntervalClass):
            return EnharmonicPitchClass((self.pitch_class.semitones - other.semitones) % 12)
        elif isinstance(other, SpecificIntervalClass):
            return SpecificPitchClass(self.fifths - other.fifths)
        elif isinstance(other, GenericIntervalClass):
            return GenericPitchClass((self.pitch_class.steps - other.steps) % 7)

        elif Tuple == type(other):
            return self - SpecificInterval(other)
        return super().__sub__(other)

    def __eq__(self, other):
        if isinstance(other, SpecificPitch):
            return self.fifths == other.fifths and self.octave == other.octave
        return False


class GenericPitch(Pitch[GenericPitchClass, OctaveNumber]):
    """A Generic Pitch is a :class:`GenericPitchClass` paired with an octave number.
    It represents a pitch where the step and the octave are known, but not the accidentals.
    E.g., think of a generic G4 as "any G4", including with any number of sharps or flats.

    Initialized from a pair of two values (GPC, Octave) or from a pitch name such as "C4"
    (optionally preceded by a lowercase "g", as in "gC4").
    When initialized from a pair, the first value can be any value that can be used for
    initializing an GPC."""

    @staticmethod
    def convert_init_value(
        value: Union[
            Tuple[GenericPitchClass | int, OctaveNumber | int],
            str,
            int,
            "SpecificPitch",
        ],
    ) -> Tuple[GenericPitchClass, OctaveNumber]:
        """Used by the constructor to convert the initial value into a pair of IntType."""
        if isinstance(value, tuple):
            pitch_class, octave = value
            if isinstance(pitch_class, int):
                while pitch_class < 0:
                    pitch_class += 7
                    octave -= 1
                while pitch_class >= 7:
                    pitch_class -= 7
                    octave += 1
        elif isinstance(value, str):
            pitch_class, octave = parse_generic_pitch(value)
        else:
            raise InvalidInitValue(
                f"A GenericPitch cannot be expressed as a {type(value)}. Pass a pitch name as a string "
                f"or a (PitchClass, Octave) tuple."
            )
        return GenericPitchClass(pitch_class), OctaveNumber(octave)

    @property
    def name(self) -> str:
        return f"{self.pitch_class.name}{int(self.octave)}"

    @property
    def steps(self) -> int:
        return int(self.octave) * 7 + self.pitch_class.steps

    def __repr__(self):
        return f"GP(g{self.name})"

    def __str__(self):
        return f"GP(g{self.name})"

    def _repr_html_(self):
        from flexohr.core.html_colors import PITCHSPACE_COLORS

        c = PITCHSPACE_COLORS["GP"]
        return f'<span style="font-family:monospace;color:{c}"><b>GP</b>(<i>g{self.name}</i>)</span>'

    def __add__(
        self,
        other: Union[
            Interval,
            IntervalClass,
            int,
            IntType,
            Tuple[GenericIntervalClass | int, OctaveNumber | int],
        ],
    ) -> (
        GenericPitch
        | GenericPitchClass
        | tuple[GenericIntervalClass, OctaveNumber]
        | tuple[PitchClass | Any, OctaveNumber | Any]
    ):
        if isinstance(other, GenericInterval | SpecificInterval):
            return GenericPitch(
                general_tuple(
                    self.pitch_class.steps,
                    self.octave,
                    other.interval_class.steps,
                    other.octaves,
                    7,
                    1,
                )
            )
        elif isinstance(other, GenericIntervalClass | SpecificIntervalClass):
            return GenericPitchClass(self.pitch_class.steps + other.steps)
        elif isinstance(other, EnharmonicInterval | EnharmonicIntervalClass):
            raise StepUnknown(f"{type(self)} + {type(other)}")

        elif Tuple == type(other):
            return self + GenericInterval(other)
        return super().__add__(other)

    def __sub__(
        self,
        other: Union[
            Pitch,
            Interval,
            PitchClass,
            IntervalClass,
            int,
            IntType,
            Tuple[GenericIntervalClass | int, OctaveNumber | int],
        ],
    ) -> (
        GenericInterval
        | GenericPitch
        | GenericIntervalClass
        | GenericPitchClass
        | tuple[GenericIntervalClass, OctaveNumber]
        | tuple[PitchClass | Any, OctaveNumber | Any]
    ):
        if isinstance(other, GenericPitch | SpecificPitch):
            sd = step_difference(
                self.pitch_class.steps,
                self.octave,
                other.pitch_class.steps,
                other.octave,
            )
            return GenericInterval(interval_tuple_from_sd(sd, 7))
        elif isinstance(other, GenericInterval | SpecificInterval):
            sd = step_difference(
                self.pitch_class.steps,
                self.octave,
                other.interval_class.steps,
                other.octaves,
            )
            p_tuple = (sd % 7, sd // 7)
            return GenericPitch(p_tuple)

        elif isinstance(other, GenericPitchClass | SpecificPitchClass):
            return GenericIntervalClass((self.pitch_class.steps - other.steps) % 7)

        elif isinstance(other, GenericIntervalClass | SpecificIntervalClass):
            return GenericPitchClass((self.pitch_class.steps - other.steps) % 7)
        elif isinstance(
            other,
            EnharmonicPitch | EnharmonicPitchClass | EnharmonicInterval | EnharmonicIntervalClass,
        ):
            raise StepUnknown(f"{type(self)} - {type(other)}")

        elif Tuple == type(other):
            return self - GenericInterval(other)
        return super().__sub__(other)

    def __eq__(self, other):
        if isinstance(other, GenericPitch):
            return self.pitch_class.steps == other.pitch_class.steps and self.octave == other.octave
        return False


EPC = EnharmonicPitchClass
SPC = SpecificPitchClass
GPC = GenericPitchClass
EP = EnharmonicPitch
SP = SpecificPitch
GP = GenericPitch
