# -*- coding: utf-8 -*-
# Elisa - Home multimedia server
# Copyright (C) 2006-2008 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 Elisa with Fluendo's plugins.
#
# The GPL part of Elisa is also available under a commercial licensing
# agreement from Fluendo.
# See "LICENSE.Elisa" in the root directory of this distribution package
# for details on that license.

"""
Manage Actions queued in a Thread. 
"""

__maintainer__ = 'Florian Boucault <florian@fluendo.com>'

from twisted.internet import defer, reactor
from twisted.python import failure
from threadsafe_list import ThreadsafeList
import time
from elisa.core import log

class DeferredActionsManager(log.Loggable):

    """
    Manage a queue of actions (callables), and execute them sequentially in
    a single thread.

    This is very inefficiant and should not be used anymore! 

    WARNING: It is NOT thread-safe.
    """


    def __init__(self):
        log.Loggable.__init__(self)
        
        self._tasks_queue = ThreadsafeList()
        # NOTE: self._running accesses are not protected by locks
        # which means that start, stop and enqueue_action should not
        # be called by different threads
        self._running = False
        self.time_between_tasks = 0

    def start(self):
        """
        Start processing the actions in a separate thread
        if it was not already the case.
        """
        if self._running == False:
            self._running = True
            self.debug("Starting")
            reactor.callInThread(self._main_loop)

    def stop(self):
        """
        Stop processing the actions. The thread is stopped as soon as possible.
        """
        self.debug("Stopping")
        self.remove_all_actions()

    def remove_all_actions(self):
        """
        Empty the queue of actions.
        """
        self._tasks_queue = ThreadsafeList()

        self._running = False

    def enqueue_action(self, action, *args, **kwargs):
        """
        Add an action at the end of the queue. It will be processed when all the previous
        actions in the queue have finished. If no actions are being processed,
        it starts processing automatically.

        WARNING: The program will not stop until the current task
        has finished.

        :parameters:
            `action` : callable
                Action to be executed
            `args` : list
                Arguments passed to the action
            `kwargs` : dictionary
                Keywords arguments passed to the action

        :return: Deferred object used to chain success and error callbacks
        :rtype: twisted.internet.defer.Deferred
        """
        deferred = defer.Deferred()
        self._tasks_queue.append((action, args, kwargs, deferred))

        if self._running == False:
            self.start()

        return deferred

    def insert_action(self, rank, action, *args, **kwargs):
        """
        FIXME, DOCME: wrong documentation copied/pasted from enqueue_action

        Add an action at the end of the queue. It will be processed when all the previous
        actions in the queue have finished. If no actions are being processed,
        it starts processing automatically.

        WARNING: The program will not stop until the current task
        has finished.

        :parameters:
            `action` : callable
                Action to be executed
            `args` : list
                Arguments passed to the action
            `kwargs` : dictionary
                Keywords arguments passed to the action

        :return: Deferred object used to chain success and error callbacks
        :rtype: twisted.internet.defer.Deferred
        """
        deferred = defer.Deferred()
        self._tasks_queue.insert(rank, (action, args, kwargs, deferred))

        if self._running == False:
            self.start()

        return deferred

    def _main_loop(self):
        """
        Process all the actions in the queue sequentially.
        """
        # FIXME: reactor.running behaviour is not clear
        while reactor.running:
            if len(self._tasks_queue) == 0:
                break

            task = self._tasks_queue.pop(0)
            self._execute_task(task)

            #if self.time_between_tasks > 0:
            time.sleep(self.time_between_tasks)

        self._running = False

    def _execute_task(self, task):
        """
        Process a single action.

        :parameters:
            `task` : (callable, list, dictionary, twisted.internet.defer.Deferred)
                Action to be processed and associated deferred object
        """

        action = task[0]
        args = task[1]
        kwargs = task[2]
        deferred = task[3]

        self.debug("execute task %r", task)
        try:
            result = action(*args, **kwargs)
        except Exception, exc:
            self.debug("failed: %r", exc)
            f = failure.Failure()
            reactor.callFromThread(deferred.errback, f)
        else:
            self.debug("result: %r", result)
            reactor.callFromThread(deferred.callback, result)


#if __name__ == "__main__":
#    import time
#
#    mng = DeferredActionsManager()
#
#    def f():
#        print "f called"
#        time.sleep(2)
#        return 51
#
#    def g(x, y):
#        print "g called"
#        time.sleep(2)
#        return x+y
#
#    def display(x):
#        print "display called:", x
#
#    def test1():
#        dfr = mng.enqueue_action(f)
#        dfr.addCallback(display)
#        time.sleep(1)
#
#        dfr = mng.enqueue_action(f)
#        dfr.addCallback(display)
#
#        dfr = mng.enqueue_action(g, 4, 8)
#        dfr.addCallback(display)
#
#        print "end of queue creation"
#
#    def test2():
#        for i in xrange(10):
#            #dfr = mng.enqueue_action(f)
#            dfr = mng.enqueue_action(g, 2, 3)
#            dfr.addCallback(display)
#
#        print "end of queue creation"
#
#    reactor.callWhenRunning(test1)
#    reactor.run()
#
#    print "quit"
