# -*- coding: utf-8 -*-
# Moovida - Home multimedia server
# Copyright (C) 2006-2009 Fluendo Embedded S.L. (www.fluendo.com).
# All rights reserved.
#
# This file is available under one of two license agreements.
#
# This file is licensed under the GPL version 3.
# See "LICENSE.GPL" in the root of this distribution including a special
# exception to use Moovida with Fluendo's plugins.
#
# The GPL part of Moovida is also available under a commercial licensing
# agreement from Fluendo.
# See "LICENSE.Moovida" in the root directory of this distribution package
# for details on that license.

from twisted.internet import task

from elisa.core.utils import defer
from elisa.core.log import Loggable
from elisa.core import common

import gobject

class History(gobject.GObject, Loggable):

    # FIXME: signal name should contain a dash, not an underscore
    __gsignals__ = {'push_controller': 
                              (gobject.SIGNAL_RUN_LAST,
                              gobject.TYPE_BOOLEAN,
                              (gobject.TYPE_PYOBJECT,
                               gobject.TYPE_PYOBJECT,)),
                    'pop_controller': 
                              (gobject.SIGNAL_RUN_LAST,
                              gobject.TYPE_BOOLEAN,
                              (gobject.TYPE_PYOBJECT,
                               gobject.TYPE_PYOBJECT,))
                              }

    def __init__(self, frontend):
        super(History, self).__init__()
        self._marks = {}
        self._pending_append_deferred = None
        self.frontend = frontend
        self.current = None
        self.index = -1
        self._controllers = []

    def append_controller(self, path, display_name, **kwargs):
        if self._pending_append_deferred != None:
            self._pending_append_deferred.cancel()
            self._pending_append_deferred = None

        def append_controller(controller):
            self._pending_append_deferred = None
            controller.display_name = display_name
            self._controllers.append(controller)
            previous = self.current
            self.current = controller
            self.index = self._controllers.index(self.current)
            
            return controller

        def chaining_failed(failure):
            # the chaining failed because the deferred which is wrapped around
            # the other one has been cancelled
            failure.trap(defer.AlreadyCalledError)
        
        def log_controller_creation_error(error, controller_path):
            self._pending_append_deferred = None
            if error.type != defer.CancelledError:
                # as it was not a CancelledError, we want to log it
                log_path = common.application.log_failure(error)
                self.warning("Entering controller with path %s failed. Full" \
                         " failure log at %s" % (controller_path, log_path))
                # eat CancelledErrors
                return error
       
        try:
            dfr = self.frontend.create_controller(path, **kwargs)

            # chain it together with the cancellable deferred and take of the
            # case that the chaining fails
            cancellable_dfr = defer.Deferred()
            dfr.chainDeferred(cancellable_dfr)
            dfr.addErrback(chaining_failed)

            # store it, so that we can cancel it later
            self._pending_append_deferred = cancellable_dfr

        except:
            cancellable_dfr = defer.fail()

        try:
            previous = self._controllers[-1]
        except IndexError:
            previous = None


        # first the append callback
        cancellable_dfr.addCallback(append_controller)
        
        # then callbacks from somewhere else
        self.emit('push_controller', previous, cancellable_dfr)

        # then errors
        cancellable_dfr.addErrback(log_controller_creation_error, path)

        return cancellable_dfr

    def go_back(self):
        if not self.current or self.index < 1:
            self.warning("No current controller in this history.")
            return

        # if there was an append ongoing cancel it
        if self._pending_append_deferred is not None:
            self._pending_append_deferred.cancel()
            self._pendind_append_deferred = None

        # remove the current controller from the history
        try:
            index = self._controllers.index(self.current)
        except ValueError:
            self.warning("Current controller is unknown to this history.")
            return
        self._controllers.pop(index)

        # update the current index
        self.index = index-1

        if index > 0:
            previous = self.current
            self.current = self._controllers[index-1]
            self.emit('pop_controller', previous, self.current)

            # call clean method of the controller before losing last reference
            # to it
            return previous.clean()

    def clear(self):
        def iterate():
            while self.index > 0:
                yield self.go_back()

        return task.coiterate(iterate())

    def set_mark(self, label):
        self._marks[label] = self.index

    def goto_mark(self, label):
        marked_index = self._marks[label]
        del self._marks[label]

        def iterate():
            while self.index > marked_index:
                yield self.go_back()

        return task.coiterate(iterate())
