# coding: utf-8
import warnings
from typing import Any
from typing import Optional
from typing import Text

from .callbacks import Callbacks
from .exceptions import StateMachineError
from .transition import Transition
from .transition_list import TransitionList
from .utils import ugettext as _


class State(object):
    """
    A State in a state machine describes a particular behaviour of the machine.
    When we say that a machine is “in” a state, it means that the machine behaves
    in the way that state describes.

    Args:
        name: An human readable representation of the state.
        value: A specific value to the storage and retrieval of states.
            If specified, you can use It to map a more friendly representation to a low-level
            value.
        initial: Set `' True`` if the ``State`` is the initial one. There must be one and only
            one initial state in a statemachine. Defaults to ``False``.
        final: Set ``True`` if represents a final state. A machine can have
            optionally many final states. Final states have no :ref:`transition` starting from It.
            Defaults to ``False``.
        enter: One or more callbacks assigned to be executed when the state is entered.
            See :ref:`actions`.
        exit: One or more callbacks assigned to be executed when the state is exited.
            See :ref:`actions`.

    State is a core component on how this library implements an expressive API to declare
    StateMachines.

    >>> from statemachine import State

    Given a few states...

    >>> draft = State("Draft", initial=True)

    >>> producing = State("Producing")

    >>> closed = State('Closed', final=True)

    Transitions are declared using the :func:`State.to` or :func:`State.from_` (reversed) methods.

    >>> draft.to(producing)
    TransitionList([Transition(State('Draft', ...

    The result is a `TransitionList`. Don't worry about this internal class. But the good
    thing is that it implements the ``OR`` operator to combine transitions, so you can use the
    ``|`` syntax to compound a list of transitions and assign to the same event.

    >>> transitions = draft.to(draft) | draft.to(producing)

    >>> [(t.source.name, t.target.name) for t in transitions]
    [('Draft', 'Draft'), ('Draft', 'Producing')]

    There are handy shortcuts that you can use to express this same set of transitions.

    The first one, ``draft.to(draft)``, is also called a :ref:`self-transition`, and can be
    expressed using an alternative syntax:

    >>> draft.to.itself()
    TransitionList([Transition(State('Draft', ...

    You can even pass a list of target states to declare at once all transitions starting
    from the same state.

    >>> transitions = draft.to(draft, producing, closed)

    >>> [(t.source.name, t.target.name) for t in transitions]
    [('Draft', 'Draft'), ('Draft', 'Producing'), ('Draft', 'Closed')]

    """

    def __init__(
        self, name, value=None, initial=False, final=False, enter=None, exit=None
    ):
        # type: (Text, Optional[Any], bool, bool, Optional[Any], Optional[Any]) -> None
        self.name = name
        self.value = value
        self._id = None  # type: Optional[Text]
        self._initial = initial
        self.transitions = TransitionList()
        self._final = final
        self.enter = Callbacks().add(enter)
        self.exit = Callbacks().add(exit)

    def _setup(self, resolver):
        self.enter.setup(resolver)
        self.exit.setup(resolver)

    def _add_observer(self, *resolvers):
        for r in resolvers:
            self.enter.add(
                "on_enter_state", resolver=r, prepend=True, suppress_errors=True
            )
            self.enter.add(
                "on_enter_{}".format(self.id), resolver=r, suppress_errors=True
            )
            self.exit.add(
                "on_exit_state", resolver=r, prepend=True, suppress_errors=True
            )
            self.exit.add(
                "on_exit_{}".format(self.id), resolver=r, suppress_errors=True
            )

    def __repr__(self):
        return "{}({!r}, id={!r}, value={!r}, initial={!r}, final={!r})".format(
            type(self).__name__,
            self.name,
            self.id,
            self.value,
            self.initial,
            self.final,
        )

    def __get__(self, machine, owner):
        self.machine = machine
        return self

    def __set__(self, instance, value):
        raise StateMachineError(
            _("State overriding is not allowed. Trying to add '{}' to {}").format(
                value, self.id
            )
        )

    @property
    def id(self):
        return self._id

    @property
    def identifier(self):
        warnings.warn(
            "`State.identifier` is deprecated. Use `State.id` instead.",
            DeprecationWarning,
        )
        return self.id

    def _set_id(self, id):
        self._id = id
        if self.value is None:
            self.value = id

    def _to_(self, *states, **kwargs):
        transitions = TransitionList(
            Transition(self, state, **kwargs) for state in states
        )
        self.transitions.add_transitions(transitions)
        return transitions

    def _from_(self, *states, **kwargs):
        transitions = TransitionList()
        for origin in states:
            transition = Transition(origin, self, **kwargs)
            origin.transitions.add_transitions(transition)
            transitions.add_transitions(transition)
        return transitions

    def _get_proxy_method_to_itself(self, method):
        def proxy(*states, **kwargs):
            return method(*states, **kwargs)

        def proxy_to_itself(**kwargs):
            return proxy(self, **kwargs)

        proxy.itself = proxy_to_itself
        return proxy

    @property
    def to(self):
        """Create transitions to the given target states."""
        return self._get_proxy_method_to_itself(self._to_)

    @property
    def from_(self):
        """Create transitions from the given target states (reversed)."""
        return self._get_proxy_method_to_itself(self._from_)

    @property
    def initial(self):
        return self._initial

    @property
    def final(self):
        return self._final

    @property
    def is_active(self):
        return self.machine.current_state == self
