# -*- coding: utf-8 -*-
# Elisa - Home multimedia server
# Copyright (C) 2007 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 2.
# 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.


import xmlrpclib
import tempfile
import os, os.path
import sys
import sets
import shutil

import pkg_resources

from twisted.trial import unittest
from twisted.trial.unittest import SkipTest
from twisted.internet.defer import Deferred, DeferredList

from elisa.core.epm.egg_registry import EggRegistry
from elisa.core.epm.egg_plugin import EggPlugin

from elisa.core.epm.exceptions import *

from elisa.extern.epr.egg_parser import EggParser, make_fake_egg


def _make_egg(name, version='0.1', deps=[]):
    egg, data = make_fake_egg(name=name, version=version, deps=deps)
    tmpdir = tempfile.mkdtemp()
    egg_path = os.path.join(tmpdir, egg)
    egg_file = open(egg_path, "wb")
    egg_file.write(data)
    egg_file.close()

    return egg_path


class TestEggRegistry(unittest.TestCase):

    system_wide_install_dir = tempfile.mkdtemp()
    local_wide_install_dir = tempfile.mkdtemp(dir=os.path.expanduser("~"))

    egg_names = ['one', 'two']
    fake_eggs = [_make_egg(name) for name in egg_names]

    egg_names.append('a')
    fake_eggs.append(_make_egg('a', deps=['aa', 'ab']))

    fake_eggs.append(_make_egg('aa'))
    fake_eggs.append(_make_egg('ab'))

    def _cleanup_eggs(self, eggs):
        for egg in eggs:
            os.remove(egg)
            os.rmdir(os.path.dirname(egg))

    def tearDownClass(self, *args):
        self._cleanup_eggs(self.fake_eggs)
        shutil.rmtree(self.system_wide_install_dir, ignore_errors=True)
        shutil.rmtree(self.local_wide_install_dir, ignore_errors=True)

    def setUp(self):
        cache_file = os.path.join(self.local_wide_install_dir, 'cache.db')
        self.registry = EggRegistry(plugin_dirs=[self.local_wide_install_dir,
                                                 self.system_wide_install_dir],
                                    cache_file=cache_file)
        self.server = xmlrpclib.Server('http://localhost:8000/xmlrpc')

        try:
            methods = sets.Set(self.server.system.listMethods())
        except Exception, exc:
            raise SkipTest(exc)

        exported = sets.Set(['add_plugin', 'get_plugins',
                             'get_plugin_information',
                             'update_plugin', 'get_download_url_for_plugin',
                             'list_categories', 'get_plugins_for_category',
                             'get_bundles', 'get_plugins_for_bundle',
                             'add_bundle', 'add_plugin_in_bundle',
                             'get_bundles_to_rebuild',
                             'rebuild_bundle', 'remove_plugin', 'reset'])

        if not exported.issubset(methods):
            raise SkipTest("Server API incomplete")

    def tearDown(self):
        pass

    def test_get_repositories(self):
        self.registry.get_repositories()

    def test_update_cache(self):
        dfr = self.registry.update_cache()

        return dfr

    def _test_on_all_plugins(self, callback, errback=None):
        dfr = self.registry.list_plugins()

        dfr.addCallback(callback)
        if errback:
            dfr.addErrback(errback)

        return dfr

    def test_list_plugins(self, count=None):

        def callback(res):
            self.assert_(isinstance(res, list))
            if not count is None:
                self.assert_(len(res) == count)

        return self._test_on_all_plugins(callback)

    def test_get_installed_plugins(self):
        plugins = self.registry.get_installed_plugins()

        self.assert_(isinstance(plugins, list))

    def test_get_upgradeable_plugins(self):
        plugins = self.registry.get_upgradeable_plugins()

        self.assert_(isinstance(plugins, list))

    def test_upload_eggs(self, res=None, eggs=None, reset=True):
        if reset:
            self.server.reset ()

        if not eggs:
            eggs = self.fake_eggs

        repository = self.registry.static_repositories[0]
        dfr = repository.upload(api_key="test_api", eggs=eggs)

        def on_uploading_done(res):
            ok, errors = res
            print "OK", len(ok)
            print "ERRORS", len(errors)
            return ok, errors

        dfr.addCallback(on_uploading_done)

        return dfr

    def _upload_egg(self, egg_path, remove=False):
        repository = self.registry.static_repositories[0]
        dfr = repository.upload(api_key="test_api", eggs=[egg_path])

        def on_upload(res, egg_path, remove):
            ok, errors = res
            if remove:
                os.remove(egg_path)
            return res + (egg_path,)

        dfr.addCallback(on_upload, egg_path, remove)

        return dfr

    def _test_install_eggs(self):
        self.server.reset ()

        dfr = self.test_upload_eggs()

        def _install_all(res, name, remaining=[]):
            dfr = self.registry.easy_install_plugin('elisa-plugin-' + name,
                                                    directory=self.local_wide_install_dir)

            if remaining:
                dfr.addCallback(_install_all, remaining[0], remaining[1:])

            return dfr

        def update_ok(res, plugins):
            return _install_all(None, self.egg_names[0], self.egg_names[1:])

        def update_chache(plugins):
            dfr = self.registry.update_cache()
            dfr.addCallback(update_ok, plugins)

            return dfr

        dfr.addCallback(update_chache)

        return dfr

    def test_upgrade_eggs(self, res=None):
        """Build new version of a subset of eggs (egg_names)."""
        new_eggs = [_make_egg(name, version="0.2") for name in self.egg_names]

        dfr = self._test_install_eggs()

        dfr.addCallback(self.test_upload_eggs, eggs=new_eggs, reset=False)

#        dfr = self.test_upload_eggs(eggs=new_eggs, reset=False)

        def on_upgrade(res):
            print "UPGRADE"
            self._cleanup_eggs(new_eggs)

        def on_upgrade_error(res):
            """If there is nothing to do, that's because neither we can
            reset the server and upload the 0.1 plugins first, neither we can
            be sure the test_install_eggs() is run first."""
            try:
                res.raiseException()
            except NothingToUpgradeError:
                pass
            except:
                self.assert_(False)
            print "NOTHING TO UPGRADE"
            self._cleanup_eggs(new_eggs)

        def update_ok(res, plugins):
            upgradeables = self.registry.get_upgradeable_plugins()
            self.assert_(len(new_eggs) == len(self.egg_names))
            dfr = self.registry.upgrade(directory=self.local_wide_install_dir)
            dfr.addCallback(on_upgrade)
            dfr.addErrback(on_upgrade_error)

            return dfr

        def update_chache(plugins):
            dfr = self.registry.update_cache()
            dfr.addCallback(update_ok, plugins)

            return dfr

        dfr.addCallback(update_chache)

        return dfr

    def test_remove_localwide(self):
        name = "removeable"
        egg = _make_egg(name)

        dfr = self.registry.easy_install_plugin(egg,
                                                directory=self.local_wide_install_dir)

        def on_uninstall(res):
            pass
            self._cleanup_eggs([egg])

        def on_uninstall_error(res):
            self.assert_(False)
            self._cleanup_eggs([egg])

        def on_install(res):
            dfr = self.registry.remove(name)
            dfr.addCallback(on_uninstall)
            dfr.addErrback(on_uninstall_error)

            return dfr

        dfr.addCallback(on_install)

        return dfr

    def test_remove_systemwide(self):
        name = "removeable"
        egg = _make_egg(name)

        dfr = self.registry.easy_install_plugin(egg,
                                                directory=self.system_wide_install_dir)

        def on_uninstall(res):
            self.assert_(False)
            self._cleanup_eggs([egg])

        def on_uninstall_error(res):
            try:
                res.raiseException()
            except UninstallationError:
                pass
            except:
                self.assert_(False)
            self._cleanup_eggs([egg])

        def on_install(res):
            dfr = self.registry.remove(name)
            dfr.addCallback(on_uninstall)
            dfr.addErrback(on_uninstall_error)

            return dfr

        dfr.addCallback(on_install)

        return dfr

