# -*- coding: iso-8859-1 -*-
"""
    MoinMoin - Get an iCalendar (ics) feed from a MonthCalendar
    
    @copyright: 2010 Florian Birée <florian@biree.name>
    @license: GNU GPL, see COPYING for details.
    @version: 0.2
    
    Version 0.2:
        * add the support of MoinMoin 1.8, add the mime type in result headers
    Version 0.1:
        * first version, for MoinMoin 1.9
"""

import calendar
import datetime
import re

from MoinMoin.Page import Page

# Some constants :
#   Range of time (relative from today) where events are searched:
START_RANGE = datetime.timedelta(days=31)   # before today
END_RANGE = datetime.timedelta(days=6*31)   # after today
#   Default duration of an event:
DEFAULT_DURATION = datetime.timedelta(hours=2)

class VCalendar(list):
    """Far uncomplete implementation of vCalendar"""
    
    def __unicode__(self):
        """Return the calendar in the iCalendar format"""
        return (
            u"BEGIN:VCALENDAR\nVERSION:2.0\n" +
            u"PRODID:-//MoinMoin MonthCalendar2ics\n" +
            u"".join([unicode(event) for event in self]) + 
            u"END:VCALENDAR\n"
        )
    
    def __str__(self):
        """Return the calendar in the iCalendar format (utf-8 encoded)"""
        return unicode(self).encode('utf-8')

class VEvent(object):
    """Far uncomplete implementation of vEvent"""
    
    def __init__(self, summary, dtstart, dtend=None, description=None):
        """Create a new event
        
        summary is a short description of the event
        dtstart is a datetime.date for a full-day event, or a datetime.datetime
        dtend is a datetime.datetime
        description is the long description of the event
        """
        self.summary = summary
        self.dtstart = dtstart
        self.dtend = dtend
        self.description = description
    
    def __unicode__(self):
        """Return the event in the iCalendar format"""
        def idate(date):
            """Return the date or datetime in the iCalendar format"""
            return unicode(date.isoformat().replace(':', u'').replace('-', u''))
        def istr(raw_str):
            """Return the string in with iCalendar escaped new lines"""
            return unicode(raw_str.replace('\n', '\\n'))
        
        ev_str = u"BEGIN:VEVENT\n"
        ev_str += u"DTSTART:%s\n" % idate(self.dtstart)
        if self.dtend:
            ev_str += u"DTEND:%s\n" % idate(self.dtend)
        ev_str += u"SUMMARY:%s\n" % istr(self.summary)
        if self.description:
            ev_str += u"DESCRIPTION:%s\n" % istr(self.description)
        ev_str += u"END:VEVENT\n"
        return ev_str
    
    def __str__(self):
        """Return the event in the iCalendar format (utf-8 encoded)"""
        return unicode(self).encode('utf-8')

def extract_events_from_page(content, year, month, day):
    """Parse a MoinMoin page and extract events. Return a VCalendar object."""
    def tatoi(time_tupple):
        """Convert a string time tupple to an int tupple"""
        if time_tupple[0]:
            h = int(time_tupple[0])
        else:
            h = 0
        if time_tupple[1]:
            m = int(time_tupple[1])
        else:
            m = 0
        return (h, m)
    
    level1headers = re.compile(r'^\s*=\s(.*)\s=$', re.MULTILINE)
    times = re.compile(r'(\d{1,2})\s*[hH:](?:\s*(\d{2}))?')
    
    splitted_page = level1headers.split(content)
    cal = VCalendar()
    for i in range(len(splitted_page) / 2):
        summary = splitted_page[i*2+1]
        description = splitted_page[(i+1) * 2].strip()
        found_times = times.findall(summary)
        if len(found_times) == 0: # no start time, full day event
            start_date = datetime.date(year, month, day)
            end_date = None
        elif len(found_times) == 1: # one start time, 1h event
            h, m = tatoi(found_times[0])
            start_date = datetime.datetime(year, month, day, h, m)
            duration = DEFAULT_DURATION
            end_date = start_date + duration
        else: # at least two times (start and end of the event)
            h, m = tatoi(found_times[0])
            start_date = datetime.datetime(year, month, day, h, m)
            h, m = tatoi(found_times[1])
            end_date = datetime.datetime(year, month, day, h, m)
            if not start_date < end_date:
                duration = DEFAULT_DURATION
                end_date = start_date + duration
        cal.append(VEvent(summary, start_date, end_date, description))
    return cal

def execute(pagename, request):
    """Execute the generation of the ics feed.
    
    Pagename must be the page where the month calendar is.
    """
    start_date = datetime.date.today() - START_RANGE
    end_date = datetime.date.today() + END_RANGE
    year_month_list = []
    current_date = start_date.replace(day=1)
    while current_date < end_date:
        year_month_list.append((current_date.year, current_date.month))
        firstday, monthlen = calendar.monthrange(current_date.year,
                                                 current_date.month)
        current_date += datetime.timedelta(days=monthlen)
    
    # Search for events and build the calendar
    cal = VCalendar()
    for year, month in year_month_list:
        firstday, monthlen = calendar.monthrange(year, month)
        for day in range(1, monthlen + 1):
            link = "%s/%4d-%02d-%02d" % (pagename, year, month, day)
            daypage = Page(request, link)
            if daypage.exists() and request.user.may.read(link):
                #print "found :", link
                daycontent = daypage.get_raw_body()
                cal += extract_events_from_page(daycontent, year,month, day)
    # Send the iCalendar file
    request.content_type = 'text/calendar'
    if hasattr(request, 'headers') and hasattr(request.headers, 'add'):#moin 1.9
        request.headers.add('Content-Type: text/calendar')
        request.headers.add('Content-Disposition', 'inline; filename="%s.ics"' %
        pagename)
    else: # moin 1.8
        request.emit_http_headers([                                                  
            'Content-Type: text/calendar',
            'Content-Disposition: inline; filename="%s.ics"' % pagename,
        ])
    
    request.write(str(cal))
    #request.close()

                    
                    
                    
                    
