#!/usr/bin/python
# -*- coding: utf-8 -*-
### BEGIN LICENSE
# Copyright (C) 2009 Jono Bacon <jono@ubuntu.com>
# Copyright (C) 2010 Michael Budde <mbudde@gmail.com>
# Copyright (c) 2011 John S Gruber <johnsgruber@gmail.com>
#
#This program is free software: you can redistribute it and/or modify it
#under the terms of the GNU General Public License version 3, as published
#by the Free Software Foundation.
#
#This program is distributed in the hope that it will be useful, but
#WITHOUT ANY WARRANTY; without even the implied warranties of
#MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
#PURPOSE.  See the GNU General Public License for more details.
#
#You should have received a copy of the GNU General Public License along
#with this program.  If not, see <http://www.gnu.org/licenses/>.
### END LICENSE

import glib
import gobject
import gtk
import gio
import os
import time
import urlparse
import random
import pynotify
import logging
from datetime import timedelta

import lernid.DateTime as dt
from lernid.widgets.Widget import Widget
from lernid.lernidconfig import get_data_path
from lernid.Sessions import Session, parse_ical, read_ical, start_read_ical
from lernid.LernidOptions import Options


class Schedule(Widget):

    __gtype_name__ = 'LernidSchedule'

    __gsignals__ = {
        'session-changed': (
            gobject.SIGNAL_RUN_LAST, None, (object,)
        ),
        'session-ended': (
            gobject.SIGNAL_RUN_LAST, None, ()
        ),
    }

    COL_ICON, COL_DATE, COL_START, COL_END, COL_TITLE, COL_SESSION, \
    COL_DATETIME = range(7)

    def __init__(self):
        Widget.__init__(self, 'schedule')
        vbox = gtk.VBox()
        self.add(vbox)
        scroll = gtk.ScrolledWindow()
        scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        vbox.pack_start(scroll, expand=True)
        hbuttonbox = gtk.HBox(False, 0)
        vbox.pack_start(hbuttonbox, expand=False)
        self._copy_button = gtk.Button()
        self._open_button = gtk.Button()
        self._refresh_button = gtk.ToolButton(gtk.STOCK_REFRESH)
        self._refresh_button.set_expand(False)
        hbuttonbox.pack_start(self._copy_button, True, True, 0)
        hbuttonbox.pack_start(self._open_button, True, True, 0)
        hbuttonbox.pack_start(self._refresh_button, True, True, 0)
        self._button_setup(self._copy_button, self._open_button,
            self._refresh_button)

        self._treeview = gtk.TreeView()
        scroll.add(self._treeview)
        self._model = gtk.ListStore(str, str, str, str, str, object, object)
        self._treeview.set_model(self._model)
        self._treeview.set_search_column(self.COL_TITLE)
        def search_equal_func(model, column, key, iter):
            if key.upper() in model.get_value(iter, column).upper():
                return False
            return True
        self._treeview.set_search_equal_func(search_equal_func)
        def session_clicked_cb(tv, event, self):
            if not self._event:
                logging.error('Schedule clicked with no event connected')
                return
            logstring = self._event.logstring
            if not logstring:
                logging.debug('Schedule clicked, but no event logging'
                ' specified')
                return
            i = tv.get_path_at_pos(int(event.x), int(event.y))
            if not i:
                return
            row = tv.get_model()[i[0][0]]
            if not row[self.COL_DATETIME]:
                return
            date_time = row[self.COL_DATETIME]
            url = logstring % (date_time.year, date_time.month, date_time.day)
            gtk.show_uri(None, url, gtk.gdk.CURRENT_TIME)
        self._treeview.connect('button-release-event', session_clicked_cb, self)
        self._treeview.set_headers_visible(False)

        column = gtk.TreeViewColumn()
        cell = gtk.CellRendererPixbuf()
        cell.set_property('xalign', 0.5)
        column.pack_start(cell, True)
        column.set_attributes(cell, stock_id=self.COL_ICON)
        column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
        column.set_fixed_width(30)
        self._treeview.append_column(column)

        column = gtk.TreeViewColumn(_('Date'))
        cell = gtk.CellRendererText()
        cell.set_property('xpad', 6)
        column.pack_start(cell, False)
        column.set_attributes(cell, text=self.COL_DATE)
        column.set_alignment(0.5)
        self._treeview.append_column(column)

        column = gtk.TreeViewColumn(_('Starts'))
        cell = gtk.CellRendererText()
        cell.set_property('xpad', 6)
        column.pack_start(cell, False)
        column.set_attributes(cell, text=self.COL_START)
        column.set_alignment(0.5)
        self._treeview.append_column(column)

        column = gtk.TreeViewColumn(_('Ends'))
        cell = gtk.CellRendererText()
        cell.set_property('xpad', 6)
        column.pack_start(cell, False)
        column.set_attributes(cell, text=self.COL_END)
        column.set_alignment(0.5)
        self._treeview.append_column(column)

        column = gtk.TreeViewColumn(_('Title'))
        cell = gtk.CellRendererText()
        cell.set_property('xpad', 6)
        column.pack_start(cell, True)
        column.set_attributes(cell, markup=self.COL_TITLE)
        label = gtk.Label(_('Title'))
        label.set_padding(6, 0)
        label.show()
        column.set_widget(label)
        self._treeview.append_column(column)

        self._schedule = None
        self._current_session = None
        self._update_handle = None
        self._ical = None
        self._icalurl = None
        self._connected = False
        self._ircdate = (dt.now_utc() - timedelta(minutes=70)).date()
        self._cancellable = None
        self._event = None

        self.show_all()

    def _scramble_case(self, string):
        if '@' in string:
            pstring = string.split('@')
            newstring = pstring[0] + '@'
            string = pstring[1]
        else:
            newstring = ''
        random.seed()
        for s in string:
            if random.random() >= .5:
               newstring += s.upper()
            else:
               newstring += s.lower()
        return newstring
        # scramble the case of the url for this event connection.
        # Avoids occasional problem with gvfsd-http wedging 
        # particular url's after loss of connectivity

    def do_event_connect(self, event_man, event):
        self._event = event_man.current_event()
        self._connected = True
        self._last_upcoming_announce = None
        def finished_initial_ical_load_cb(gfile, result, unused):
            ical = read_ical(gfile, result, (event,self._ical))
            if ical == '': 
                return # Do nothing for cancelled I/O Operation
            self._ical = ical
            logging.debug('Finished initial ical load')
            if not (gfile.get_uri() == self._icalurl): # Sanity Check
                logging.debug('Old I/O completion ignored')
                return
            self._cancellable = None
            if not self._connected or not self._event:
                return
            self._update(event, self._ical, initial=True)
            secs = time.localtime().tm_sec
            self._update_handle = glib.timeout_add_seconds(65-secs, self._calendar_refresh, event)
        parsed = [j for j in urlparse.urlparse(event.icalurl)]
        parsed[1]= self._scramble_case(parsed[1])
        self._icalurl = urlparse.urlunparse(parsed)
        cancellable = gio.Cancellable()
        start_read_ical(self._icalurl, finished_initial_ical_load_cb,
            cancellable, (None, None))
        self._cancellable = cancellable
        logging.debug('Started initial ical load %s' % self._icalurl)

    def _calendar_refresh(self, event, force_update=False):
        def finished_refresh_ical_load_cb(gfile, result, data):
            force_reload, irclog_force = data
            ical = read_ical(gfile, result, (event, self._ical))
            if ical == '':
                return # Do nothing for a cancelled calendar read
            if not (gfile.get_uri() == self._icalurl): # Sanity Check
                logging.debug('Old I/O completion ignored')
                return
            self._cancellable = None
            logging.debug('Finished ical load')
            if not self._connected or not self._event:
                return
            if ical == self._ical and not irclog_force:
                logging.debug('Skipping Update--Calendar Unchanged')
                self._update_currency()
            else:
                self._ical = ical
                logging.debug('Updating Schedule')
                self._update(event, self._ical)
            secs = time.localtime().tm_sec
            self._update_handle = glib.timeout_add_seconds(65-secs, self._calendar_refresh, event)
            return

        self._update_handle = None
        irc_force = False
        now = dt.now_utc()
        trigger_time = now.minute in [1, 6, 11, 21, 31, 36, 41, 51]
        trigger_time = trigger_time and not Options.get('no-update')
        # Trigger in time to be done at :50, :00, :20, and :30 so that session
        # announcements will be made on time (bearing in mind low bandwidth
        # connections and the large calendar size
        ircdate = (now - timedelta(minutes=71)).date()
        if ircdate != self._ircdate and trigger_time:
            self._ircdate = ircdate
            irc_force = True
            logging.debug('Daily full calendar update %s' % now)
        if irc_force or force_update or trigger_time:
            parsed = [j for j in urlparse.urlparse(event.icalurl)]
            parsed[1]= self._scramble_case(parsed[1])
            self._icalurl = urlparse.urlunparse(parsed)
            cancellable = gio.Cancellable()
            start_read_ical(self._icalurl, finished_refresh_ical_load_cb, 
                cancellable, (force_update, irc_force))
            self._cancellable = cancellable
            logging.debug('Started ical load')
        else:
            self._update_currency()
            secs = time.localtime().tm_sec
            self._update_handle = glib.timeout_add_seconds(65-secs, self._calendar_refresh, event) 
        return False

    def _update(self, event, ical, initial=False):
        def quote(text):
            quote_table = {
                 '<' : '&lt;',
                 '>' : '&gt;',
                 "'" : '&apos;',
                 '"' : '&quot;',
                 '&' : '&amp;'
                }
            return_value = ''
            for l in text:
                return_value= return_value + quote_table.get(l,l)
            return return_value
        def insert_log_line(date, session):
            difference =  dt.now_utc() - date
            if difference < timedelta(minutes=70):
                return # There may not be a log yet
            sessionrow = ['', '', '', '']
            formatted_date = date.strftime('%x')
            localtime = dt.as_local(date)
            formatted_local = localtime.strftime('%H:00')
            formatted = \
            _('<u>Irc Logs for UTC %s </u><i>(Starting at %s local time)</i>') \
            % (formatted_date, formatted_local)
            sessionrow.append(formatted)
            sessionrow.append(session)
            sessionrow.append(date)
            self._model.append(sessionrow)
        
        def insert_blank_lines(number):
            for i in range(number):
                sessionrow = ['', '', '', '', '', None, None]
                self._model.append(sessionrow)

        self._schedule = parse_ical(event, ical)
        self._model.clear()
        self._scroll_to = None
        self._last_date = None
        current_row = False
        for session in self._schedule:
            utc_starttime = dt.as_utc(session.start_datetime)
            utc_date= dt.datetime(utc_starttime.year, utc_starttime.month,
                utc_starttime.day, tzinfo=utc_starttime.tzinfo)
            if (not utc_date == self._last_date):
                self._last_date = utc_date
                if event.logstring:
                    insert_log_line(utc_date, session)
            sessionrow = []
            sessionrow.append('')
            sessionrow.append(session.start_local_date)
            sessionrow.append(session.start_local_time)
            sessionrow.append(session.end_local_time)
            instructors = reduce(lambda x, y: x + y + ', ', session.instructors, '  ')[:-2]
            title = quote(session.title)
            if session.event:
                sessionrow.append(title + '<small>' + instructors + '</small> <i>' + quote(session.event) +'</i>')
            else:
                sessionrow.append(title + '<small>' + instructors + '</small>')
            sessionrow.append(session)
            sessionrow.append(None)
            current_row = self._model.append(sessionrow)
            if not self._scroll_to and session.state in (session.FUTURE, session.NOW):
                self._scroll_to = self._model.get_path(current_row)
        if not self._scroll_to and current_row:
            self._scroll_to = self._model.get_path(current_row)
        self._update_currency()

        insert_blank_lines(30)

        if initial and self._scroll_to:
            self._treeview.scroll_to_cell(self._scroll_to, use_align=True, row_align=.2)


        self._treeview.set_headers_visible(True)

    def do_event_disconnect(self, event_man, event):
        self._connected = False
        self._schedule = []
        self._current_session = None
        self._model.clear()
        if self._update_handle:
            glib.source_remove(self._update_handle)
            self._update_handle = None
        if self._cancellable:
            self._cancellable.cancel()
            self._cancellable = None
        self._treeview.set_headers_visible(False)
        self._event = None

    def _update_currency(self):
        ended = False
        if self._current_session and self._current_session.state == Session.PAST:
            ended = True
            self._current_session = None
        for i, row in enumerate(self._model):
            session = row[self.COL_SESSION]
            if session == None:
                continue
            if self._current_session and self._current_session.uid == session.uid:
                self._current_session = session  
            if session.state == Session.NOW:
                row[self.COL_ICON] = gtk.STOCK_GO_FORWARD
                if session != self._current_session:
                    if self._current_session == None:
                        session_message = _("Please keep the Session "
                        "tab selected to see session"
                        " learning materials.")
                        self.show_notification(
                            _('Session started'),
                            _('The session "{0}" has started. ').format(session.title) +
                            session_message
                        )
                    self._current_session = session
                    logging.debug('session changed')
                    self.emit('session-changed', session)
            if session.state == Session.FUTURE:
                row[self.COL_ICON] = None
                if timedelta(minutes=9) < session.start_datetime - dt.now_local() < timedelta(minutes=10):
                    if not self._last_upcoming_announce == session.uid:
                        self._last_upcoming_announce = session.uid
                        self.show_notification(
                            _('Session upcoming'),
                            _('The session "{0}" will begin in 10 minutes.').format(session.title)
                    )
            if session.state == Session.PAST:
                row[self.COL_ICON] = gtk.STOCK_NO
        if not self._current_session and ended:
            logging.debug('session ended')
            self.emit('session-ended')
        return True

    def get_current_session(self):
        return self._current_session

    def on_faculty(self,name):
        return self._current_session and name.lower() in \
	[i.lower() for i in self._current_session.instructors + self._current_session.helpers]

    def get_question_token(self):
        if self._current_session:
            if self._current_session.question_token:
                return self._current_session.question_token
            if self._current_session.locale:
                if self._current_session.locale.startswith('es'):
                    return 'PREGUNTA:'
                else:
                    return 'QUESTIION:'
        event = self._event
        if event and event.question_token:
            return event.question_token
        if event and event.locale:
            if event.locale.startswith('es'):
                return 'PREGUNTA:'
            return 'QUESTION:'
        return 'QUESTION:'

    def show_notification(self, summary, body):
        n = pynotify.Notification(summary, body, 'lernid')
        try:
            n.show()
        except:
            logging.debug("Notification failed from Schedule Widget")

    def _button_setup(self, copy_button, open_button, refresh_button):
        copy_button.set_label(_("Copy Calendar ICAL URL to Clipboard"))
        open_button.set_label(_("Open Calendar ICAL URL Using Your Browser"))
        copy_button.connect("clicked", self._copy_button_clicked)
        open_button.connect("clicked", self._open_button_clicked)
        refresh_button.connect("clicked", self._refresh_button_clicked)

    def _copy_button_clicked(self, widget):
        clipboard = gtk.Clipboard()
        event = self._event
        if not event:
            return
        clipboard.set_text(event.icalurl)

    def _open_button_clicked(self, widget):
        event = self._event
        if not event:
            return
        gtk.show_uri(None, event.icalurl, gtk.gdk.CURRENT_TIME)

    def _refresh_button_clicked(self, widget):
        event = self._event
        if not event:
            return
        if self._update_handle:
           glib.source_remove(self._update_handle)
           self._update_handle = None
           self._calendar_refresh(event, force_update=True)
