import abc

import abjad

from .qevents import PitchedQEvent


class GraceHandler:
    """
    Abstract grace-handler.

    Determines what pitch, if any, will be selected from a list of
    ``QEvents`` to be applied to an attack-point generated by a ``QGrid``,
    and whether there should be a ``BeforeGraceContainer`` attached to that
    attack-point.

    When called on a sequence of ``QEvents``, ``GraceHandler``
    subclasses should return a pair, where the first item of the pair
    is a sequence of pitch tokens or ``None``, and where the
    second item of the pair is a ``BeforeGraceContainer`` instance or None.
    """

    ### CLASS VARIABLES ###

    __slots__ = ()

    ### INITIALIZER ###

    def __init__(self):
        pass

    ### SPECIAL METHODS ###

    @abc.abstractmethod
    def __call__(self, q_events):
        """
        Calls grace handler.
        """
        raise NotImplementedError


class CollapsingGraceHandler(GraceHandler):
    """
    Collapsing grace-handler.

    Collapses pitch information into a single chord rather than creating a
    grace container.
    """

    ### CLASS VARIABLES ###

    __slots__ = ()

    ### SPECIAL METHODS ###

    def __call__(self, q_events):
        """
        Calls collapsing grace handler.
        """
        pitches = []
        for q_event in q_events:
            if isinstance(q_event, PitchedQEvent):
                pitches.extend(q_event.pitches)
        return tuple(pitches), None


class ConcatenatingGraceHandler(GraceHandler):
    """
    Concatenating grace-handler.

    Concatenates all but the final ``QEvent`` attached to a ``QGrid`` offset
    into a ``BeforeGraceContainer``, using a fixed leaf duration ``duration``.

    When called, it returns pitch information of final ``QEvent``, and the
    generated ``BeforeGraceContainer``, if any.
    """

    ### CLASS VARIABLES ###

    __slots__ = ("_grace_duration",)

    ### INITIALIZER ###

    def __init__(self, grace_duration=None):
        if grace_duration is None:
            grace_duration = (1, 16)
        grace_duration = abjad.Duration(grace_duration)
        assert grace_duration.has_power_of_two_denominator
        self._grace_duration = grace_duration

    ### SPECIAL METHODS ###

    def __call__(self, q_events):
        """
        Calls concatenating grace handler.
        """
        grace_events, final_event = q_events[:-1], q_events[-1]
        if isinstance(final_event, PitchedQEvent):
            pitches = final_event.pitches
        else:
            pitches = ()

        if grace_events:
            grace_container = abjad.BeforeGraceContainer()
            for q_event in grace_events:
                if isinstance(q_event, PitchedQEvent):
                    if len(q_event.pitches) == 1:
                        leaf = abjad.Note(q_event.pitches[0], self.grace_duration)
                    else:
                        leaf = abjad.Chord(q_event.pitches, self.grace_duration)
                else:
                    leaf = abjad.Rest(self.grace_duration)
                grace_container.append(leaf)
        else:
            grace_container = None

        return pitches, grace_container

    ### PUBLIC PROPERTIES ###

    @property
    def grace_duration(self) -> abjad.Duration:
        """
        Grace duration of concantenating grace handler.
        """
        return self._grace_duration


class DiscardingGraceHandler(GraceHandler):
    """
    Discarindg grace-handler.

    Dscards all but final q-event attached to an offset.

    Does not create grace containers.
    """

    ### CLASS VARIABLES ###

    __slots__ = ()

    ### SPECIAL METHODS ###

    def __call__(self, q_events):
        """
        Calls discarding grace handler.
        """
        q_event = q_events[-1]
        if isinstance(q_event, PitchedQEvent):
            return q_event.pitches, None
        return (), None
