from Products.CMFPlone.PloneBatch import Batch
from Products.Five.browser import BrowserView
from calendar import monthrange
from datetime import date
from datetime import timedelta
from plone.app.event import messageFactory as _
from plone.app.event.base import RET_MODE_ACCESSORS
from plone.app.event.base import RET_MODE_OBJECTS
from plone.app.event.base import _prepare_range
from plone.app.event.base import date_speller
from plone.app.event.base import expand_events
from plone.app.event.base import get_events
from plone.app.event.base import guess_date_from
from plone.app.event.base import localized_now
from plone.app.event.base import start_end_from_mode
from plone.app.event.base import start_end_query
from plone.app.event.ical.exporter import construct_icalendar
from plone.app.layout.navigation.defaultpage import getDefaultPage
from plone.app.querystring import queryparser
from plone.memoize import view
from plone.uuid.interfaces import IUUID
from zope.component import getMultiAdapter
from zope.contentprovider.interfaces import IContentProvider

try:
    from plone.app.contenttypes.behaviors.collection import ISyndicatableCollection as ICollection  # noqa
except ImportError:
    ICollection = None


class EventListing(BrowserView):

    def __init__(self, context, request):
        super(EventListing, self).__init__(context, request)

        self.now = now = localized_now(context)

        # Try to get the default page
        default = getDefaultPage(context)
        self.default_context = context[default] if default else context

        self.is_collection = False
        if ICollection:
            self.is_collection = ICollection.providedBy(self.default_context)

        # Request parameter
        req = self.request.form

        b_size  = int(req.get('b_size', 0))
        if not b_size and self.is_collection:
            collection_behavior = ICollection(self.default_context)
            b_size = getattr(collection_behavior, 'item_count', 0)
        self.b_size = b_size or 10
        self.b_start = int(req.get('b_start', 0))
        self.orphan  = int(req.get('orphan', 1))
        self.mode    = req.get('mode', None)
        self._date   = req.get('date', None)
        self.tags    = req.get('tags', None)
        self.searchable_text = req.get('SearchableText', None)
        self.path    = req.get('path', None)

        day   = int(req.get('day', 0)) or None
        month = int(req.get('month', 0)) or None
        year  = int(req.get('year', 0)) or None

        if not self._date and day or month or year:
            self._date = date(year or now.year,
                              month or now.month,
                              day or now.day).isoformat()

        if self.mode is None:
            self.mode = 'day' if self._date else 'future'

        self.uid = None  # Used to get all occurrences from a single event. Overrides all other settings  # noqa

    @property
    def show_filter(self):
        ret = True
        if self.is_collection:
            ctx = self.default_context
            query = queryparser.parseFormquery(ctx, ctx.query)
            if 'start' in query or 'end' in query:
                # Don't show the date filter, if a date is given in the
                # collection's query
                ret = False
        return ret

    @property
    def date(self):
        dt = None
        if self._date:
            try:
                dt = guess_date_from(self._date)
            except TypeError:
                pass
        return dt

    @property
    def _start_end(self):
        start, end = start_end_from_mode(self.mode, self.date, self.context)
        return start, end

    def _get_events(self, ret_mode=RET_MODE_ACCESSORS, expand=True):
        context = self.context
        kw = {}
        if self.uid:
            # In this case, restrict search for single event
            kw['UID'] = self.uid
        else:
            if self.path:
                kw['path'] = self.path
            else:
                # Search current and subsequent folders
                kw['path'] = '/'.join(context.getPhysicalPath())

            if self.tags:
                kw['Subject'] = {'query': self.tags, 'operator': 'and'}

            if self.searchable_text:
                kw['SearchableText'] = self.searchable_text

        # kw['b_start'] = self.b_start
        # kw['b_size']  = self.b_size

        start, end = self._start_end

        sort = 'start'
        sort_reverse = False
        if self.mode in ('past', 'all'):
            sort_reverse = True
        return get_events(context, start=start, end=end,
                          sort=sort, sort_reverse=sort_reverse,
                          ret_mode=ret_mode, expand=expand, **kw)

    @view.memoize
    def events(self, ret_mode=RET_MODE_ACCESSORS, expand=True, batch=True):
        res = []
        if self.is_collection:
            ctx = self.default_context
            # Whatever sorting is defined, we're overriding it.
            sort_on = 'start'
            sort_order = None
            if self.mode in ('past', 'all'):
                sort_order = 'reverse'
            query = queryparser.parseFormquery(
                ctx, ctx.query, sort_on=sort_on, sort_order=sort_order
            )
            custom_query = self.request.get('contentFilter', {})
            if 'start' not in query or 'end' not in query:
                # ... else don't show the navigation bar
                start, end = self._start_end
                start, end = _prepare_range(ctx, start, end)
                custom_query.update(start_end_query(start, end))
            res = ctx.results(
                batch=False, brains=True, custom_query=custom_query
            )
            if expand:
                # get start and end values from the query to ensure limited
                # listing for occurrences
                start, end = self._expand_events_start_end(
                    query.get('start') or custom_query.get('start'),
                    query.get('end') or custom_query.get('end')
                )
                res = expand_events(
                    res, ret_mode,
                    start=start, end=end,
                    sort=sort_on, sort_reverse=True if sort_order else False
                )
        else:
            res = self._get_events(ret_mode, expand=expand)
        if batch:
            b_start = self.b_start
            b_size = self.b_size
            res = Batch(res, size=b_size, start=b_start, orphan=self.orphan)
        return res

    @property
    def ical(self):
        # Get as objects.
        # Don't include occurrences to avoid having them along with their
        # original events and it's recurrence definition in icalendar exports.
        events = self.events(ret_mode=RET_MODE_OBJECTS, expand=False,
                             batch=False)
        cal = construct_icalendar(self.context, events)
        name = '%s.ics' % self.context.getId()
        self.request.RESPONSE.setHeader('Content-Type', 'text/calendar')
        self.request.RESPONSE.setHeader(
            'Content-Disposition',
            'attachment; filename="%s"' % name
        )
        self.request.RESPONSE.write(cal.to_ical())

    @property
    def ical_url(self):
        date = self.date
        mode = self.mode

        qstr = '&'.join([
            it for it in ['mode=%s' % mode if mode else None,
                          'date=%s' % date if date else None]
            if it
        ])
        qstr = '?%s' % qstr if qstr else ''
        return '%s/@@event_listing_ical%s' % (
            self.context.absolute_url(),
            qstr
        )

    # COLLECTION daterange start/end determination
    def _expand_events_start_end(self, start, end):
        # make sane start and end values for expand_events from
        # Collection start/end criterions.
        # if end/min is given, it overrides start/min settings to make sure,
        # ongoing events are shown in the listing!
        # XXX: This actually fits most needs, but not all. Maybe someone
        # wants to come up with some edgecases!
        se = dict(start=None, end=None)
        if start:
            q = start.get('query')
            r = start.get('range')
            if r == "min":
                se["start"] = q
            elif r == "max":
                se["end"] = q
            elif r in ("minmax", "min:max"):
                list(q).sort()
                se["start"] = q[0]
                se["end"] = q[1]
        if end:
            q = end.get('query')
            r = end.get('range')
            if r == "min":
                se["start"] = q
        return se["start"], se["end"]

    def formatted_date(self, occ):
        provider = getMultiAdapter(
            (self.context, self.request, self),
            IContentProvider, name='formatted_date'
        )
        return provider(occ)

    def date_speller(self, date):
        return date_speller(self.context, date)

    @property
    def header_string(self):
        start, end = self._start_end
        start_dict = date_speller(self.context, start) if start else None
        end_dict = date_speller(self.context, end) if end else None

        mode = self.mode
        main_msgid = None
        sub_msgid = None
        if mode == 'all':
            main_msgid = _(u"all_events", default=u"All events")

        elif mode == 'past':
            main_msgid = _(u"past_events", default=u"Past events")

        elif mode == 'future':
            main_msgid = _(u"future_events", default=u"Future events")

        elif mode == 'now':
            main_msgid = _(u"todays_upcoming_events",
                           default=u"Todays upcoming events")

        elif mode == 'today':
            main_msgid = _(u"todays_events", default=u"Todays events")

        elif mode == '7days':
            main_msgid = _(u"7days_events", default=u"Events in next 7 days.")
            sub_msgid = _(
                u"events_from_until",
                default=u"${from} until ${until}.",
                mapping={
                    'from': "%s, %s. %s %s" % (
                        start_dict['wkday_name'],
                        start.day,
                        start_dict['month_name'],
                        start.year
                    ),
                    'until': "%s, %s. %s %s" % (
                        end_dict['wkday_name'],
                        end.day,
                        end_dict['month_name'],
                        end.year
                    ),
                }
            )

        elif mode == 'day':
            main_msgid = _(
                u"events_on_day",
                default=u"Events on ${day}",
                mapping={
                    'day': "%s, %s. %s %s" % (
                        start_dict['wkday_name'],
                        start.day,
                        start_dict['month_name'],
                        start.year
                    ),
                }
            )

        elif mode == 'week':
            main_msgid = _(u"events_in_week",
                           default=u"Events in week ${weeknumber}",
                           mapping={'weeknumber': start.isocalendar()[1]})
            sub_msgid = _(
                u"events_from_until",
                default=u"${from} until ${until}.",
                mapping={
                    'from': "%s, %s. %s %s" % (
                        start_dict['wkday_name'],
                        start.day,
                        start_dict['month_name'],
                        start.year
                    ),
                    'until': "%s, %s. %s %s" % (
                        end_dict['wkday_name'],
                        end.day,
                        end_dict['month_name'],
                        end.year
                    ),
                }
            )

        elif mode == 'month':
            main_msgid = _(
                u"events_in_month",
                default=u"Events in ${month} ${year}",
                mapping={
                    'month': start_dict['month_name'],
                    'year': start.year,
                }
            )

        trans = self.context.translate
        return {'main': trans(main_msgid) if main_msgid else '',
                'sub': trans(sub_msgid) if sub_msgid else ''}

    # MODE URLs
    def _date_nav_url(self, mode, datestr=''):
        return '%s?mode=%s%s' % (
            self.request.getURL(),
            mode,
            '&date=%s' % datestr if datestr else ''
        )

    @property
    def mode_all_url(self):
        return self._date_nav_url('all')

    @property
    def mode_future_url(self):
        return self._date_nav_url('future')

    @property
    def mode_past_url(self):
        return self._date_nav_url('past')

    @property
    def mode_day_url(self):
        now = self.date or self.now
        return self._date_nav_url('day', now.date().isoformat())

    @property
    def mode_week_url(self):
        now = self.date or self.now
        return self._date_nav_url('week', now.date().isoformat())

    @property
    def mode_month_url(self):
        now = self.date or self.now
        return self._date_nav_url('month', now.date().isoformat())

    # DAY NAV
    @property
    def next_day_url(self):
        now = self.date or self.now
        datestr = (now + timedelta(days=1)).date().isoformat()
        return self._date_nav_url('day', datestr)

    @property
    def today_url(self):
        return self._date_nav_url('day')

    @property
    def prev_day_url(self):
        now = self.date or self.now
        datestr = (now - timedelta(days=1)).date().isoformat()
        return self._date_nav_url('day', datestr)

    # WEEK NAV
    @property
    def next_week_url(self):
        now = self.date or self.now
        datestr = (now + timedelta(days=7)).date().isoformat()
        return self._date_nav_url('week', datestr)

    @property
    def this_week_url(self):
        return self._date_nav_url('week')

    @property
    def prev_week_url(self):
        now = self.date or self.now
        datestr = (now - timedelta(days=7)).date().isoformat()
        return self._date_nav_url('week', datestr)

    # MONTH NAV
    @property
    def next_month_url(self):
        now = self.date or self.now
        last_day = monthrange(now.year, now.month)[1]  # (wkday, days)
        datestr = (now.replace(day=last_day) +
                   timedelta(days=1)).date().isoformat()
        return self._date_nav_url('month', datestr)

    @property
    def this_month_url(self):
        return self._date_nav_url('month')

    @property
    def prev_month_url(self):
        now = self.date or self.now
        datestr = (now.replace(day=1) - timedelta(days=1)).date().isoformat()
        return self._date_nav_url('month', datestr)


class EventListingIcal(EventListing):
    def __call__(self, *args, **kwargs):
        return self.ical


class EventEventListing(EventListing):
    """This is an EventListing for an individual event, to list all
    occurrences batched and navigatable with all the features, the EventListing
    offers.
    """
    def __init__(self, context, request):
        super(EventEventListing, self).__init__(context, request)
        self.uid = IUUID(self.context)
