#!/usr/bin/python
#
#
# The MIT License (MIT)
# Copyright (C) 2012 SUSE Linux Products GmbH
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions: 
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software. 
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE. 
# 

from smdba.basegate import GateException
import sys
import os
import time
from threading import Thread


class Console:
    """
    Console app for SUSE Manager database control.

    Author: Bo Maryniuk <bo@suse.de>
    """

    # General
    VERSION = "1.5.2"
    DEFAULT_CONFIG = "/etc/rhn/rhn.conf"

    # Config
    DB_BACKEND = "db_backend"


    def __init__(self, configpath=None):
        """
        Constructor.
        """
        self.console_location = os.path.abspath(__file__)
        self.config_file = configpath
        if 'auto' not in sys.argv:
            self.get_config()
        else:
            self.get_config_auto()
        self.load_db_backend()


    def load_db_backend(self):
        """
        Load required backend for the database.
        """
        try:
            gate_name = "smdba." + self.config.get(self.DB_BACKEND, "unknown") + "gate"
            __import__(gate_name)
            self.gate = sys.modules[gate_name].getGate(self.config)
            self.gate.check()
        except GateException, ex:
            raise Exception("Gate error: " + str(ex))
        except Exception, ex:
            if self.config_file:
                raise Exception("Unknown database backend. Please check %s configuration file." % self.config_file)
            else:
                raise Exception("Unknown database backend and config was not specified.")


    def get_config_auto(self):
        """
        Manual config.
        """
        self.config = {}
        args, params = self.get_opts(sys.argv[2:])
        if not 'backend' in params.keys():
            raise Exception("When using Auto, you need also load backend.\nBut you should not manually use Auto at first place!")
        self.config = {'db_backend' : params.get('backend', 'unknown')}


    def get_config(self):
        """
        Read rhn config for database type.
        """
        self.config = {}
        self.config_file = self.config_file and self.config_file or self.DEFAULT_CONFIG

        cfg = None
        if os.path.exists(self.config_file):
            cfg = open(self.config_file).readlines()
        else:
            raise Exception("Cannot open configuration file: " + self.config_file + "\n" + "Use sudo, perhaps?")

        for line in cfg:
            try:
                key, value = line.replace(" ", "").strip().split("=", 1)
                if key.startswith("db_"):
                    # Handling odd change in Spacewalk 1.7 where "db_name" could be URI
                    self.config[key] = ((key == "db_name") and value.startswith("//")) and value.split("/")[-1] or value
            except:
                pass


    @staticmethod
    def usage_header():
        print "SUSE Manager Database Control. Version", Console.VERSION
        print "Copyright (c) 2012 by SUSE Linux Products GmbH\n"


    def usage(self, command=None):
        """
        Print usage.
        """
        if command:
            help = self.gate.get_gate_commands().get(command)
            print >> sys.stdout, "Command:\n\t%s\n\nDescription:" % self.translate_command(command)
            print >> sys.stdout, "\t" + help.get('description', 'No description available') + "\n"
            if help.get('help'):
                print >> sys.stdout, "Parameters:"
                print >> sys.stdout, '\n'.join(["\t" + hl.replace("@nl", "") for hl in help.get('help').split("\n")]) + "\n"
            sys.exit(1)
        
        print >> sys.stderr, "Available commands:"

        index_commands = []
        longest = 0
        for command, description in self.gate.get_gate_commands().items():
            command = self.translate_command(command)
            index_commands.append(command)
            longest = longest < len(command) and len(command) or longest

        index_commands.sort()

        for command in index_commands:
            print >> sys.stderr, "\t", (command + ((longest - len(command)) * " ")), \
                "\t", self.gate.get_gate_commands().get(self.translate_command(command)).get('description', '')

        print >> sys.stderr, "\nTo know a complete description of each command, use parameter 'help'."
        print >> sys.stderr, "Usage:\n\tsmdba <command> help <ENTER>\n"


    def translate_command(self, command):
        """
        Translate from "do_something_like_this" as a method name
        to "something-like-this" for CLI. And vice versa.
        """

        return command.startswith("do_") and command[3:].replace("_", "-") or ("do_" + command.replace("-", "_"))


    def execute(self, command):
        """
        Execute one command.
        """
        if command[0].startswith('--'):
            self.execute_static(command)
        else:
            method = self.translate_command(command[0])
            if self.gate.get_gate_commands().get(method):
                args, params = self.get_opts(command[1:])
                if 'help' in args:
                    self.usage(command=method)
                params['__console_location'] = self.console_location
                self.gate.startup()
                getattr(self.gate, method)(*args, **params)
                self.gate.finish()
            else:
                raise Exception(("The parameter \"%s\" is an unknown command.\n\n"  % command[0]) + 
                                "Hint: Try with no parameters first, perhaps?")


    def execute_static(self, commands):
        """
        Execute static commands.
        """
        if commands[0] == '--help':
            self.usage()


    def get_opts(self, opts):
        """
        Parse --key=value params.
        """
        # When something more serious will be needed, standard lib might be used with more code. :)
        params = {}
        args = []
        for opt in opts:
            if opt.startswith("--"):
                opt = opt.split("=", 1)
                if len(opt) == 2:
                    params[opt[0][2:]] = opt[1]
                else:
                    raise Exception("Wrong argument: %s" % opt[0])
            else:
                args.append(opt)

        return args, params


def format_error(title, msg):
    """
    Format error on STDERR.
    """
    if not msg:
        return
    msg = str(msg)
    print >> sys.stderr, title + ":\n",
    for line in msg.split("\n"):
        print >> sys.stderr, "\t" + line.strip()
    print >> sys.stderr


def main():
    """
    Main app runner.
    """
    Console.usage_header()
    try:
        console = Console()
        if len(sys.argv[1:]) > 0:
            console.execute(sys.argv[1:])
        else:
            console.usage()
    except GateException, err:
        format_error("Backend error", err)
        sys.exit(1)
    except Exception, err:
        format_error("General error", err)
        sys.exit(1)



if __name__ == "__main__":
    process = Thread(target=main)
    process.start()

    while process.is_alive():
        try:
            time.sleep(0.1)
        except KeyboardInterrupt, err:
            inp = None
            print "\rCtrl+C? You are about to screw up everything!"
            while inp not in ['y', 'n']:
                try:
                    inp = str(raw_input("Can't wait and sure to break? (N/Y) ")).lower()
                except:
                    print "\nThey say hitting keyboard with a hammer might help too..."
                    inp = None
                if inp and inp not in ['y', 'n']:
                    print "I was asking you to press \"Y\" key or \"N\" key, but you chose \"Any key\"."
    
            if inp == 'n':
                print "Good. Don't Ctrl+C anymore!"
                continue
            else:
                print "\rOK, blame yourself."
                sys.exit(1)
