#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (C) 2010 Canonical
#
# Authors:
#  Didier Roche <didrocks@ubuntu.com>
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; version 3.
#
# This program is distributed in the hope that it will be useful, but WITHOUTa
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA


import logging
from optparse import OptionParser, OptionGroup
import sys
from time import localtime, strftime

import gettext
from gettext import gettext as _

from oneconf.version import *

SCOPE_NONE, SCOPE_SELECTION, SCOPE_ALL, SCOPE_HOSTS, SCOPE_HOST = range(5)
ACTION_NONE, ACTION_LIST, ACTION_DIFF, ACTION_UPDATE, ACTION_SHOW_INVENTORY, \
ACTION_SHOW_OTHERS = range(6)


def print_packages(installed_pkg_by_host, removed_pkg_by_host=None):

    if installed_pkg_by_host:
        print "Installed package:"
        for pkg_name in installed_pkg_by_host:
            (last_modification, distro_channel) = installed_pkg_by_host[pkg_name]
            print " %s: %s, installed from: %s" % (pkg_name,
                 strftime("%a, %d %b %Y %H:%M:%S", localtime(last_modification)),
                 distro_channel)
    if removed_pkg_by_host:
        print "Removed package:"
        for pkg_name in removed_pkg_by_host:
            (last_modification, distro_channel) = removed_pkg_by_host[pkg_name]
            print " %s: %s, was installed from: %s" % (pkg_name,
                 strftime("%a, %d %b %Y %H:%M:%S",
                              localtime(last_modification)))


def print_packages_diff(additional_pkg_by_host, missing_pkg_by_host):

    print "Additional packages: (package to install)"
    for pkg_name in additional_pkg_by_host:
        (time_added_on_hostid, distro_channel) = additional_pkg_by_host[pkg_name]
        message = "  %s: added on %s, from: %s" % (pkg_name,
                  strftime("%a, %d %b %Y %H:%M:%S",
                  localtime(time_added_on_hostid)), distro_channel)
        print message
    print "Missing packages: (package to remove)"
    for pkg_name in missing_pkg_by_host:
        (time_removed_on_hostid, distro_channel) = missing_pkg_by_host[pkg_name]
        message = "  %s: " % pkg_name
        if time_removed_on_hostid:
            message += "remotely removed on %s, was from: %s" % \
                      strftime("%a, %d %b %Y %H:%M:%S",
                      localtime(time_removed_on_hostid), distro_channel)
        else:
            message += "has never been present remotely"
        print message


def print_hosts(hosts, only_current=False):
    if len(hosts) == 1 or only_current:
        print "Listing this host stored in OneConf:"
    else:
        print "Hosts stored for OneConf:"

    for hostid in hosts:
        current, name, show_inventory, show_others = hosts[hostid]
        if (only_current and current) or not only_current:
            print "ID: %s\n name: %s\n share_inventory: %s\n show_others: %s" % (hostid, name, show_inventory, show_others)

def err_scope():
    print _("you can't define --selection, --all or --hosts together.")
    sys.exit(1)

def err_action():
    print _("you can't define --list, --diff, --update, --show/hide-inventory, --show/hide-others together.")
    sys.exit(1)

def option_not_compatible(options, action):
    print _("%s isn't compatible with %s" % (options, action))
    sys.exit(1)

if __name__ == '__main__':
    
    usage = _("usage: %prog [options]")
    parser = OptionParser(version= "%prog " + VERSION, usage=usage)
    parser.add_option("-d", "--diff", action="store_true",
                      dest="action_diff",
                      help=_("Current diff between this machine and another " \
                             "provided by hostname/hostid"))
    parser.add_option("-l", "--list", action="store_true",
                      dest="action_list",
                      help=_("List stored package or host lists (default)"))
    parser.add_option("-u", "--update", action="store_true",
                      dest="action_update",
                      help=_("update the package list in couchdb if host accepts updates"))
    parser.add_option("--debug", action="store_true", dest="debug",
                      help=_("enable debug mode (use --direct)"))
    parser.add_option("--direct", action="store_true", dest="directaccess",
                      help=_("don't use dbus for the request"))
    parser.add_option("--nocache", action="store_true", dest="nocache",
                      help=_("ignore cache (in daemon mode)"))
    scope_group = OptionGroup(parser, "Scope of actions:", "This define the " \
                       "scope to consider for list and diff command.")
    scope_group.add_option("-s", "--selection", action="store_true",
                      dest="scope_selection",
                      help=_("manually installed packages, not present " \
                                      "by default (default)"))
    scope_group.add_option("-a", "--all", action="store_true",
                      dest="scope_all",
                      help=_("get all relevant packages from storage"))
    scope_group.add_option("--hosts", action="store_true",
                      dest="scope_hosts",
                      help=_("all available hosts from storage (only with list)"))
    scope_group.add_option("--host", action="store_true",
                      dest="scope_host",
                      help=_("This host (only with list)"))
    scope_hosts = OptionGroup(parser, "Host print management:", "Thoses options " \
                       "can't be used together and only concerns diff and " \
                       "list actions. List hosts to get registered strings.")
    # default is '' for dbus compatible format
    scope_hosts.add_option("--hostname", action="store", dest="hostname",
                      help=_("specify target host"), default='')
    scope_hosts.add_option("--hostid", action="store", dest="hostid",
                      help=_("specify target host"), default='')
    scope_manage_host = OptionGroup(parser, "GUI host management:",
                        "Those options can't be used with anything else and are "
                        "present to manage GUI parameters for this host.")
    scope_manage_host.add_option("--show-inventory", action="store_true",
                      dest="show_inventory",
                      help=_("show this inventory in GUI"),
                      default=None)
    scope_manage_host.add_option("--hide-inventory", action="store_false",
                      dest="show_inventory",
                      help=_("hide this inventory in GUI"),
                      default=None)
    scope_manage_host.add_option("--show-others", action="store_true",
                      dest="show_others",
                      help=_("show others inventory in GUI"),
                      default=None)
    scope_manage_host.add_option("--hide-others", action="store_false",
                      dest="show_others",
                      help=_("hide others others inventory in GUI"),
                      default=None)
    parser.add_option_group(scope_group)
    parser.add_option_group(scope_hosts)
    parser.add_option_group(scope_manage_host)
    (options, args) = parser.parse_args()

    # set verbosity
    if options.debug:
        logging.basicConfig(level=logging.DEBUG)
    else:
        logging.basicConfig(level=logging.INFO)

    if options.directaccess or options.debug:
        logging.debug("Direct call")
        from oneconf.directconnect import DirectConnect
        oneconf = DirectConnect()
    else:
        logging.debug("Using dbus")
        from oneconf.dbusconnect import DbusConnect
        oneconf = DbusConnect()

    # store_const doesn't handle conflicts, so use manual triage
    scope = SCOPE_NONE
    if options.scope_selection:
        scope = SCOPE_SELECTION
    if options.scope_all:
        if scope != SCOPE_NONE:
            err_scope()
        scope = SCOPE_ALL
    if options.scope_hosts:
        if scope != SCOPE_NONE:
            err_scope()
        scope = SCOPE_HOSTS
    if options.scope_host:
        if scope != SCOPE_NONE:
            err_scope()
        scope = SCOPE_HOST

    # store_const doesn't handle conflicts, so use manual triage
    action = ACTION_NONE
    if options.action_list:
        action = ACTION_LIST
    if options.action_diff:
        if action != ACTION_NONE:
            err_action()
        action = ACTION_DIFF
    if options.action_update:
        if action != ACTION_NONE:
            err_action()
        action = ACTION_UPDATE
    if options.show_inventory is not None: # True and False both used
        if action != ACTION_NONE:
            err_action()
        action = ACTION_SHOW_INVENTORY
    if options.show_others is not None: # True and False both used
        if action != ACTION_NONE:
            err_action()
        action = ACTION_SHOW_OTHERS
    if action == ACTION_NONE:
        action = ACTION_LIST

    if options.hostid and options.hostname:
        print _("hostid and hostname can't be provided together")
        sys.exit(1)

    if action == ACTION_UPDATE:
        if options.nocache:
            option_not_compatible("--nocache", "--update")
        if options.hostid or options.hostname:
            print _("You can't use hostid or hostname when updating")
            sys.exit(1)
        if scope != SCOPE_NONE:
            print _("You can't define --selection, --all, --host or --hosts " \
                    "when updating")
            sys.exit(1)
        oneconf.update()

    elif action == ACTION_LIST:
        if scope == SCOPE_NONE:
            scope = SCOPE_SELECTION
        if scope == SCOPE_HOSTS:
            print_hosts(oneconf.get_all_hosts())
        if scope == SCOPE_HOST:
            print_hosts(oneconf.get_all_hosts(), True)
        elif scope == SCOPE_SELECTION:
            package_selection = oneconf.get_selection(
                               hostid=options.hostid, hostname=options.hostname,
                               use_cache=(not options.nocache))
            print_packages(package_selection)
        elif scope == SCOPE_ALL:
            (manually_installed_pkg, removed_pkg) = oneconf.get_all(
                    hostid=options.hostid, hostname=options.hostname,
                    use_cache=(not options.nocache))
            print_packages(manually_installed_pkg, removed_pkg)

    elif action == ACTION_DIFF:
        if not options.hostid and not options.hostname:
            print _("You have to provide either hostid or hostname for " \
                    "getting a diff")
            sys.exit(1)
        if scope == SCOPE_NONE:
            scope = SCOPE_SELECTION
        if scope == SCOPE_HOSTS:
            option_not_compatible("--hosts", "--diff")
        if scope == SCOPE_HOST:
            option_not_compatible("--host", "--diff")
        elif scope == SCOPE_SELECTION:
            (additional_pkg, missing_pkg) = oneconf.diff_selection(
                    hostid=options.hostid, hostname=options.hostname,
                    use_cache=(not options.nocache))
        elif scope == SCOPE_ALL:
            (additional_pkg, missing_pkg) = oneconf.diff_all(
                    hostid=options.hostid, hostname=options.hostname,
                    use_cache=(not options.nocache))
        print_packages_diff(additional_pkg, missing_pkg)

    elif action == ACTION_SHOW_INVENTORY:
        if options.nocache:
            option_not_compatible("--nocache", "--show/hide-inventory")
        if options.hostid or options.hostname:
            print _("You can't use hostid or hostname when changing show inventory status")
            sys.exit(1)
        if scope != SCOPE_NONE:
            print _("You can't define --selection, --all, --host or --hosts " \
                    "when changing show inventory status")
        oneconf.set_show_inventory(options.show_inventory, others=False)

    elif action == ACTION_SHOW_OTHERS:
        if options.nocache:
            option_not_compatible("--nocache", "--show/hide-others")
        if options.hostid or options.hostname:
            print _("You can't use hostid or hostname when changing show inventory status")
            sys.exit(1)
        if scope != SCOPE_NONE:
            print _("You can't define --selection, --all, --host or --hosts " \
                    "when changing show inventory status")
        oneconf.set_show_inventory(options.show_others, others=True)
    sys.exit(0)

