#!/opt/local/bin/python2.7
#
# Copyright (C) 2007 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
#
# This sample uses the Google Spreadsheets data API and the Google 
# Calendar data API. The script pulls a list of birthdays from a 
# Google Spreadsheet and inserts them as webContent events in the
# user's Google Calendar. 
# 
# The script expects a certain format in the spreadsheet: Name,
# Birthday, Photo URL, and Edit URL as headers. Expected format 
# of the birthday is: MM/DD. Edit URL is to be left blank by the
# user - the script uses this column to determine whether to insert
# a new event or to update an event at the URL. 
# 
# See the spreadsheet below for an example:
# http://spreadsheets.google.com/pub?key=pfMX-JDVnx47J0DxqssIQHg
#


__author__ = 'api.stephaniel@google.com (Stephanie Liu)'

try:
  from xml.etree import ElementTree # for Python 2.5 users
except:  
  from elementtree import ElementTree

import gdata.spreadsheet.service
import gdata.calendar.service
import gdata.calendar
import gdata.service
import atom.service
import gdata.spreadsheet
import atom
import string
import time
import datetime
import getopt
import getpass
import sys


class BirthdaySample:
  # CONSTANTS: Expected column headers: name, birthday, photourl, editurl &
  # default calendar reminder set to 2 days
  NAME = "name"
  BIRTHDAY = "birthday"
  PHOTO_URL = "photourl"
  EDIT_URL = "editurl"
  REMINDER = 60 * 24 * 2 # minutes

  def __init__(self, email, password):
    """ Initializes spreadsheet and calendar clients.
        
        Creates SpreadsheetsService and CalendarService objects and 
        authenticates to each with ClientLogin. For more information
        about ClientLogin authentication: 
        http://code.google.com/apis/accounts/AuthForInstalledApps.html

        Args:
          email: string
          password: string
    """

    self.s_client = gdata.spreadsheet.service.SpreadsheetsService()
    self.s_client.email = email
    self.s_client.password = password
    self.s_client.source = 'exampleCo-birthdaySample-1'
    self.s_client.ProgrammaticLogin()

    self.c_client = gdata.calendar.service.CalendarService()
    self.c_client.email = email
    self.c_client.password = password
    self.c_client.source = 'exampleCo-birthdaySample-1'
    self.c_client.ProgrammaticLogin()
  
  def _PrintFeed(self, feed):
    """ Prints out Spreadsheet feeds in human readable format.
      
        Generic function taken from spreadsheetsExample.py.          

        Args:
          feed: SpreadsheetsCellsFeed, SpreadsheetsListFeed,
            SpreadsheetsWorksheetsFeed, or SpreadsheetsSpreadsheetsFeed
    """
    for i, entry in enumerate(feed.entry):
      if isinstance(feed, gdata.spreadsheet.SpreadsheetsCellsFeed):
        print '%s %s\n' % (entry.title.text, entry.content.text)
      elif isinstance(feed, gdata.spreadsheet.SpreadsheetsListFeed):
        print '%s %s %s\n' % (i, entry.title.text, entry.content.text)
      else:
        print '%s %s\n' % (i, entry.title.text)

  def _PromptForSpreadsheet(self):
    """ Prompts user to select spreadsheet.
     
        Gets and displays titles of all spreadsheets for user to 
        select. Generic function taken from spreadsheetsExample.py.

        Args:
          none

        Returns:
           spreadsheet ID that the user selected: string
    """

    feed = self.s_client.GetSpreadsheetsFeed()
    self._PrintFeed(feed)
    input = raw_input('\nSelection: ')
    
    # extract and return the spreadsheet ID
    return feed.entry[string.atoi(input)].id.text.rsplit('/', 1)[1]

  def _PromptForWorksheet(self, key):
    """ Prompts user to select desired worksheet.

        Gets and displays titles of all worksheets for user to
        select. Generic function taken from spreadsheetsExample.py.

        Args:
          key: string

        Returns:
           the worksheet ID that the user selected: string
    """

    feed = self.s_client.GetWorksheetsFeed(key)
    self._PrintFeed(feed)
    input = raw_input('\nSelection: ')

    # extract and return the worksheet ID
    return feed.entry[string.atoi(input)].id.text.rsplit('/', 1)[1]

  def _AddReminder(self, event, minutes):
    """ Adds a reminder to a calendar event. 

        This function sets the reminder attribute of the CalendarEventEntry.
        The script sets it to 2 days by default, and this value is not 
        settable by the user. However, it can easily be changed to take this
        option.

        Args:
          event: CalendarEventEntry
          minutes: int
 
        Returns:
          the updated event: CalendarEventEntry
    """

    for a_when in event.when:
      if len(a_when.reminder) > 0:
        a_when.reminder[0].minutes = minutes
      else:
        a_when.reminder.append(gdata.calendar.Reminder(minutes=minutes))

    return self.c_client.UpdateEvent(event.GetEditLink().href, event)

  def _CreateBirthdayWebContentEvent(self, name, birthday, photo_url):
    """ Create the birthday web content event.

        This function creates and populates a CalendarEventEntry. webContent
        specific attributes are set. To learn more about the webContent
        format:

        http://www.google.com/support/calendar/bin/answer.py?answer=48528

        Args:
          name: string
          birthday: string - expected format (MM/DD)
          photo_url: string     

        Returns:
           the webContent CalendarEventEntry
    """   

    title = "%s's Birthday!" % name
    content = "It's %s's Birthday!" % name
    month = string.atoi(birthday.split("/")[0])
    day = string.atoi(birthday.split("/")[1])

    # Get current year
    year = time.ctime()[-4:]
    year = string.atoi(year)
    
    # Calculate the "end date" for the all day event
    start_time = datetime.date(year, month, day)
    one_day = datetime.timedelta(days=1)
    end_time = start_time + one_day

    start_time_str = start_time.strftime("%Y-%m-%d")
    end_time_str = end_time.strftime("%Y-%m-%d")

    # Create yearly recurrence rule
    recurrence_data = ("DTSTART;VALUE=DATE:%s\r\n"
        "DTEND;VALUE=DATE:%s\r\n"
        "RRULE:FREQ=YEARLY;WKST=SU\r\n" % 
        (start_time.strftime("%Y%m%d"), end_time.strftime("%Y%m%d")))
 
    web_rel = "http://schemas.google.com/gCal/2005/webContent"
    icon_href = "http://www.perstephanie.com/images/birthdayicon.gif"
    icon_type = "image/gif"
    extension_text = (
        'gCal:webContent xmlns:gCal="http://schemas.google.com/gCal/2005"'
        ' url="%s" width="300" height="225"' % (photo_url))

    event = gdata.calendar.CalendarEventEntry()
    event.title = atom.Title(text=title)
    event.content = atom.Content(text=content)
    event.recurrence = gdata.calendar.Recurrence(text=recurrence_data)
    event.when.append(gdata.calendar.When(start_time=start_time_str, 
        end_time=end_time_str))

    # Adding the webContent specific XML 
    event.link.append(atom.Link(rel=web_rel, title=title, href=icon_href, 
        link_type=icon_type))
    event.link[0].extension_elements.append(
        atom.ExtensionElement(extension_text))
 
    return event

  def _InsertBirthdayWebContentEvent(self, event):
    """ Insert event into the authenticated user's calendar. 

        Args:
          event: CalendarEventEntry

        Returns:
           the newly created CalendarEventEntry 
    """

    edit_uri = '/calendar/feeds/default/private/full'
    return self.c_client.InsertEvent(event, edit_uri)

  def Run(self):
    """ Run sample.
 
        TODO: add exception handling 

        Args:
          none
    """
     
    key_id = self._PromptForSpreadsheet()
    wksht_id = self._PromptForWorksheet(key_id)
 
    feed = self.s_client.GetListFeed(key_id, wksht_id) 

    found_name = False
    found_birthday = False
    found_photourl = False
    found_editurl = False 

    # Check to make sure all headers are present
    # Need to find at least one instance of name, birthday, photourl
    # editurl 
    if len(feed.entry) > 0:
      for name, custom in feed.entry[0].custom.iteritems():
        if custom.column == self.NAME:
          found_name = True
        elif custom.column == self.BIRTHDAY:
          found_birthday = True
        elif custom.column == self.PHOTO_URL:
          found_photourl = True
        elif custom.column == self.EDIT_URL:
          found_editurl = True

    if not found_name and found_birthday and found_photourl and found_editurl:
      print  ("ERROR - Unexpected number of column headers. Should have: %s,"
             " %s, %s, and %s." % (self.NAME, self.BIRTHDAY, self.PHOTO_URL, 
             self.EDIT_URL)) 
      sys.exit(1)

    # For every row in the spreadsheet, grab all the data and either insert
    # a new event into the calendar, or update the existing event

    # Create dict to represent the row data to update edit link back to
    # Spreadsheet
     
    for entry in feed.entry:
      d = {} 
      input_valid = True
      
      for name, custom in entry.custom.iteritems():
        d[custom.column] = custom.text

      month = int(d[self.BIRTHDAY].split("/")[0]) 
      day = int(d[self.BIRTHDAY].split("/")[1])
      
      # Some input checking. Script will allow the insert to continue with
      # a missing name value.
      if d[self.NAME] is None:
        d[self.NAME] = " "
      if d[self.PHOTO_URL] is None:
        input_valid = False
      if d[self.BIRTHDAY] is None:
        input_valid = False
      elif not 1 <= month <= 12 or not 1 <= day <= 31:
        input_valid = False  

      if d[self.EDIT_URL] is None and input_valid:
        event = self._CreateBirthdayWebContentEvent(d[self.NAME], 
            d[self.BIRTHDAY], d[self.PHOTO_URL])
        event = self._InsertBirthdayWebContentEvent(event)
        event = self._AddReminder(event, self.REMINDER)
	print "Added %s's birthday!" % d[self.NAME]
      elif input_valid: # Event already exists
        edit_link = d[self.EDIT_URL]
        event = self._CreateBirthdayWebContentEvent(d[self.NAME], 
            d[self.BIRTHDAY], d[self.PHOTO_URL])          
        event = self.c_client.UpdateEvent(edit_link, event)
        event = self._AddReminder(event, self.REMINDER)
        print "Updated %s's birthday!" % d[self.NAME]
      
      if input_valid:
        d[self.EDIT_URL] = event.GetEditLink().href
        self.s_client.UpdateRow(entry, d)
      else:
        print "Warning - Skipping row, missing valid input." 

def main():
  email = raw_input("Please enter your email: ")
  password = getpass.getpass("Please enter your password: ")
  
  sample = BirthdaySample(email, password)
  sample.Run()


if __name__ == '__main__':
  main()

