#!/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 os
import gtk
import gobject
import poppler
import re
import logging
import gio
import cairo
import StringIO
import glib
import random

from lernid.widgets.Widget import Widget
from lernid.lernidconfig import save_cache_path
from lernid.Statusbar import Statusbar

class Slide(Widget):

    __gtype_name__ = 'LernidSlide'

    __gsignals__ = {
        'slides-downloaded': (
            gobject.SIGNAL_RUN_LAST, None, ()
        ),
        'slide-changed': (
            gobject.SIGNAL_RUN_LAST, None, ()
        ),
    }

    SLIDE_START_SIZE = 300
    SLIDE_MIN_SIZE = 150

    def __init__(self, vertical=False):
        Widget.__init__(self, 'slide')
        self._vertical = vertical
        self._slidebutton_cb = None
        align = gtk.Alignment(0.5, 0.5, 1.0, 1.0)
        if vertical:
            align.set_padding(0, 5, 3, 3)
        else:
            align.set_padding(3, 5, 0, 4)
        self.add(align)
        frame = gtk.Frame()
        frame.set_shadow_type(gtk.SHADOW_IN)
        align.add(frame)
        self._vbox = gtk.VBox()
        frame.add(self._vbox)
        self._top = gtk.EventBox()
        self._scroll = gtk.ScrolledWindow()
        if vertical:
            self._scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_NEVER)
        else:
            self._scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
        self._image = gtk.Image()
        self._scroll.add_with_viewport(self._image)
        self._top.add(self._scroll)
        self._vbox.add(self._top)
        self._eventbox = gtk.EventBox()
        self._slidebutton = gtk.Button()
        self._slidelabel = gtk.Label()
        self._slidelabel.set_line_wrap(True)
        self._slidelabel.set_justify(gtk.JUSTIFY_CENTER)
        self._slidelabel.set_text(_('Click to Open in External Browser'))
        self._slidebutton.add(self._slidelabel)
        self._eventbox.add(self._slidebutton)
        self._eventbox.set_tooltip_text(_('Click to open the slides in ' 
            'your browser. From there you can browse, bookmark, or save them.'))
        self._vbox.pack_start(self._eventbox, expand=False)
        viewport = self._scroll.get_child()
        viewport.set_shadow_type(gtk.SHADOW_NONE)
        self._master_slide = None
        self._session_slide_downloaded = False
        self._cancellable = None
        self._slide_retry = None

        self._last_width = None
        self._last_height = None
        self.connect('expose-event', self._resize)
        self.set_no_show_all(False)
        self.hide()

    def do_event_connect(self, eventman, event):
        classroom = eventman.get_widget_by_name('classroom')
        schedule = eventman.get_widget_by_name('schedule')
        self._on_faculty = schedule.on_faculty # retrieve method
        self.event_connect_signal(classroom, 'message-received', self._classroom_msg_received)
        schedule = eventman.get_widget_by_name('schedule')
        self.event_connect_signal(schedule, 'session-changed', self._session_changed)
        self.event_connect_signal(schedule, 'session-ended', self._session_ended)

    def do_event_disconnect(self, eventman, event):
        self._image.clear()
        self.hide()
        if self._slidebutton_cb:
            self._slidebutton.disconnect(self._slidebutton_cb)
            self._slidebutton_cb = None
        Statusbar.pop_message('slidesession')
        if self._slide_retry:
            glib.source_remove(self._slide_retry)
            self._slide_retry = None
        if self._cancellable:
            self._cancellable.cancel()
            self._cancellable = None
        self.event_disconnect_signals()

    def _classroom_msg_received(self, classroom, chan, sender, text):
        matches = re.search(r'\[(?i)SLIDE\s+(\d+).*\]', text)
        if matches and self._session_slide_downloaded:
            if self._on_faculty(sender):
                self._change_slide_page(int(matches.groups()[0]))
                return
        matches = re.search(r'\[(?i)SLIDEFILE\s+(\S+)\s+(\d+).*\]', text)
        if matches:
            if self._on_faculty(sender):
                self._download_slides(matches.groups()[0], int(matches.groups()[1]))
                return
        matches = re.search(r'\[(?i)SLIDEFILE\s+(\S+).*\]', text)
        if matches:
            if self._on_faculty(sender):
                self._download_slides(matches.groups()[0], 1)
                return

    def _change_slide_page(self, pagenumber):
        logging.debug('changing slide to page %d' % pagenumber)
        try:
            # FIXME
            slide_url = "file://%s/slides.pdf" % os.path.realpath(save_cache_path('lernid'))
            pdf = poppler.document_new_from_file(slide_url, None)
            if pagenumber < 0 or pagenumber > pdf.get_n_pages():
                return
            page = pdf.get_page(int(pagenumber) - 1)
            w, h = page.get_size()
            w, h = (int(w), int(h))
            surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
            context = cairo.Context(surface)
            page.render(context)
            # technique from diogodivision's "BlingSwitcher",
            # through http://www.mikedesjardins.net
            temp = StringIO.StringIO()
            surface.write_to_png(temp)
            temp.seek(0)
            loader = gtk.gdk.PixbufLoader()
            loader.write(temp.getvalue())
            loader.close()
            pixbuf = loader.get_pixbuf()
        except:
            Statusbar.push_message(_('An error was encountered while trying to load slide number {0}'.format(pagenumber)), 'slidesession', duration=15)
            logging.debug("Something went wrong when loading slide %s" % pagenumber)
            return
        self._master_slide = pixbuf
        self.show()
        if self._vertical:
            length = self.allocation.height
        else:
            length = self.allocation.width
        if length < self.SLIDE_START_SIZE:
            if self._vertical:
                self.set_size_request(-1, self.SLIDE_START_SIZE)
            else:
                self.set_size_request(self.SLIDE_START_SIZE, -1)
            self.queue_resize()
        self._last_width = None
        self._last_height = None
        self._resize()
        self.emit('slide-changed')

    def _resize(self, widget=None, data=None):
        if self._master_slide is not None:
            mwidth, mheight = self._master_slide.get_width(), self._master_slide.get_height()
            width, height = self._scroll.allocation.width, self._scroll.allocation.height
            if self._vertical:
                height = max(self.SLIDE_MIN_SIZE, height)
                width = int(float(height)/float(mheight)*mwidth)
            else:
                width = max(self.SLIDE_MIN_SIZE, width)
                height = int(float(width)/float(mwidth)*mheight)
            if width == self._last_width and height == self._last_height:
                return True
            self._last_width = width
            self._last_height = height
            newpb = gtk.gdk.Pixbuf.scale_simple(self._master_slide, width, height, gtk.gdk.INTERP_BILINEAR)
            self._image.clear()
            self._image.set_from_pixbuf(newpb)
            self._image.show()

    @staticmethod
    def _retry(n): # retry generator
        for i in range(n):
            yield random.randint(30,120) # random delay until retry
        while 1: yield False

    def _download_slides(self, slideurl, page):
        if self._slide_retry:
            glib.source_remove(self._slide_retry)
            self._slide_retry = None
        if self._cancellable:
            self._cancellable.cancel()
            self._cancellable = None
        self._retries = self._retry(3).next # retry generator obj. next method
        self._download_slides_attempt(slideurl, page)

    def _download_slides_attempt(self, slideurl, page):
        self._slide_retry = None
        Statusbar.pop_message('slidesession')
        path = os.path.join(save_cache_path('lernid'), 'slides.pdf')
        cancellable = gio.Cancellable()
        try:
            logging.debug('downloading slides from %s' % slideurl)
            slidefile = gio.File(slideurl)
            destfile =  gio.File(path)
# It doesn't seem that the gio.FILE_COPY_OVERWRITE is effective for copy_async
            if destfile.query_exists():
                try:
                    destfile.delete(None)
                except: pass
            def reporthook(done_so_far, size):
                Statusbar.pop_message('slidesession')
                if size == 0:
                    return
                msg = _('Downloading session slides (%i%% of %i KB)...') % (100*(float(done_so_far) / float(size)), float(size)/float(1024))
                Statusbar.push_message(msg, 'slidesession', duration=3)
            def download_done(giofile, result, stuff):
                    self, slideurl, page, cancellable = stuff
                    try:
                        giofile.copy_finish(result)
                        self._cancellable = None
                    except gio.Error, information:
                        Statusbar.push_message(_('An error was encountered while downloading slides'), 'slidesession', duration=120)
                        logging.debug('Error when downloading slides from %s;  %s' % (slideurl, information))
                        secs = self._retries()
                        if secs and not cancellable.is_cancelled():
                            logging.error( \
                               'Retrying slide download in %i secs' % secs)
                            self._slide_retry = glib.timeout_add_seconds(secs,
                                self._download_slides_attempt, slideurl,
                                    page) 
                        else:
                            logging.error('Slide downloading failed')
                        return
                    logging.debug('Slide download completed')
                    Statusbar.push_message(_('Slides have been downloaded'), 'slidesession', duration=10)
                    self._session_slide_downloaded = True
                    self.emit('slides-downloaded')
                    self._top.set_tooltip_text(slideurl)
                    if self._slidebutton_cb:
                        self._slidebutton.disconnect(self._slidebutton_cb)
                    self._slidebutton_cb = self._slidebutton.connect("clicked",
                        self._open_slides_in_browser,
                        slideurl)
                    self._change_slide_page(page)
            slidefile.copy_async(destfile, download_done, reporthook, cancellable=cancellable, user_data=(self, slideurl, page, cancellable), flags=gio.FILE_COPY_OVERWRITE)
            self._cancellable = cancellable
        except gio.Error, information:
            Statusbar.push_message(_('An error was encountered while downloading slides'), 'slidesession', duration=120)
            logging.debug('Error when downloading slides from %s;  %s' % (slideurl, information))

    def _session_changed(self, schedule, session):
        if self._slide_retry:
            glib.source_remove(self._slide_retry)
            self._slide_retry = None
        if self._cancellable:
            self._cancellable.cancel()
            self._cancellable = None
        Statusbar.pop_message('slidesession')
        if session.slides:
            self._download_slides(session.slides, 1)
        else:
            self._image.clear()
            self.hide()
            self._session_slides_downloaded = False
            if self._slidebutton_cb:
                self._slidebutton.disconnect(self._slidebutton_cb)
                self._slidebutton_cb = None
            Statusbar.push_message(_('This session does not use slides'), 'slidesession', duration=10)

    def _session_ended(self, schedule):
        if self._slide_retry:
            glib.source_remove(self._slide_retry)
            self._slide_retry = None
        if self._cancellable:
            self._cancellable.cancel()
            self._cancellable = None
        self._image.clear()
        self.hide()
        Statusbar.pop_message('slidesession')
        self._session_slide_downloaded = False
        if self._slidebutton_cb:
            self._slidebutton.disconnect(self._slidebutton_cb)
            self._slidebutton_cb = None

    def _open_slides_in_browser(self, widget, url):
        gtk.show_uri(None, url, gtk.gdk.CURRENT_TIME)
