class INode(object):
    """
    This is the foundation of all nodes that take part in the processing of subtitle documents.
    The Node should deal with subtitles in a high level interface,
    which is an instance of :class:`<ebu_tt_live.documents.SubtitleDocument>`. That is the interface which should
    be used to communicate with the carriage mechanism. See :class:`<ebu_tt_live.carriage.ICarriageMechanism>`
    """
    __metaclass__ = AutoRegisteringABCMeta

    @abstractmethod
    def process_document(self, document, **kwargs):
        """
        The central hook that is meant to implement the main functionality of the node.
        A node must implement this method.

        :param kwargs: Extra parameters
        :param document: Can be XML, Document object...etc. depending on the carriage implementation

        """
        raise NotImplementedError() class IConsumerNode(INode):

    _expects = AbstractStaticMember(validate_types_only)

    @classmethod
    def expects(cls):
        """
        Data type expected
        :return:
        """
        if isinstance(cls._expects, AbstractStaticMember):
            raise TypeError('Classmethod relies on abstract property: \'_expects\'')
        return cls._expects

    @abstractproperty
    def consumer_carriage(self):
        """
        Carriage mechanism accessor
        :return:
        """

    @abstractmethod
    def register_consumer_carriage(self, consumer_carriage):
        """
        Input carriage mechanism registration
        :param consumer_carriage:
        """ class IProducerNode(INode):

    _provides = AbstractStaticMember(validate_types_only)

    @abstractmethod
    def resume_producing(self):
        """
        This allows the node to be triggered by events, such as a timer.
        """

    @classmethod
    def provides(cls):
        """
        Data type provided
        :return:
        """
        if isinstance(cls._provides, AbstractStaticMember):
            raise TypeError('Classmethod relies on abstract property: \'_provides\'')
        return cls._provides

    @abstractproperty
    def producer_carriage(self):
        """
        Carriage mechanism accessor
        :return:
        """

    @abstractmethod
    def register_producer_carriage(self, producer_carriage):
        """
        Output carriage mechanism registration
        :param producer_carriage:
        """ class IConsumerCarriage(ICarriageMechanism):
    """
    Carriage mechanism interface for consumer nodes.
    """

    _provides = AbstractStaticMember(validate_types_only)

    @classmethod
    def provides(cls):
        """
        Data type provided

        :return:
        """
        if isinstance(cls._provides, AbstractStaticMember):
            raise TypeError('Classmethod relies on abstract property: \'_provides\'')
        return cls._provides

    @abstractmethod
    def register_consumer_node(self, node):
        """
        Register the consumer node in the carriage mechanism

        :param node:
        :return:
        """

    @abstractproperty
    def consumer_node(self):
        """
        Node accessor

        :return:
        """

    @abstractmethod
    def on_new_data(self, data, **kwargs):
        """
        Implement protocol specific preprocessing here.

        :param kwargs: Extra parameters to send down

        """ class IProducerCarriage(ICarriageMechanism):
    """
    Carriage mechanism interface for producer nodes.
    """

    _expects = AbstractStaticMember(validate_types_only)

    @classmethod
    def expects(cls):
        """
        Data type expected

        :return:
        """
        if isinstance(cls._expects, AbstractStaticMember):
            raise TypeError('Classmethod relies on abstract property: \'_expects\'')
        return cls._expects

    @abstractmethod
    def register_producer_node(self, node):
        """
        Register the producer node in the carriage mechanism

        :param node: The node to connect to.

        """

    @abstractproperty
    def producer_node(self):
        """
        Node accessor

        :return:
        """

    @abstractmethod
    def emit_data(self, data, **kwargs):
        """
        Implement protocol specific postprocessing here.

        :param kwargs: Extra parameters to send down
        :param data:
        :return:
        """

    @abstractmethod
    def resume_producing(self):
        """
        This makes sure that the producers can be pulled. This is good for timer or manual triggering

        :return:
        """ class ComponentCompatError(TypeError):
    pass class DataCompatError(TypeError):
    pass class UnexpectedSequenceIdentifierError(Exception):
    pass ERR_INCOMPATIBLE_COMPONENT = gettext('Incompatible component: {component} Expected interface: {expected_interface}') ERR_INCOMPATIBLE_DATA_EXPECTED = gettext('{component} expects incompatible data type: {expects} Provided type: {provides}') ERR_INCOMPATIBLE_DATA_PROVIDED = gettext('{component} provides incompatible data type: {provides} Expected type: {expects}') class RingBufferWithCallback(collections.deque):
    """
    This class calls a callback when an item is falling out of the buffer due to removal.
    On manual removal it does not. That is the user's responsibility.
    """

    _callback = None

    def __init__(self, iterable=(), maxlen=None, callback=None):
        if callback is not None and not callable(callback):
            raise ValueError('Callback: {} is not callable'.format(callback))
        self._callback = callback
        super(RingBufferWithCallback, self).__init__(iterable, maxlen)

    def append(self, item):
        if len(self) >= self.maxlen:
            if self._callback is not None:
                self._callback(self.popleft())
        super(RingBufferWithCallback, self).append(item) class SubtitleDocument(ComparableMixin):

    def __init__(self):
        raise NotImplementedError('This is an abstract class')

    def validate(self):
        raise NotImplementedError()
from .interface import INode, IConsumerNode, IProducerNode
from ebu_tt_live.carriage.interface import IConsumerCarriage, IProducerCarriage
from ebu_tt_live.errors import ComponentCompatError, DataCompatError, UnexpectedSequenceIdentifierError
from ebu_tt_live.strings import ERR_INCOMPATIBLE_COMPONENT, ERR_INCOMPATIBLE_DATA_EXPECTED, ERR_INCOMPATIBLE_DATA_PROVIDED
from ebu_tt_live.utils import RingBufferWithCallback
from ebu_tt_live.documents import SubtitleDocument
import logging

log = logging.getLogger(__name__)


class __AbstractNode(INode):

    _node_id = None

    def __init__(self, node_id, **kwargs):
        self._node_id = node_id

    def __repr__(self):
        return '<{name}, ID:{id} at {address} >'.format(
            name=self.__class__,
            id=self._node_id,
            address=hex(id(self))
        )

    @property
    def node_id(self):
        return self._node_id

    @node_id.setter
    def node_id(self, value):
        self._node_id = value


class AbstractProducerNode(IProducerNode, __AbstractNode):

    _producer_carriage = None

    def __init__(self, node_id, producer_carriage=None, **kwargs):
        super(AbstractProducerNode, self).__init__(node_id=node_id, **kwargs)
        if producer_carriage is not None:
            self.register_producer_carriage(producer_carriage)

    def register_producer_carriage(self, producer_carriage):
        if not isinstance(producer_carriage, IProducerCarriage):
            raise ComponentCompatError(
                ERR_INCOMPATIBLE_COMPONENT.format(
                    component=producer_carriage,
                    expected_interface=IProducerCarriage
                )
            )
        if self.provides() != producer_carriage.expects():
            raise DataCompatError(

                ERR_INCOMPATIBLE_DATA_EXPECTED.format(