# This code is part of Qiskit.
#
# (C) Copyright IBM 2020.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Directives are hints to the pulse compiler for how to process its input programs."""
from abc import ABC
from typing import Optional, Tuple

from qiskit.pulse import channels as chans
from qiskit.pulse.instructions import instruction
from qiskit.pulse.exceptions import PulseError


class Directive(instruction.Instruction, ABC):
    """A compiler directive.

    This is a hint to the pulse compiler and is not loaded into hardware.
    """

    @property
    def duration(self) -> int:
        """Duration of this instruction."""
        return 0


class RelativeBarrier(Directive):
    """Pulse ``RelativeBarrier`` directive."""

    def __init__(self, *channels: chans.Channel, name: Optional[str] = None):
        """Create a relative barrier directive.

        The barrier directive blocks instructions within the same schedule
        as the barrier on channels contained within this barrier from moving
        through the barrier in time.

        Args:
            channels: The channel that the barrier applies to.
            name: Name of the directive for display purposes.
        """
        super().__init__(operands=tuple(channels), name=name)

    @property
    def channels(self) -> Tuple[chans.Channel]:
        """Returns the channels that this schedule uses."""
        return self.operands

    def __eq__(self, other):
        """Verify two barriers are equivalent."""
        return isinstance(other, type(self)) and set(self.channels) == set(other.channels)


class TimeBlockade(Directive):
    """Pulse ``TimeBlockade`` directive.

    This instruction is intended to be used internally within the pulse builder,
    to convert :class:`.Schedule` into :class:`.ScheduleBlock`.
    Because :class:`.ScheduleBlock` cannot take an absolute instruction time interval,
    this directive helps the block representation to find the starting time of an instruction.

    Example:

        This schedule plays constant pulse at t0 = 120.

        .. code-block:: python

            schedule = Schedule()
            schedule.insert(120, Play(Constant(10, 0.1), DriveChannel(0)))

        This schedule block is expected to be identical to above at a time of execution.

        .. code-block:: python

            block = ScheduleBlock()
            block.append(TimeBlockade(120, DriveChannel(0)))
            block.append(Play(Constant(10, 0.1), DriveChannel(0)))

        Such conversion may be done by

        .. code-block:: python

            from qiskit.pulse.transforms import block_to_schedule, remove_directives

            schedule = remove_directives(block_to_schedule(block))


    .. note::

        The TimeBlockade instruction behaves almost identically
        to :class:`~qiskit.pulse.instructions.Delay` instruction.
        However, the TimeBlockade is just a compiler directive and must be removed before execution.
        This may be done by :func:`~qiskit.pulse.transforms.remove_directives` transform.
        Once these directives are removed, occupied timeslots are released and
        user can insert another instruction without timing overlap.
    """

    def __init__(
        self,
        duration: int,
        channel: chans.Channel,
        name: Optional[str] = None,
    ):
        """Create a time blockade directive.

        Args:
            duration: Length of time of the occupation in terms of dt.
            channel: The channel that will be the occupied.
            name: Name of the time blockade for display purposes.
        """
        super().__init__(operands=(duration, channel), name=name)

    def _validate(self):
        """Called after initialization to validate instruction data.

        Raises:
            PulseError: If the input ``duration`` is not integer value.
        """
        if not isinstance(self.duration, int):
            raise PulseError(
                "TimeBlockade duration cannot be parameterized. Specify an integer duration value."
            )

    @property
    def channel(self) -> chans.Channel:
        """Return the :py:class:`~qiskit.pulse.channels.Channel` that this instruction is
        scheduled on.
        """
        return self.operands[1]

    @property
    def channels(self) -> Tuple[chans.Channel]:
        """Returns the channels that this schedule uses."""
        return (self.channel,)

    @property
    def duration(self) -> int:
        """Duration of this instruction."""
        return self.operands[0]
