# -*- 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.

"""
Elisa Plugin Manager
"""

__maintainer__ = 'Guido Amoruso <guidonte@fluendo.com>'


from elisa.core.log import Loggable
from elisa.core import __version__ as elisa_version
from elisa.core.epm.exceptions import *
from elisa.core.epm.egg_plugin import EggPlugin
from elisa.core import common
from elisa.core.utils import misc

from twisted.web import xmlrpc
from twisted.web import client
from twisted.internet import defer
from twisted.internet import threads

import locale
import base64
import os
import sys
import xmlrpclib
import time
import cPickle
import datetime
from urllib2 import Request, urlopen
import zlib

# HACK
ELISA_PLUGINS_DIR = os.path.join(os.path.expanduser('~'), '.elisa', 'plugins')

def create_repository_from_source(source):
    """Repository factory.

    Create the suitable repository object for the source schema.

    @param source: the source for the repository
    @type source:  str
    @return:       the suitable repository
    @rtype:        L{epm.EggRepository}
    """

    if source.startswith('http://'):
        return EggXmlrpcRepository(source=source)
    elif source.startswith('file://'):
        return EggFileRepository(source=source)
    else:
        return None


class EggRepository(Loggable):
    """
    Elisa egg repository.
    """
    
    def __init__(self, source=None):
        self.source = source
        Loggable.__init__(self)
        self.cache_file = os.path.join(ELISA_PLUGINS_DIR, self.get_id())
        self._restore_state()
        
    def __repr__(self):
        result = "<%s source=%r>" % (self.__class__.__name__, self.source)
        return result

    def _restore_state(self):
        self._persistent_data = {}
        self.debug("Loading persistent state from %r", self.cache_file)
        
        try:
            cache = open(self.cache_file, 'rb')
        except IOError:
            self.debug("can't restore persistent state. %r not found",
                       self.cache_file)
            self.last_modified = 0
        else:

            try:
                self._persistent_data = cPickle.load(cache)
            except Exception, e:
                self.warning('Cannot load plugins from cache: ' + str(e))
            else:
                
                self.last_modified = self._persistent_data.get('last_modified',
                                                               0)

            cache.close()

    def _save_state(self):
        cache = open(self.cache_file, 'wb')

        self._persistent_data['last_modified'] = self.last_modified
        
        cPickle.dump(self._persistent_data, cache)
        cache.close()
        
    def get_id(self):
        """
        """
        if self.source is None:
            source = "egg_repository"
        else:
            source = self.source
        repo_id = source.replace('/', '_').replace(':', '_')
        repo_id += '.db'
        return repo_id

    def get_plugins(self):
        """Retrive the list of its own plugins.

        @return: the list of plugins
        @rtype: a L{twisted.internet.defer.Deferred} with the list of
                L{epm.egg_plugin.EggPlugin}s as result
        """
        raise NotImplementedError

    def get_download_url_for_plugin(self, api_key, plugin_id):
        """Get the download url.

        @param api_key: the user's key
        @type api_key: string
        @param plugin: the plugin name
        @type plugin: string
        @return: the download url
        @rtype: a L{twisted.internet.defer.Deferred} with a string as result
        """
        raise NotImplementedError

    def download(self, plugin):
        """Actually download the plugin data.

        @param plugin: the plugin
        @type plugin: L{epm.egg_plugin.EggPlugin}
        @return: the plugin data
        @rtype: a L{twisted.internet.defer.Deferred} with a string as result
        """
        raise NotImplementedError

    def upload(self, api_key, plugin, update_state=''):
        """Upload a plugin to the repository.

        @param api_key: the user key
        @type api_key: string
        @param plugin: the plugin
        @type plugin: L{epm.egg_plugin.EggPlugin}
        @param update_state: the update state for the plugin (eg.: important,
                             recommended)
        @type update_state: string
        @type plugin: L{epm.egg_plugin.EggPlugin}
        @return: the plugin data
        @rtype: string
        """
        raise NotImplementedError


class EggXmlrpcRepository(EggRepository):
    """
    Elisa egg repository.
    """

    def get_plugins(self):
        self.debug("Retrieving plugins on %r", self.source)
        server = xmlrpc.Proxy(self.source)
        install_date = ""
        if common.application:
            # FIXME: make install_date public
            install_date = common.application._install_date

        # FIXME: fill me
        os_name = misc.get_distro()
        os_version = ""
        partner_code = ""
            
        dfr = server.callRemote('get_plugins_list_url', install_date,
                                elisa_version, os_name, os_version,
                                partner_code)

        def got_xml_url((last_modified, xml_url)):
            return self._plugins_from_xml_url(last_modified, xml_url)
        
        dfr.addCallback(got_xml_url)
        return dfr

    def _plugins_from_xml_url(self, last_modified, url):
        plugins = []
        now = time.time()
        
        delta = last_modified - self.last_modified
        self.debug("remote last modification date: %r", last_modified)
        self.debug("saved  last modification date: %r", self.last_modified)
        self.debug("Delta between last modification date of the file and our saved date: %r", delta)
        if last_modified > self.last_modified:
            req = Request(url)
            try:
                response = urlopen(req)
            except IOError, e:
                if hasattr(e, 'reason'):
                    self.info('We failed to reach a server. Reason: ', e.reason)
                elif hasattr(e, 'code'):
                    self.debug('The server couldn\'t fulfill the request. Error code: ', e.code)
            else:
                xml = zlib.decompress(response.read())
                plugins = xmlrpclib.loads(xml)[0][0]
                self.debug("%r plugins found", len(plugins))
                plugins = [EggPlugin(repository=self, **p) for p in plugins]
                self.last_modified = last_modified
        else:
            self.info("Plugins list already uptodate")
            raise AlreadyUpToDate()
            
        self._save_state()
        return defer.succeed(plugins)
    
    def get_download_url_for_plugin(self, api_key, plugin_id):
        server = xmlrpc.Proxy(self.source)
        dfr = server.callRemote('get_download_url_for_plugin',
                                api_key,
                                plugin_id)

        def got_url(url):
            #FIXME: remove this hack
            url = url.replace('bling', 'localhost')
            self.debug('Download url for plugin with id "%s": %s' % (plugin_id, url))
            return url

        dfr.addCallback(got_url)

        return dfr

    def download(self, plugin):
        dfr = self.get_download_url_for_plugin(api_key='test_api',
                                               plugin_id=plugin.id)

        def got_data(data, url):
            #FIXME: this will not make sense when the url will be "obscured"
            return data, os.path.basename(url)

        def download_error(res, url):
            self.warning("Error while downloading %s: %s" \
                         % (url, res.getErrorMessage()))
            return res

        def got_url(url):
            self.debug("Download url for '%s': %s" % (plugin.name, url))
            dfr = client.getPage(url)
            dfr.addCallback(got_data, url)
            dfr.addErrback(download_error, url)
            return dfr

        def url_error(err):
            self.warning("Error while retrieving download url: %s" \
                         % (err.getErrorMessage()))
            return err

        dfr.addCallback(got_url)
        dfr.addErrback(url_error)

        return dfr

    def _blocking_upload(self, api_key, eggs, update_state):
        server = xmlrpclib.Server(self.source)

        ok = []
        errors = []
        for egg in eggs:
            try:
                egg_file = open(egg, 'rb')
                egg_name = os.path.basename(egg)
                egg_data = base64.b64encode(egg_file.read())
                egg_file.close()
            except:
                errors.append(egg)
                continue
            try:
                server.add_plugin(api_key, update_state, egg_name, egg_data)
            except Exception, e:
                self.warning("Error uploading %r: %r", egg, e)
                errors.append(egg)
            else:
                ok.append(egg)

        return ok, errors

    def upload(self, api_key, eggs, update_state=''):
        return threads.deferToThread(self._blocking_upload,
                                     api_key,
                                     eggs,
                                     update_state)


class EggFileRepository(EggRepository):
    """
    Elisa egg repository.
    """

    def get_plugins(self):
        pass

    def download(self, plugin):
        pass

