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

Review (by philn)

- this code won't be easy to maintain and is not suitable for
  production. It needs to be refactored and cleaned before release.
"""

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


import pprint
import optparse
import os
import sys

from twisted.internet import reactor
from twisted.internet import defer
from twisted.python import failure

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

from elisa.core import log


def option_parser():
    usage = """%prog [options] command


Commands:
  list [repository_source]
  list-installed
  update
  info plugin_name|plugin_file
  install plugin_name|plugin_file
  upgrade [plugin_name|plugin_file]
  upgrade-important
  remove plugin_name
"""

    parser = optparse.OptionParser(usage=usage)

    parser.add_option("--debug",
                      dest="debug",
                      action="store_true",
                      default=False,
                      help="Show detailed debug output")
    parser.add_option("--repository",
                      dest="repository",
                      default=None,
                      help="Only use the specified repository")
    parser.add_option("--shell",
                      dest="shell",
                      action="store_true",
                      default=False,
                      help="Open a shell from where you can use one registry "
                           "instance, called 'registry'")
    parser.add_option("--plugin-dirs",
                      dest="plugin_dirs",
                      default=None,
                      help="Directories where to search for installed plugins")

    return parser


def init_log(debug=False):
    log.init('EPM_DEBUG')
    log.setPackageScrubList('epm')
    if debug:
        log.setDebug("default:5")
    else:
        log.setDebug(os.getenv('EPM_DEBUG', ""))


def installation_ok(res):
    print "Installation went ok."
    return res


def installation_broken(res):
    print "Problems during installation: %s" % (res.getErrorMessage ())
    #return res


def stop_reactor(res=None):
    reactor.stop()


def do_list(registry, repository=None):

    repositories = registry.get_repositories()

    def got_plugins(plugins, repository=None):
        if repository:
            print "Plugins in", repository
        else:
            print "Known repositories:", [r.source for r in repositories]

        pprint.pprint(map (lambda p: (p.name, p.version), plugins))

        return plugins

    if repository and not repository in [r.source for r in repositories]:
        print repository, "is not a known repository."
        return defer.succeed(-1)

    dfr = registry.list_plugins(source=repository)
    dfr.addCallback(got_plugins, repository)

    return dfr


def main(argv=sys.argv):

    parser = option_parser()
    (options, args) = parser.parse_args(args=argv[1:])

    init_log(debug=options.debug)

    plugin_dirs = options.plugin_dirs
    cache_file = None
    if plugin_dirs:
        plugin_dirs = plugin_dirs.split(":")
        cache_file = os.path.join(plugin_dirs[0], 'cache.db')

    sources = []
    if options.repository:
        sources = [options.repository]

    registry = EggRegistry(static_sources=sources,
                           plugin_dirs=plugin_dirs,
                           cache_file=cache_file)

    if options.shell:
        try:
            import IPython
            shell = IPython.Shell.IPShell(args, locals())
            shell.mainloop()
        except ImportError:
            import code
            shell = code.InteractiveConsole(locals=locals())
            shell.interact()
        stop_reactor()
        return

    if len(args) == 0:
        parser.print_help()
        stop_reactor()
        return -2

    if args[0] == 'list':

        if len(args) > 2:
            parser.print_help()
            stop_reactor()
            return -2

        repository = None
        if len(args) == 2:
            repository = args[1]

        dfr = do_list(registry=registry, repository=repository)
        dfr.addBoth(stop_reactor)

    elif args[0] == 'list-installed':

        if len(args) > 1:
            parser.print_help()
            stop_reactor()
            return -2

        pprint.pprint(map (lambda p: (p.name, p.version),
                          registry.get_installed_plugins()))

        stop_reactor()

    elif args[0] == 'update':

        if len(args) > 1:
            parser.print_help()
            return -2

        dfr = registry.update_cache()

        def updated_cache(res):
            print "Successfully updated cache."
            return res

        dfr.addCallback(updated_cache)
        dfr.addBoth(stop_reactor)

    elif args[0] == 'info':

        if len(args) != 2:
            parser.print_help()
            stop_reactor()
            return -2

        dfr = registry.get_info(plugin_name=args[1])

        def got_info(info):
            print info

        dfr.addCallback(got_info)
        dfr.addBoth(stop_reactor)

    elif args[0] == 'install':

        if len(args) != 2:
            parser.print_help()
            stop_reactor()
            return -2

        def install_done(res):
            print "DONE", res

        def install_error(res):
            print "Errors while installing %s: %s" % \
                  (args[1], res.getErrorMessage())

        if not args[1].endswith('.egg'):
            dfr = registry.easy_install_plugin('elisa-plugin-' + args[1])
        else:
            dfr = registry.easy_install_plugin(args[1])

        dfr.addCallback(install_done)
        dfr.addErrback(install_error)
        dfr.addBoth(stop_reactor)

    # FIXME: think about uninstalling before/after installing the upgrade
    elif args[0] in ['upgrade', 'upgrade-important']:

        if len(args) > 2:
            parser.print_help()
            stop_reactor()
            return -2

        if len(args) == 1:
            update_states = []
            if args[0] == 'upgrade-important':
                update_states = ['important']

            upgradeables = registry.get_upgradeable_plugins(update_states=update_states)
            if upgradeables:
                print "These plugins can be upgraded:", \
                      pprint.pformat(map(lambda p: (p.name, p.version, p.new_version),
                                     upgradeables))
            else:
                print "Nothing to upgrade."
                stop_reactor()
                return 0

            go = raw_input("Do you want to continue [Y/n]? ")
            if go.strip() in ['y', 'Y', '']:
                dfr = registry.upgrade(update_states=update_states)
                dfr.addCallback(installation_ok)
                dfr.addErrback(installation_broken)
            else:
                dfr = defer.succeed(True)
                dfr.addCallback(lambda res: pprint.pprint("Abort."))
        else:
            dfr = registry.upgrade(plugin_name=args[1])
            dfr.addCallback(installation_ok)
            dfr.addErrback(installation_broken)

        dfr.addBoth(stop_reactor)

    elif args[0] == 'remove':

        if len(args) != 2:
            parser.print_help()
            stop_reactor()
            return -2

        if not args[1] in map(lambda p: p.name, registry.get_installed_plugins()):
            dfr = defer.succeed(True)
            dfr.addCallback(lambda res: pprint.pprint("'%s' is not installed." % (args[1])))
            dfr.addBoth(stop_reactor)
            return 0

        # FIXME: think about uninstall deps, too. This is not yet activated, as
        # we get the dependecies in an "unorderd" list, so we should set the
        # "force" option to the uninstall() method. We are not sure we want to
        # do that.
        deps = registry.get_installed_dependent_plugins(plugin_name=args[1])
        deps = map(lambda d: d.name, deps)

        print "The following plugins will be removed:\n  ", args[1]
        go = raw_input("Do you want to continue [Y/n]? ")
        if go.strip() in ['y', 'Y', '']:
            dfr = registry.remove(args[1])
            dfr.addCallback(lambda res: pprint.pprint("Removed " + (res.name) + "."))
            dfr.addErrback(lambda res, name: pprint.pprint("Error while removing '%s': %s" \
                                                           % (name, res.getErrorMessage())),
                           args[1])
        else:
            dfr = defer.succeed(True)
            dfr.addCallback(lambda res: pprint.pprint("Abort."))

        dfr.addBoth(stop_reactor)

    # FIXME: remove me or make me better
    elif args[0] in ['upload', 'upload-important']:

        if len(args) < 2:
            parser.print_help()
            stop_reactor()
            return -2

        target = options.repository or "http://localhost:8000/xmlrpc"
        repository = create_repository_from_source(source=target)
        if not repository:
            print "Cannot use this repository:", target
            stop_reactor()
            return

        update_state = ''
        if args[0] == 'upload-important':
            update_state = 'important'

        dfr = repository.upload(api_key="test_api",
                                eggs=args[1:],
                                update_state=update_state)

        def upload_done(res):
            ok, errors = res
            if ok:
                print "Successfully uploaded these eggs:", ok
            if errors:
                print "Couldn't upload these eggs:", errors

        dfr.addCallback(upload_done)
        dfr.addBoth(stop_reactor)

    else:

        parser.print_help()
        stop_reactor()
        return -2

