#!/usr/bin/python
"""
 iCalendar 2.0 Implementation as described in RFC 2445
 
 We use the former name 'vCalendar' here.
 WARNING: This iCalendar implementation is VERY incomplete
 and should not be used for any real-world calendar applications!
"""
#  Copyright (C) 2004  Henning Jacobs <henning@srcco.de>
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  $Id: vcalendar.py 82 2004-07-11 13:01:44Z henning $

import re, time
from types import *
import string
import debug
from vcore import *
import Set

FIELDNAMES = [
    "Summary",
    "Description",
    "Categories",
    "Comment",
    "Location",
    "Attendee",
    # special PyCoCuMa virtual field:
    "AttendeeIds",
    "Organizer",
    "Contact",
    "DateStart",
    "DateEnd",
    "LastModification",
    "Created",
    "TimeStamp",
    "URL",
    "UID"]
    
FIELDNAME2ATTR = {
    "Summary"         : "summary",
    "Description"     : "description",
    "Categories"      : "categories",
    "Comment"         : "comment",
    "Location"        : "location",
    "Attendee"        : "attendee",
    "AttendeeIds"     : "getAttendeeIds",
    "Organizer"       : "organizer",
    "Contact"         : "contact",
    "DateStart"       : "dtstart",
    "DateEnd"         : "dtend",
    "LastModification": "last_mod",
    "Created"         : "created",
    "TimeStamp"       : "dtstamp",
    "URL"             : "url",
    "UID"             : "uid" 
    }

SUPPORTED_VERSIONS = ["2.0"]

class vC_caladdress(vC_text):
    def __init__(self, value="", params=None):
        parts = value.split(':')
        if len(parts) > 1:
            value = ':'.join(parts[1:])
        vC_text.__init__(self, value, params)
        # PyCoCuMa's Contact UID is wrapped in the DIR URI Param:
        # E.g: http://pycocuma.localhost:8810/xmlrpc/vCard?UID=wxyz-1234-56
        uid = self.params.get('dir')
        if uid is None: 
            self._uid = ''
        else:
            parts = uid[0].split('=')
            self._uid = parts[-1]
    def getUID(self):
        return self._uid
    def setUID(self, uid):
        self._uid = uid
    def assignFromCard(self, card):
        "We set our Calendar Address from a vCard"
        fn = card.fn.get()
        if card.email:
            # Find the preferred Email-Address:
            prefmail = 0
            for i in range(len(card.email)):
                if card.email[i].is_pref(): prefmail = i
            self.set(card.email[prefmail].get())
        else:
            # Sorry, the card has no email:
            self.set("nobody@nowhere")
        # Set our Common Name to the card's FormattedName:
        self.params.set('cn', Set.Set(fn))
        # Set our UID:
        if not card.uid.is_empty():
            self.setUID(card.uid.get())
        else: self.setUID('')    
    def is_pref(self):
        # This Function is required by InputWidgets.MultiRecordEdit
        return False
    def VCF_repr(self):
        if self._uid:
            # This is a fake URI to wrap the vCard's UID:
            dir = "http://pycocuma.localhost:8810/xmlrpc/vCard?UID=%s" % self._uid
            self.params.set('dir', Set.Set(dir))
        return self.params.VCF_repr() + ":MAILTO:" + self.value.VCF_repr()

class vC_attendee(vC_caladdress):
    "Event Attendee"
    def __init__(self, value="", params=None):
        vC_caladdress.__init__(self, value, params)
    
class vC_organizer(vC_caladdress):
    "Event Organizer"
    def __init__(self, value="", params=None):
        vC_caladdress.__init__(self, value, params)

# Stores information about last call to getFieldValue():
# _lastreturnedfield = "%d_%s" % (vevent.handle(), fieldname)
_lastreturnedfield = ""
_lastreturnedvalueidx = 0
_uidcounter = 0

class vEvent:
    """vEvent Implementation as described in RFC 2445"""
    def _generateUID(self):
        global _uidcounter
        dtstr =  time.strftime("%Y%m%dT%H%M%SZ", time.gmtime())
        _uidcounter += 1
        return "%s-%04X@pycocuma.srcco.de" % (dtstr, _uidcounter)
    def _name2attr(self, name):
        return name.lower().replace("-", "_")
    def _attr2name(self, attr):
        return attr.upper().replace("_", "-")
    def __init__(self, block_of_lines=[]):
        self.__dict__["_handle"] = None
        # default values:
        self.__dict__["data"] = {
            "summary"   : vC_text(),
            "description": vC_text(),
            "categories": vC_categories(),
            "comment"   : vC_text(),
            "location"  : vC_text(),
            "attendee"  : [],
            "organizer" : vC_organizer(),
            "contact"   : vC_text(),
            "dtstart"   : vC_datetime(),
            "dtend"     : vC_datetime(),
            "last_mod"  : vC_datetime(time.gmtime()),
            "created"   : vC_datetime(time.gmtime()),
            "dtstamp"   : vC_datetime(time.gmtime()),
            "url"   : vC_text(),
            # RFC-2445 says that UID MUST BE present:
            "uid"   : vC_text(self._generateUID())}
        # Name to function mapping:     
        self.__dict__["name_func"] = {
            "SUMMARY"   : vC_text,
            "DESCRIPTION": vC_text,
            "CATEGORIES": vC_categories,
            "COMMENT"   : vC_text,
            "LOCATION"  : vC_text,
            "ATTENDEE"  : vC_attendee,
            "ORGANIZER" : vC_organizer,
            "CONTACT"   : vC_text,
            "DTSTART"   : vC_datetime,
            "DTEND"     : vC_datetime,
            "LAST-MOD"  : vC_datetime,
            "CREATED"   : vC_datetime,
            # Timestamp will only be set once on object creation:
            "DTSTAMP"   : vC_datetime,
            "URL"   : vC_text,
            "UID"   : vC_text}
        if type(block_of_lines) != ListType:
            # Input was a string?
            # Delete empty lines:
            block_of_lines = filter(isNonEmptyLine, block_of_lines.split('\n'))
            # de-fold:
            makeloglines(block_of_lines)
            block_of_lines = map(vC_contentline, block_of_lines)
        for line in block_of_lines:
            self._insertline(line)
        global _lastreturnedvalueidx
        _lastreturnedvalueidx = 0
    def _insertline(self, line):
        if self.name_func.has_key(line.name):
            attr = self._name2attr(line.name)
            if hasattr(self, attr) and type(self.__getattr__(attr)) == ListType:
                # Multi-Value:
                self.__getattr__(attr).append(\
                    self.name_func[line.name](line.value, line.params))
            else:
                # Single-Value:
                self.__setattr__(attr,\
                    self.name_func[line.name](line.value, line.params))
    def __getattr__(self, name):
        try:
            return self.data[name]
        except:
            raise AttributeError, "No Attribute '%s'" % (name)
    def __setattr__(self, name, value):
        try:
            self.data[name] = value
        except:
            raise AttributeError, "No Attribute '%s'" % (name)
    def __repr__(self):
        ret = "<%s: " % self.__class__
        for key, value in zip(self.data.keys(), self.data.values()):
            ret = ret + "%s: %s\n" % (repr(key), repr(value)) 
        return ret+">"
    def handle(self):
        return self.__dict__["_handle"]
    def sethandle(self, handle):
        "Handle can be set only once in a lifetime!"
        if self.__dict__["_handle"] == None:
            self.__dict__["_handle"] = handle
    def getAttendeeIds(self):
        "Return comma separated list of attendee UIDs"
        return ', '.join(map(vC_caladdress.getUID, self.attendee))
    def getFieldValue(self, field_and_idx):
        """Returns content of field as vC_value
           field_and_idx => e.g. 'Street 1'
           return => vC_value or None
           On multiple calls it returns further items from value array
           (Call this functions until it returns None)"""
        global _lastreturnedfield, _lastreturnedvalueidx
        def lastretfieldstr(field, self=self):
            handle = self.handle()
            if handle is None:
                return "None_%s" % (field)
            else:
                return "%d_%s" % (handle, field)
        parts = field_and_idx.split(" ")
        field = parts[0]
        try:
            idx = int(parts[1])-1
        except:
            if  lastretfieldstr(field) == _lastreturnedfield:
                idx = _lastreturnedvalueidx + 1
            else:
                idx = 0
        attr = FIELDNAME2ATTR[field]
        attrobj = getsubattr(self, attr)
        if type(attrobj) == ListType:
            if len(attrobj) > idx:
                value = attrobj[idx]
            else:
                value = None
        elif idx >0:
            value = None
        else:
            value = attrobj
        _lastreturnedvalueidx = idx
        _lastreturnedfield = lastretfieldstr(field)
        return value
    def getFieldValueStr(self, field_and_idx, default=""):
        """Returns content of field as string
           field_and_idx => e.g. 'Street 1'
           return => e.g. 'Fifth-Avenue 89'"""
        val = self.getFieldValue(field_and_idx)
        if val:
            return flattenattr(val)
        else:
            # the field does not exist
            # (NOTE: this does not mean empty fields!):
            return default
    def VCF_repr(self):
        ret = "BEGIN:VEVENT\n"
        Dict = self.data
        for name, val in Dict.items():
            if type(val) == ListType:
                for itm in val:
                    ret = ret + log2phylines(self._attr2name(name) + itm.VCF_repr() + "\n")
            elif val != None:
                if not val.is_empty():
                    ret = ret + log2phylines(self._attr2name(name) + val.VCF_repr() + "\n")
        return ret + "END:VEVENT\n"
        
class vCalendar:
    """List of vEvents"""
    def __init__(self):
        self.handlecounter = 0
        self.data = {}
    def sortedlist(self, sortby=""):
        "Return list of event handles"
        if not sortby:
            sortby = "DateStart"
        events = self.data.values()
        handles = self.data.keys()
        def getfieldvaluestr(event, field=sortby):
            return event.getFieldValueStr(field)
        decorated = zip(map(getfieldvaluestr, events), handles)
        decorated.sort()
        self.forgetLastReturnedField()
        return [ handle for sortstr, handle in decorated ]
    def LoadFromFile(self, fname):
        "Load from *.ics file"
        try:
            fd = open(fname, "rb")
            self.LoadFromStream(fd)
            fd.close()
        except: # Loading failed
            return False
        return True
    def LoadFromStream(self, stream, encoding='utf8'):
        "Load from any text stream with file-like methods"
        vEventBlock = None
        lines = stream.readlines()
        makeloglines(lines) 
        for line in filter(isNonEmptyLine, lines):
            if type(line) != UnicodeType:
                line = unicode(line, encoding, 'replace')
            line = vC_contentline(line)
            if line.name == "BEGIN": 
                if line.value.upper() == "VEVENT":
                    vEventBlock = []
            if vEventBlock is not None: 
                vEventBlock.append(line)
            if line.name == "END" \
            and line.value.upper() == "VEVENT":
                self.add(vEvent(vEventBlock))
    def SaveToFile(self, fname):
        "Save to *.ics file"
        fd = open(fname, "wb")
        self.SaveToStream(fd)
        fd.close()
    def SaveToStream(self, stream, encoding='utf8'):
        "Save to Stream (FileDescriptor)"
        stream.write(self.VCF_repr().encode(encoding, 'replace'))
    def __str__(self):
        return str(zip(self.data.keys(), map(str,self.data.values())))
    def __getitem__(self, key):
        return self.data[key]
    def __setitem__(self, key, value):
        self.data[key] = value
    def clear(self):
        "Removes all vEvents"
        self.data.clear()
    def add(self, event=None):
        "Add new vEvent to list"
        if event == None:
            event = vEvent()
        self.handlecounter += 1
        newhandle = self.handlecounter
        # Handle must not have been set before:
        event.sethandle(newhandle)
        self.data[newhandle] = event
        return newhandle
    def delete(self, handle):
        "Removes vEvent with handle from list"
        if self.data.has_key(handle):
            del self.data[handle]
            return True
        else:   return False
    def forgetLastReturnedField(self):
        "Reset the lastreturnedfield variable"
        global _lastreturnedfield, _lastreturnedvalueidx
        _lastreturnedfield = ""
        _lastreturnedvalueidx = 0
    def VCF_repr(self):
        "Representation for file output"
        from __version__ import __version__
        ret = "BEGIN:VCALENDAR\nVERSION:2.0\n"
        ret = ret + "PRODID:-//Henning Jacobs//NONSGML PyCoCuMa Calendar Version %s//EN\n\n" % __version__
        for event in self.data.values():
            ret = ret + event.VCF_repr() + "\n" # add blank line between events
        return ret+"END:VCALENDAR\n"

