#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2009, 2010 Novell, Inc.
#   This library is free software; you can redistribute it and/or modify
# it only under the terms of version 2.1 of the GNU Lesser General Public
# License as published by the Free Software Foundation.
#
#   This library 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 library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

import socket
import sys
import optparse
import xml.etree.ElementTree as etree
import xmlrpclib

from spacewalk.susemanager import mgr_ncc_sync_lib
from spacewalk.susemanager.authenticator import Authenticator
from spacewalk.common.suseLib import current_cc_backend, BackendType


def main():
    parser = optparse.OptionParser(
        version="%prog 0.1",
        description="Sync SUSE Manager repositories from NCC")

    parser.add_option("-l", "--list-channels", action="store_true", dest="list",
                      help="list all the channels which are available for you")
    parser.add_option("--list-products", action="store_true", dest="productlist",
                      help="list all the products which are available for you")
    parser.add_option("--list-products-xml", action="store_true", dest="productlistxml",
                      help="list all the products which are available for you [XML Format]")
    parser.add_option("--add-product", action="store_true", dest="addproduct",
                      help="Add all mandatory channels of a product [interactive]")
    parser.add_option("--add-product-by-ident", action="store", dest="addproductbyident",
                      help="Add all mandatory channels of the product identified by the given ident")
    parser.add_option("-c", "--channel", action="store",
                      help="add a new channel and trigger a reposync")
    parser.add_option("--all-childs", action="store_true", dest="allchilds",
                      help="Show also children, if the parent is not synced yet")
    parser.add_option("--no-optional", action="store_true", dest="noopt",
                      help="Do not list optional channels")
    parser.add_option("--filter", action="store", dest="filter",
                      help="Show only labels, which contains the filter word (case-insensitiv)")
    # hide the -p, -f, -s options, they're deprecated bnc#760924
    parser.add_option("-p", "--products", action="store_true",
                      help=optparse.SUPPRESS_HELP)
    parser.add_option("-f", "--update_cf", action="store_true",
                      help=optparse.SUPPRESS_HELP)
    parser.add_option("-s", "--update_subscriptions", action="store_true",
                      help=optparse.SUPPRESS_HELP)
    # hide also new option -u, the customer should use always -r
    parser.add_option('-u', "--update_up", action="store_true",
                      help=optparse.SUPPRESS_HELP)

    parser.add_option("-r", "--refresh", action="store_true",
                      help="refresh product, channel and subscription "
                      "information without triggering any reposyncs")
    parser.add_option("-m", "--migrate_res", action="store_true",
                      help="migrate to RES subscriptions")
    parser.add_option('-q', '--quiet', action='store_true', dest='quiet',
                      help="Print no output, still logs output")
    parser.add_option('-d', '--debug', dest='debug', default=-1,
                      help="debugging")
    # --test is some kind of unit test. Do not use it in production systems
    parser.add_option("-t", "--test", action="store_true",
                      help=optparse.SUPPRESS_HELP)
    parser.add_option('-D', '--dump', dest="dumppath", action="store",
                      help='Dump NCC xml data into the given directory')
    parser.add_option("--from-dir", action="store", dest='fromdir',
                      help="read data from directory instead of NCC")
    parser.add_option("--from-mirror", action="store", dest="mirror",
                      help="URL of a local mirror like SMT. Only to download the RPMs.")

    (options, args) = parser.parse_args()

    if not current_cc_backend() == BackendType.NCC:
        msg = """Error: mgr-ncc-sync cannot be used because the Suse Customer Center (SCC) backend is currently active.

Please use the "mgr-sync" tool instead.
"""
        sys.stderr.write(msg)
        sys.exit(1)

    syncer = mgr_ncc_sync_lib.NCCSync(quiet=options.quiet,
                                      debug=options.debug,
                                      fromdir=options.fromdir,
                                      mirror=options.mirror)
    if options.list:
        if syncer.is_iss_slave:
            print "SUSE Manager is configured as slave server. Please use \"mgr-inter-sync\" command."
            return
        clist = syncer.list_channels()
        display_channel_list(clist, allchilds=options.allchilds, filter=options.filter, noopt=options.noopt)
    elif options.productlist:
        if syncer.is_iss_slave:
            print "SUSE Manager is configured as slave server. Please use \"mgr-inter-sync\" command."
            return
        ret = syncer.list_products()
        product_list(ret, allchilds=options.allchilds, filter=options.filter)
    elif options.addproduct:
        if syncer.is_iss_slave:
            print "SUSE Manager is configured as slave server. Please use \"mgr-inter-sync\" command."
            return
        plist = syncer.list_products()
        numcache = product_list(plist, allchilds=options.allchilds, filter=options.filter)
        print ""
        num = raw_input("Enter the number of the product you want to add: ")
        if not numcache.has_key(num):
            print "Invalid selection"
            sys.exit(1)
        add_product(syncer, plist[numcache[num]])
    elif options.addproductbyident:
        if syncer.is_iss_slave:
            print "SUSE Manager is configured as slave server. Please use \"mgr-inter-sync\" command."
            return
        ident = options.addproductbyident
        plist = syncer.list_products()
        if not plist.has_key(ident):
            print "Cannot find product with ident", ident
            sys.exit(1)
        add_product(syncer, plist[ident])
    elif options.productlistxml:
        if syncer.is_iss_slave:
            print "SUSE Manager is configured as slave server. Please use \"mgr-inter-sync\" command."
            return
        ret = syncer.list_products()
        root = etree.Element('product_list')
        for xp in ret.values():
            xp.to_xml(root)
        print etree.tostring(root)
    elif options.dumppath:
        syncer.dump_to(options.dumppath)
    elif options.channel:
        if syncer.is_iss_slave:
            print "SUSE Manager is configured as slave server. Please use \"mgr-inter-sync\" command."
            return
        syncer.update_channels()
        syncer.add_channel(options.channel)
        # schedule reposync even if the channel is already in the database
        syncer.sync_channel(options.channel)
    elif options.products:
        suse_products = syncer.get_suse_products_from_ncc()
        syncer.update_suse_products_table(suse_products)
    elif options.update_cf:
        syncer.update_channel_family_table_by_config()
    elif options.update_up:
        syncer.update_upgrade_pathes_by_config()
    elif options.test:
        syncer.test_subscription_calculation()
    elif options.update_subscriptions:
        syncer.update_subscriptions()
    elif options.migrate_res:
        syncer.migrate_res()
    elif options.refresh:
        syncer.update_channels()
        syncer.update_channel_family_table_by_config()
        suse_products = syncer.get_suse_products_from_ncc()
        syncer.update_suse_products_table(suse_products)
        syncer.update_subscriptions()
        syncer.sync_suseproductchannel()
        syncer.update_upgrade_pathes_by_config()
    else:
        syncer.update_channels()
        syncer.update_channel_family_table_by_config()
        suse_products = syncer.get_suse_products_from_ncc()
        syncer.update_suse_products_table(suse_products)
        syncer.update_subscriptions()
        syncer.sync_suseproductchannel()
        syncer.update_upgrade_pathes_by_config()
        syncer.sync_installed_channels()

def add_product(syncer, product):
    syncer.update_channels()
    if product.is_base():
        # we need to sync the base channel first
        bc = product.base_channel
        if product.mandatory_channels[bc] == ".":
            syncer.add_channel(bc)
            syncer.sync_channel(bc)
            product.mandatory_channels[bc] = "P"
    for channel_label in product.mandatory_channels:
        status = product.mandatory_channels[channel_label]
        if status == ".":
            syncer.add_channel(channel_label)
        if status == "." or status == "P":
            # sync already provided channels and the ones which have never
            # been added
            syncer.sync_channel(channel_label)
    for channel_label in product.optional_channels:
        status = product.optional_channels[channel_label]
        if status == "P":
            # Force a refresh of all the optional channels that have previosly
            # been added
            syncer.sync_channel(channel_label)

def display_channel_list(clist, allchilds=False, filter='', noopt=False):
    print("Listing channels ...\n\n"
          "Status:\n"
          "- P - channel is installed (provided)\n"
          "- . - channel is not installed, but is available\n"
          "- X - channel is not available\n")

    current_parent = ''
    for channel in clist:
        # we have the right order
        if channel['parent'] == '':
            current_parent = channel
            current_parent['displayed'] = False
            if do_display(channel['label'], filter):
                print "[%s] %s" % (channel['status'], channel['label'])
                current_parent['displayed'] = True
        elif channel['parent'] == current_parent['label']:
            if allchilds or current_parent['status'] == 'P':
                if (current_parent['displayed'] and do_display(channel['label'], filter) and
                     not ( noopt and channel['optional'])):
                    print "    [%s] %s" % (channel['status'], channel['label'])
        else:
            # we do not have the right order. Print an error
            print "ERROR: invalid order of channel list %s == %s" % (channel['parent'], current_parent['label'])


def product_list(plist, allchilds=False, filter=''):
    print("Listing products ...\n\n"
          "Status:\n"
          "- P - all required channels are installed (provided)\n"
          "- . - not all required channels are installed\n")
    counter = 0
    numcache = {}
    for pk in sorted(plist.iterkeys()):
        p = plist[pk]
        if not p.is_base():
            continue
        stat = p.status()

        if not do_display(p.display_name(), filter):
            continue

        if stat == ".":
            counter = counter + 1
            print "(%3d) [%s] %s" % (counter, stat, p.display_name())
            numcache[str(counter)] = p.ident
            if not allchilds:
                continue
        else:
            print "(---) [%s] %s" % (stat, p.display_name())
        for ck in sorted(plist.iterkeys()):
            c = plist[ck]
            if c.parent_product != p.ident:
                continue
            if not do_display(c.display_name(), filter):
                continue
            cstat = c.status()
            if cstat == "." and stat == 'P':
                counter = counter + 1
                print "(%3d)    [%s] %s" % (counter, cstat, c.display_name())
                numcache[str(counter)] = c.ident
            else:
                print "(---)    [%s] %s" % (cstat, c.display_name())
    return numcache

def do_display(label='', filter=''):
    if not label:
        return False
    if not filter:
        return True
    if filter.lower() in label.lower():
        return True
    return False


if __name__ == "__main__":
    try:
        main()
    except IOError, e:
        print "ERROR: %s" % e
