from zope import interface
from zope import schema
import zope.component.interfaces
from zope.interface.interfaces import IInterface
from zope.interface.common.mapping import IMapping
from zope.annotation.interfaces import IAnnotatable
import z3c.form.interfaces

from collective.singing import MessageFactory as _


class ICollector(interface.Interface):
    """Collectors are useful for automatic newsletters.  They are
    responsible for assembling a list of items for publishing.
    """

    title = schema.TextLine(
        title=_(u"Title"),
        )

    optional = schema.Bool(
        title=_(u"Subscriber optional"),
        )

    significant = schema.Bool(
        title=_(u"Significant"),
        description=_(u"Include items from this collector even if there are "
                      u"no items returned by significant siblings.")
        )

    schema = schema.Object(
        title=_(u"A schema instance for use in the subscription form"),
        description=_(u"Values are stored via the ICollectorData adapter per "
        "subscriber."),
        required=False,
        schema=IInterface,
        )

    def get_items(cue=None, subscription=None):
        """Return a tuple '(items, cue)' where 'items' is the items
        that'll be fed into the composer, and 'cue' is an object that
        can be passed to this method again.

        If 'cue' is passed, I will only return items that are newer
        than the items that were returned when the cue was retrieved.

        If 'subscription' is given, I will filter according to the
        subscriber's ICollectorData.
        """


class IComposer(interface.Interface):
    """Composers will typically provide a user interface that lets you
    modify the look of the message rendered through it.
    """

    name = schema.TextLine(
        title=_(u"The Composer's format, e.g. 'html'"),
        )

    title = schema.TextLine(
        title=_(u"The Composer's title, e.g. 'HTML E-Mail'"),
        )

    schema = schema.Object(
        title=_(u"A schema instance for use in the subscription form"),
        description=_(u"Values are stored via the IComposerData adapter per "
        "subscriber."),
        schema=IInterface,
        )

    def render(subscription, items=(), override_vars=None):
        """Given a subscription and a list of items, I will create an
        IMessage and return it.

        The ``items`` argument is a list of 2-tuples of the form
        ``(formatted, original)``, where ``original`` is the item as
        it was retrieved from the collector, and ``formatted`` is the
        result of running the item through all applicable formatters
        and transforms.  Making use of the ``original`` item will
        obviously bind the implementation of the composer to that of
        the collector.  However, it's considered useful for custom
        implementations that need total control and that know what
        collector they'll be using.
        """

    def render_confirmation(subscription):
        """Given a subscription, I will create an IMessage that the
        user has to react to in order to confirm the subscription, and
        return it.
        """

    def render_forgot_secret(subscription):
        """Given a subscription, I will create an IMessage that links
        the user to his subscription page.
        """


class IScheduler(interface.Interface):
    """A scheduler triggers the sending of messages periodically.
    """
    title = schema.TextLine(
        title=_(u"Title"),
        )

    triggered_last = schema.Datetime(
        title=_(u"Triggered the last time"),
        )

    active = schema.Bool(
        title=_(u"Active"),
        )

    def tick(channel, request):
        """Check if messages need to be assembled and sent; return the
        number of messages queued or None.

        This method is guaranteed to be called periodically.
        """

    def trigger(channel, request):
        """Assemble and queue messages; return the number of messages
        queued.

        A manual override.
        """


class ISubscriptions(interface.Interface):
    """The `subscriptions` attribute of IChannel implements this.

    Allows for adding, removing, and querying subscriptions.
    """
    def values():
        """Iterate over all subscription objects.
        """

    def add_subscription(
        channel, secret, composer_data, collector_data, metadata):
        """Add a subscription and return it.

        Raises ValueError if subscription already exists.
        """

    def remove_subscription(subscription):
        """Remove subscription.
        """

    def query(**kwargs):
        """Search subscriptions.

        Available fields are: 'fulltext', 'secret', 'format', 'key', 'label'.
        """


class ISubscription(IAnnotatable):
    """A subscription to a channel.
    """

    channel = schema.Object(
        title=_(u"The channel that we're subscribed to"),
        schema=IInterface,  # should be really IChannel
        )

    secret = schema.ASCIILine(
        title=_(u"The subscriber's secret, ideally unique across channels"),
        description=u"""\
        Might be a hash of the subscriber's e-mail and a secret on the
        server in case we're dealing with an anonymous subscription.
        Used for unsubscription or for providing an overview of all of
        a subscriber's subscriptions.
        """,
        )

    composer_data = schema.Dict(title=_(u"Composer data"))
    collector_data = schema.Dict(title=_(u"Collector data"))
    metadata = schema.Dict(title=_(u"Metadata"))


class ISubscriptionLabel(interface.Interface):
    """Marker interface for field on IComposerSchema that labels a
    subscription.
    """


class ISubscriptionKey(interface.Interface):
    """Marker interface for field on IComposerSchema that serves as a
    key for conflicts.

    For subscriptions per e-mail this is the e-mail field.  No
    subscriptions for the same format and same e-mail address will be
    allowed, then.
    """


class IMessageQueues(IMapping):
    """A dict that contains one ``zc.queue.interfaces.IQueue`` per
    message status.
    """
    messages_sent = schema.Int(
        title=_(u"Total number of messages sent through this queue"),
        )


class IChannel(interface.Interface):
    """A Channel is what we can subscribe to.

    A Channel is a hub of configuration.  It doesn't do anything by
    itself.  Rather, it provides a number of components to configure
    and work with.  It is also the container of subscriptions to it.
    """

    name = schema.ASCIILine(
        title=_(u"Unique identifier for this channel across the site."),
        )

    title = schema.TextLine(
        title=_(u"Title"),
        )

    description = schema.Text(
        title=_(u"Description")
        )

    subscribeable = schema.Bool(
        title=_(u"Subscribeable"),
        default=False
        )

    scheduler = schema.Object(
        title=_(u"Scheduler (when)"),
        required=False,
        schema=IScheduler,
        )

    collector = schema.Object(
        title=_(u"Collector (what)"),
        required=False,
        schema=ICollector,
        )

    composers = schema.Dict(
        title=_(u"The channel's composers, keyed by format."),
        key_type=schema.TextLine(title=_(u"Format")),
        value_type=schema.Object(title=_(u"Composer"), schema=IComposer),
        )

    subscriptions = schema.Object(
        title=_(u"The channel's subscriptions"),
        schema=ISubscriptions,
        )

    queue = schema.Object(
        title=_(u"This channel's message queues, keyed by message status"),
        schema=IMessageQueues,
        )

    keep_sent_messages = schema.Bool(
        title=_(u"Keep a record of sent messages."),
        description=_(u"This is not currently recommended for large volumes "
                      u"of messages due to storage requirements."),
        default=False,
        )


class IMessageAssemble(interface.Interface):
    """An adapter on the channel that's usually invoked by a scheduler
    to render and queue new messages.
    """
    def __call__(request, items=(), use_collector=True, override_vars=None):
        """Process all subscribers.

          o ``items`` is the items added manually

          o ``use_collector`` is a boolean indicating whether the
            channel's collector should be considered at all

        Returns the number of queued messages."""

    def render_message(request, subscription, items=(), use_collector=True,
                       override_vars=None):
        """Render and queue messages for an individual subscription.

          o ``subscription`` is an ISubscription

        For other arguments, please see the docstring of ``__call__``.

        Returns the IMessage if a message was queued or None to
        indicate that no message was queued.
        """


class IChannelLookup(interface.Interface):
    """A utility that looks up all channels in a site.
    """

    def __call__():
        """Return a list of ``IChannel`` objects.
        """


class ISalt(interface.Interface):
    """A utility that's a salt for use in creating secrets.
    """


class IRequestBasedSecret(interface.Interface):
    """A utility that provides a secret based on the request.
    """
    def __call__(request):
        """Return an ASCII secret.
        """


class IComposerBasedSecret(interface.Interface):
    """An adapter for composer that provides a secret based on the
    user's data used for the composer (see also 'IComposerData').
    """

    def secret(data):
        """Return an ASCII secret.

        'data' is a dict that corresponds to the IComposer's schema.
        """


MESSAGE_STATES = [u'new',    # just added
                  u'sent',   # sent successfully
                  u'error',  # error while sending
                  u'retry',  # error while sending, but retrying
                  ]


class IMessage(interface.Interface):
    """Messages are objects ready for sending.
    """
    payload = schema.Field(
        title=_(u"The message's payload, e.g. the e-mail message."),
        )

    subscription = schema.Object(
        title=_(u"Subscription, referenced for bookkeeping purposes only."),
        schema=ISubscription,
        )

    status = schema.Choice(
        title=_(u"State"),
        description=_(u"IMessageChanged is fired automatically when this "
                      u"is set"),
        values=MESSAGE_STATES)

    status_message = schema.Text(
        title=_(u"Status details"),
        required=False)

    status_changed = schema.Datetime(
        title=_(u"Last time this message changed its status"),
        )


class IMessageChanged(zope.lifecycleevent.interfaces.IObjectModifiedEvent):
    """An object event on the message that signals that the status has
    changed.
    """
    old_status = schema.TextLine(title=_(u"Old status of message"))


class IDispatch(interface.Interface):
    """Dispatchers adapt message *payloads* and send them."""

    def __call__():
        """Attempt to send message.

        Must return a tuple ``(status, status_message)``.  See
        ``MESSAGE_STATES`` for possible choices for the status.  The
        status message may be None or a text containing details about
        the status, e.g. why it failed.

        If this method raises an exception, an 'error' is assumed.
        """


class IFormatItem(interface.Interface):
    """A view that formats an item for use in a newsletter.

    Given an item as retrieved from a collector, this view returns a
    representation of the given item ready for inclusion in the
    message via the ``IComposer.render`` method.
    """

    def __call__():
        """Returns a unicode-string."""


class ITransform(interface.Interface):
    """An adapter that allows to rewrite links and the like in
    outgoing messages.
    """
    def __call__(text, subscription):
        """Return transformed text
        """


class IFormLayer(z3c.form.interfaces.IFormLayer):
    pass


class IDynamicVocabularyCollection(zope.schema.interfaces.ICollection):
    """Used to mark ICollection Fields in our dynamic schemas.  To
    indicate that thay have dynamic vocabularies.  We need a more
    tolerant z3c.form.interfaces.IDataConverter in this case - one
    that drops saved values that are no longer in the vocabulary when
    updating widgets.

    (XXX: Mabye this should go into the browser package?)
    """


class ICollectorSchema(interface.Interface):
    """An adapter from subscription to the collector data.
    """
