# -*- coding: utf-8 -*-
# Moovida - Home multimedia server
# Copyright (C) 2007-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.
#
# Author: Guido Amoruso <guidonte@fluendo.com>

from twisted.trial.unittest import TestCase
from elisa.core.utils import defer

from elisa.core import common
from elisa.core.config import Config
from elisa.core.utils.cancellable_defer import CancelledError

from elisa.plugins.pigment.pigment_frontend import PigmentFrontend
from elisa.plugins.pigment.pigment_frontend import ControllerNotFound
from elisa.plugins.pigment.pigment_controller import PigmentController

from elisa.plugins.poblesec.history import History


class FakeModel(list):
    pass

class FakeController(PigmentController):
    model = FakeModel()


class BadControllerError(Exception):
     pass


class BadController(PigmentController):
    def __init__(self):
        super(BadController, self).__init__()
        raise BadControllerError


class NeverEndingController(PigmentController):
    def initialize(self):
        return defer.Deferred()


class FakeBus(object):
    def register(self, callback, message_type):
        pass


class FakePluginRegistry(object):
    def get_enabled_plugins(self):
        return []


class FakeApplication(object):
    config = Config()
    bus = FakeBus()
    plugin_registry = FakePluginRegistry()

    def __init__(self):
        self.log_failure_called = False

    def log_failure(self, failure):
        self.log_failure_called = True


class TestHistory(TestCase):

    def setUp(self):
        common.set_application(FakeApplication())
        frontend = PigmentFrontend()
        self.history = History(frontend)

    def test_append_controller_error(self):
        dfr = self.history.append_controller("/fake/controller", "Dummy")
 
        self.failUnlessFailure(dfr, ControllerNotFound)
        self.failUnlessIdentical(self.history.current, None)
        self.failUnlessEqual(self.history.index, -1)
        self.failUnlessEqual(self.history._controllers, [])
        self.failUnless(common.application.log_failure_called)
        return dfr

    def test_append_controller_error_creating(self):
        self.history.frontend.add_controller("/bad/controller", BadController)
        dfr = self.history.append_controller("/bad/controller", "Bad")

        self.failUnlessFailure(dfr, BadControllerError)
        self.failUnlessIdentical(self.history.current, None)
        self.failUnlessEqual(self.history.index, -1)
        self.failUnlessEqual(self.history._controllers, [])
        self.failUnless(common.application.log_failure_called)
        return dfr

    def test_append_controller_success(self):
        self.history.frontend.add_controller("/fake/controller", FakeController)

        dfr = self.history.append_controller("/fake/controller", "Dummy")

        def checks(result):
            self.failUnlessIdentical(self.history.current, result)
            self.failUnlessEqual(self.history.index, 0)
            self.failUnlessEqual(self.history._controllers, [result])
            self.failUnlessEqual(len(self.history._controllers), 1)

        dfr.addCallback(checks)

        return dfr

    def _append_controllers(self):
        self.history.frontend.add_controller("/fake/controller", FakeController)
        self.history.frontend.add_controller("/dummy/controller", FakeController)

        dfr = self.history.append_controller("/fake/controller", "Fake")
        dfr.addCallback(lambda res: self.history.append_controller("/dummy/controller", "Dummy"))
        dfr.addCallback(lambda res: self.history.append_controller("/dummy/controller", "Dummy"))

        return dfr

    def test_append_controller_many(self):
        dfr = self._append_controllers()

        def checks(result):
            self.failUnlessIdentical(self.history.current, result)
            self.failUnlessEqual(self.history.index, 2)
            self.failUnlessEqual(self.history._controllers[-1], result)
            self.failUnlessEqual(len(self.history._controllers), 3)

        dfr.addCallback(checks)

        return dfr

    def test_append_controller_cancel(self):
        self.history.frontend.add_controller("/neverending/controller", NeverEndingController)
        self.history.frontend.add_controller("/fake/controller", FakeController)

        dfr = self.history.append_controller("/neverending/controller", "NeverEnding")

        dfr = self.history.append_controller("/fake/controller", "Fake")

        def checks(result):
            self.failUnlessIdentical(self.history.current, result)
            self.failUnlessEqual(self.history.index, 0)
            self.failUnlessEqual(self.history._controllers, [result])
            self.failUnlessEqual(len(self.history._controllers), 1)

        dfr.addCallback(checks)

        return dfr

    def test_go_back(self):
        dfr = self._append_controllers()

        def checks(result):
            self.failUnlessIdentical(self.history.current, self.history._controllers[-1])

            previous = self.history._controllers[-2]
            self.history.go_back()

            self.failUnlessIdentical(self.history.current, previous)
            self.failUnlessEqual(self.history.index, 1)
            self.failUnlessEqual(len(self.history._controllers), 2)

            self.history.go_back()
            self.failUnlessEqual(self.history.index, 0)
            self.failUnlessEqual(len(self.history._controllers), 1)

            # you cannot go back if there would be no controllers left
            self.history.go_back()
            self.failUnlessEqual(self.history.index, 0)
            self.failUnlessEqual(len(self.history._controllers), 1)

        dfr.addCallback(checks)

        return dfr

    def test_go_back_cancel(self):
        self.history.frontend.add_controller("/neverending/controller", NeverEndingController)
        self.history.frontend.add_controller("/fake/controller", FakeController)

        self.cancelled = False

        def on_cancel(*args):
            """catching the cancellation"""
            self.cancelled = True
            self.failUnlessEqual(self.history.index, 0)
            self.failUnlessEqual(len(self.history._controllers), 1)

        def check(result):
            # the cancelled was actually called
            self.assertTrue(self.cancelled)

        def next_append(res):
            path = "/neverending/controller"
            name = "NeverEnding"
            # delay the go back, so that it would kill this new neverending
            # controller
            reactor.callLater(0, self.history.go_back)

            return self.history.append_controller(path, name)

        dfr = self.history.append_controller("/fake/controller", "Fake")
        dfr.addCallback(next_append)
        dfr.addErrback(on_cancel)

        dfr.addCallback(check)

        return dfr

    test_go_back_cancel.timeout = 4

    def test_go_back_void(self):
        self.history.go_back()

    def test_go_back_broken_current(self):
        self.history.current = "Hijacked"
        self.history.go_back()

    def test_clear(self):
        dfr = self._append_controllers()

        def checks(result):
            self.failUnlessEqual(self.history.index, 0)
            self.failUnlessEqual(len(self.history._controllers), 1)

        dfr.addCallback(lambda result: self.history.clear())
        dfr.addCallback(checks)

        return dfr

    def test_marks(self):
        dfr = self._append_controllers()

        def in_foo_checks(result):
            self.failUnlessEqual(self.history.index, 2)
            self.failUnlessEqual(len(self.history._controllers), 3)

        def after_foo_checks(result):
            self.failUnlessEqual(self.history.index, 5)
            self.failUnlessEqual(len(self.history._controllers), 6)

        dfr.addCallback(lambda result: self.history.set_mark("foo"))
        dfr.addCallback(in_foo_checks)
        dfr.addCallback(lambda result: self._append_controllers())
        dfr.addCallback(after_foo_checks)
        dfr.addCallback(lambda result: self.history.goto_mark("foo"))
        dfr.addCallback(in_foo_checks)

        return dfr
