import os
import time
from datetime import datetime, tzinfo, timedelta
from trac.mimeview import Context

from sets import Set

from trac.core import *
from trac.core import ComponentMeta
from trac.config import IntOption
from trac.web.chrome import add_stylesheet
from trac.web.href import Href
from trac.util.datefmt import * 
from trac.util.html import Markup
from trac.util.text import to_unicode
from tram.util import debug
from trac.resource import *
from trac.wiki.web_ui import WikiModule
from trac.ticket.web_ui import TicketModule
from trac.versioncontrol.web_ui.changeset import ChangesetModule
import traceback
from genshi.core import Markup
from genshi.builder import tag
from trac.util.translation import _

try:
    from trac.timeline.api import ITimelineEventProvider
except ImportError:
    from trac.Timeline import ITimelineEventProvider

class TimelineModule(Component):

    event_providers = ComponentMeta._registry.get(ITimelineEventProvider, [])

    default_daysback = IntOption('timeline', 'default_daysback', 30,
        """Default number of days displayed in the Timeline, in days.
        (''since 0.9.'')""")

    def process_request(self, req, projects):
        maxrows = int(req.args.get('max', 0))

        t = time.localtime()
        fromdate = datetime(t[0], t[1], t[2], 23, 59, 59, t[6], tzinfo=utc)

        daysback = self.default_daysback

        todate = datetime(t[0], t[1], t[2], 23, 59, 59, t[6], tzinfo=utc) - timedelta(days=daysback) 

        req.hdf['timeline.from'] = format_date(fromdate)
        req.hdf['timeline.daysback'] = daysback

        all_filters = []
        for project in projects:
            for event_provider in self.event_providers:
                all_filters += event_provider(project['env']).get_timeline_filters(req)
        available_filters = Set(all_filters)

        filters = []
        # check the request or session for enabled filters, or use default
        for test in (lambda f: req.args.has_key(f[0]),
                     lambda f: req.session.get('timeline.filter.%s' % f[0], '')\
                               == '1',
                     lambda f: len(f) == 2 or f[2]):
            if filters:
                break
            filters = [f[0] for f in available_filters if test(f)]

        # save the results of submitting the timeline form to the session
        if req.args.has_key('update'):
            for filter in available_filters:
                key = 'timeline.filter.%s' % filter[0]
                if req.args.has_key(filter[0]):
                    req.session[key] = '1'
                elif req.session.has_key(key):
                    del req.session[key]

        stop = fromdate
        start = todate

        events = []
        old_href = req.href

        for project in projects:
            req.href = Href(project['href'])

            project_events = []
            for event_provider in self.event_providers:
                try:
                    for event in event_provider(project['env']).get_timeline_events(req, start, stop, filters):
                        event_obj = self._event_data(event_provider, event)
                        project_events.append(event_obj)  
                except Exception, e: # cope with a failure of that provider
                    debug(to_unicode(e))
                    pass

            for ev in project_events:
                title = ''
                url = '' 
                message = '' 
                try:
                    if ev['kind'] == "wiki":
                        try:
                            url = WikiModule(self.compmgr).render_timeline_event(req, 'url', ev['event'])
                            title = WikiModule(self.compmgr).render_timeline_event(req, 'title', ev['event'])
                            version = ev['data'][0].version
                            if version > 1:
                                pre_version = version -1
                                diff_href = url + '&action=diff'
                                message = tag('', ' ', tag.a(_('(diff)'), href=diff_href))
                        except Exception, e:
                            debug(traceback.format_exc())

                    elif ev['kind'] == "newticket" or ev['kind'] == "reopenedticket" or ev['kind'] == "closedticket" or ev['kind'] == "editedticket":
                        try:
                            url = TicketModule(self.compmgr).render_timeline_event(req, 'url', ev['event'])
                            title = TicketModule(self.compmgr).render_timeline_event(req, 'title', ev['event'])
                            message = ev['data'][3]
                        except Exception, e:
                            debug(traceback.format_exc())
                    elif ev['kind'] == "changeset":
                        try:
                            url = ChangesetModule(self.compmgr).render_timeline_event(req, 'url', ev['event'])
                            title = ChangesetModule(self.compmgr).render_timeline_event(req, 'title', ev['event'])
                            message = ev['data'][1] 
                        except Exception, e:
                            debug(traceback.format_exc())
                    if title != "":
                        title = title + ' (in ' + project['name'] + ') by ' + ev['author']
                        events.append( (ev['kind'], url, title, ev['date'], ev['author'], message))
                except Exception, e:
                    pass

        req.href = old_href
        
        html_context = Context.from_request(req)
        req.hdf['context'] = html_context

        events.sort(lambda x,y: cmp(y[3], x[3]))
        if maxrows and len(events) > maxrows:
            del events[maxrows:]

        req.hdf['title'] = _('Timeline')

        # Get the email addresses of all known users
        email_map = {}
        for username, name, email in self.env.get_known_users():
            if email:
                email_map[username] = email

        idx = 0
        for ev in events:
            event = {'kind': ev[0], 'title': ev[2], 'href': ev[1],
                     'author': ev[4] or 'anonymous',
                     'date': format_date(ev[3]),
                     'time': format_time(ev[3], '%H:%M'),
                     'message': ev[5]}
        
            req.hdf['timeline.events.%s' % idx] = event
            idx += 1

        for idx,fltr in enumerate(available_filters):
            req.hdf['timeline.filters.%d' % idx] = {'name': fltr[0],
                'label': fltr[1], 'enabled': int(fltr[0] in filters)}

        req.hdf["chrome.nav.mainnav.timeline.active"] = 1
        
        template_dir = os.path.dirname(__file__) + '/templates/'
        req.display(template_dir + "timeline.cs", 'text/html')

    def _event_data(self, provider, event):
        """Compose the timeline event date from the event tuple and prepared
        provider methods"""
        if len(event) == 6: # 0.10 events
            kind, url, title, date, author, markup = event
            data = {'url': url, 'title': title, 'description': markup}
            render = lambda field, context: data.get(field)
        else: # 0.11 events
            if len(event) == 5: # with special provider
                kind, date, author, data, provider = event
            else:
                kind, date, author, data = event
            render = lambda field, context: \
                    provider.render_timeline_event(context, field, event )
        if isinstance(date, datetime):
            dateuid = to_timestamp(date)
        else:
            dateuid = date
            date = datetime.fromtimestamp(date, utc)
        return {'kind': kind, 'author': author, 'date': date,
                'dateuid': dateuid, 'render': render, 'event': event,
                'data': data, 'provider': provider}


    def _provider_failure(self, exc, req, ep, current_filters, all_filters):
        """Raise a TracError exception explaining the failure of a provider.

        At the same time, the message will contain a link to the timeline
        without the filters corresponding to the guilty event provider `ep`.
        """
        ep_name, exc_name = [i.__class__.__name__ for i in (ep, exc)]
        guilty_filters = [f[0] for f in ep.get_timeline_filters(req)]
        guilty_kinds = [f[1] for f in ep.get_timeline_filters(req)]
        other_filters = [f for f in current_filters if not f in guilty_filters]
        if not other_filters:
            other_filters = [f for f in all_filters if not f in guilty_filters]
        args = [(a, req.args.get(a)) for a in ('from', 'format', 'max',
                                               'daysback')]
        href = req.href.timeline(args+[(f, 'on') for f in other_filters])
        raise TracError(Markup(
            '%s  event provider (<tt>%s</tt>) failed:<br /><br />'
            '%s: %s'
            '<p>You may want to see the other kind of events from the '
            '<a href="%s">Timeline</a></p>',
            ", ".join(guilty_kinds), ep_name, exc_name, to_unicode(exc), href))
