#!/usr/bin/python2
# coding=utf-8

# command line interface for the Koji build system
# Copyright (c) 2005-2014 Red Hat, Inc.
#
#    Koji is free software; you can redistribute it and/or
#    modify it under the terms of the GNU Lesser General Public
#    License as published by the Free Software Foundation;
#    version 2.1 of the License.
#
#    This software is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this software; if not, write to the Free Software
#    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
#
# Authors:
#       Dennis Gregorovic <dgregor@redhat.com>
#       Mike McLean <mikem@redhat.com>
#       Mike Bonnet <mikeb@redhat.com>
#       Cristian Balint <cbalint@redhat.com>

from __future__ import absolute_import
from __future__ import division
import logging
import optparse
import os
import re
import six
import sys
import types

import six.moves.configparser
import six.moves.xmlrpc_client

import koji
import koji.util
import koji.plugin

from koji_cli.lib import _, OptionParser, get_epilog_str, greetings, \
        warn, categories
from koji_cli.commands import *


def register_plugin(plugin):
    """Scan a given plugin for handlers

    Handlers are functions marked with one of the decorators defined in koji.plugin
    """
    for v in six.itervalues(vars(plugin)):
        if isinstance(v, six.class_types):
            #skip classes
            continue
        if callable(v):
            if getattr(v, 'exported_cli', False):
                if hasattr(v, 'export_alias'):
                    name = getattr(v, 'export_alias')
                else:
                    name = v.__name__
                # copy object to local namespace
                globals()[name] = v


def load_plugins(options, path):
    """Load plugins specified by our configuration plus system plugins. Order
    is that system plugins are first, so they can be overridden by
    user-specified ones with same name."""
    logger = logging.getLogger('koji.plugins')
    if os.path.exists(path):
        tracker = koji.plugin.PluginTracker(path=path)
        for name in sorted(os.listdir(path)):
            if not name.endswith('.py'):
                continue
            name = name[:-3]
            logger.info('Loading plugin: %s', name)
            tracker.load(name)
            register_plugin(tracker.get(name))


def get_options():
    """process options from command line and config file"""

    common_commands = ['build', 'help', 'download-build',
                       'latest-pkg', 'search', 'list-targets']
    usage = _("%%prog [global-options] command [command-options-and-arguments]"
                "\n\nCommon commands: %s" % ', '.join(sorted(common_commands)))
    parser = OptionParser(usage=usage)
    parser.disable_interspersed_args()
    progname = os.path.basename(sys.argv[0]) or 'koji'
    parser.__dict__['origin_format_help'] = parser.format_help
    parser.__dict__['format_help'] = lambda formatter=None: (
        "%(origin_format_help)s%(epilog)s" % ({
            'origin_format_help': parser.origin_format_help(formatter),
            'epilog': get_epilog_str()}))
    parser.add_option("-c", "--config", dest="configFile",
                      help=_("use alternate configuration file"), metavar="FILE")
    parser.add_option("-p", "--profile", default=progname,
                      help=_("specify a configuration profile"))
    parser.add_option("--keytab", help=_("specify a Kerberos keytab to use"), metavar="FILE")
    parser.add_option("--principal", help=_("specify a Kerberos principal to use"))
    parser.add_option("--krbservice", help=_("specify the Kerberos service name for the hub"))
    parser.add_option("--runas", help=_("run as the specified user (requires special privileges)"))
    parser.add_option("--user", help=_("specify user"))
    parser.add_option("--password", help=_("specify password"))
    parser.add_option("--noauth", action="store_true", default=False,
                      help=_("do not authenticate"))
    parser.add_option("--force-auth", action="store_true", default=False,
                      help=_("authenticate even for read-only operations"))
    parser.add_option("--authtype", help=_("force use of a type of authentication, options: noauth, ssl, password, or kerberos"))
    parser.add_option("-d", "--debug", action="store_true",
                      help=_("show debug output"))
    parser.add_option("--debug-xmlrpc", action="store_true",
                      help=_("show xmlrpc debug output"))
    parser.add_option("-q", "--quiet", action="store_true", default=False,
                      help=_("run quietly"))
    parser.add_option("--skip-main", action="store_true", default=False,
                      help=_("don't actually run main"))
    parser.add_option("-s", "--server", help=_("url of XMLRPC server"))
    parser.add_option("--topdir", help=_("specify topdir"))
    parser.add_option("--weburl", help=_("url of the Koji web interface"))
    parser.add_option("--topurl", help=_("url for Koji file access"))
    parser.add_option("--pkgurl", help=optparse.SUPPRESS_HELP)
    parser.add_option("--help-commands", action="store_true", default=False, help=_("list commands"))
    (options, args) = parser.parse_args()

    # load local config
    try:
        result = koji.read_config(options.profile, user_config=options.configFile)
    except koji.ConfigurationError as e:
        parser.error(e.args[0])
        assert False  # pragma: no cover

    # update options according to local config
    for name, value in six.iteritems(result):
        if getattr(options, name, None) is None:
            setattr(options, name, value)

    dir_opts = ('topdir', 'cert', 'serverca')
    for name in dir_opts:
        # expand paths here, so we don't have to worry about it later
        value = os.path.expanduser(getattr(options, name))
        setattr(options, name, value)

    #honor topdir
    if options.topdir:
        koji.BASEDIR = options.topdir
        koji.pathinfo.topdir = options.topdir

    #pkgurl is obsolete
    if options.pkgurl:
        if options.topurl:
            warn("Warning: the pkgurl option is obsolete")
        else:
            suggest = re.sub(r'/packages/?$', '', options.pkgurl)
            if suggest != options.pkgurl:
                warn("Warning: the pkgurl option is obsolete, using topurl=%r"
                     % suggest)
                options.topurl = suggest
            else:
                warn("Warning: The pkgurl option is obsolete, please use topurl instead")

    plugins_path = '%s/lib/python%s.%s/site-packages/koji_cli_plugins' % \
                   (sys.prefix, sys.version_info[0], sys.version_info[1])
    load_plugins(options, plugins_path)

    if options.help_commands:
        list_commands()
        sys.exit(0)
    if not args:
        list_commands()
        sys.exit(0)

    aliases = {
        'cancel-task' : 'cancel',
        'cxl' : 'cancel',
        'list-commands' : 'help',
        'move-pkg': 'move-build',
        'move': 'move-build',
        'latest-pkg': 'latest-build',
        'tag-pkg': 'tag-build',
        'tag': 'tag-build',
        'untag-pkg': 'untag-build',
        'untag': 'untag-build',
        'watch-tasks': 'watch-task',
    }
    cmd = args[0]
    cmd = aliases.get(cmd, cmd)
    if cmd.lower() in greetings:
        cmd = "moshimoshi"
    cmd = cmd.replace('-', '_')
    if ('anon_handle_' + cmd) in globals():
        if not options.force_auth and '--mine' not in args:
            options.noauth = True
        cmd = 'anon_handle_' + cmd
    elif ('handle_' + cmd) in globals():
        cmd = 'handle_' + cmd
    else:
        list_commands()
        parser.error('Unknown command: %s' % args[0])
        assert False  # pragma: no cover

    return options, cmd, args[1:]


def handle_help(options, session, args):
    "[info] List available commands"
    usage = _("usage: %prog help <category> ...")
    usage += _("\n(Specify the --help global option for a list of other help options)")
    parser = OptionParser(usage=usage)
    # the --admin opt is for backwards compatibility. It is equivalent to: koji help admin
    parser.add_option("--admin", action="store_true", help=optparse.SUPPRESS_HELP)

    (options, args) = parser.parse_args(args)

    chosen = set(args)
    if options.admin:
        chosen.add('admin')
    avail = set(list(categories.keys()) + ['all'])
    unavail = chosen - avail
    for arg in unavail:
        print("No such help category: %s" % arg)

    if not chosen:
        list_commands()
    else:
        list_commands(chosen)


def fix_pyver(options, logger):
    '''Attempt to run under the correct python version, if requested'''
    pyver = getattr(options, 'pyver', None)
    if not pyver:
        return
    if pyver not in [2,3]:
        logger.warning('Invalid python version requested: %s', pyver)
    if sys.version_info[0] == pyver:
        return
    py_exec = '/usr/bin/python%i' % pyver
    if not os.path.exists(py_exec):
        logger.error('No such file: %s', py_exec)
        return
    args = list(sys.argv)
    args.insert(0, py_exec)
    logger.debug('Executing via %s', py_exec)
    logger.debug('args = %r', args)
    try:
        os.execvp(py_exec, args)
    except Exception:
        logger.exception('Unable to execute with requested python version')


def list_commands(categories_chosen=None):
    if categories_chosen is None or "all" in categories_chosen:
        categories_chosen = list(categories.keys())
    else:
        # copy list since we're about to modify it
        categories_chosen = list(categories_chosen)
    categories_chosen.sort()
    handlers = []
    for name,value in globals().items():
        if name.startswith('handle_'):
            alias = name.replace('handle_','')
            alias = alias.replace('_','-')
            handlers.append((alias,value))
        elif name.startswith('anon_handle_'):
            alias = name.replace('anon_handle_','')
            alias = alias.replace('_','-')
            handlers.append((alias,value))
    handlers.sort()
    print(_("Available commands:"))
    for category in categories_chosen:
        print(_("\n%s:" % categories[category]))
        for alias,handler in handlers:
            desc = handler.__doc__ or ''
            if desc.startswith('[%s] ' % category):
                desc = desc[len('[%s] ' % category):]
            elif category != 'misc' or desc.startswith('['):
                continue
            print("        %-25s %s" % (alias, desc))

    print("%s" % get_epilog_str().rstrip("\n"))


if __name__ == "__main__":
    global options
    options, command, args = get_options()

    logger = logging.getLogger("koji")
    handler = logging.StreamHandler(sys.stderr)
    handler.setFormatter(logging.Formatter('%(asctime)s [%(levelname)s] %(name)s: %(message)s'))
    handler.setLevel(logging.DEBUG)
    logger.addHandler(handler)
    if options.debug:
        logger.setLevel(logging.DEBUG)
    elif options.quiet:
        logger.setLevel(logging.ERROR)
    else:
        logger.setLevel(logging.WARN)

    fix_pyver(options, logger)

    session_opts = koji.grab_session_options(options)
    session = koji.ClientSession(options.server, session_opts)
    rv = 0
    try:
        rv = locals()[command].__call__(options, session, args)
        if not rv:
            rv = 0
    except (KeyboardInterrupt, SystemExit):
        rv = 1
    except:
        if options.debug:
            raise
        else:
            exctype, value = sys.exc_info()[:2]
            rv = 1
            logger.error("%s: %s" % (exctype.__name__, value))
    try:
        session.logout()
    except:
        pass
    sys.exit(rv)
