#!/usr/bin/env python
# Copyright (C) 2012-2013  Peter Hatina <phatina@redhat.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; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.

import os
import sys
import code
import copy
import pywbem
import readline
import urlparse
import rlcompleter
import __builtin__
from lmi.lmi_client_base import LmiReturnValue
from lmi.lmi_client_base import LmiBaseClient
from lmi.lmi_client_shell import LmiConnection
from lmi.lmi_client_shell import _LmiNamespace
from lmi.lmi_client_shell import _LmiNamespaceRoot
from lmi.lmi_client_shell import _LmiClass
from lmi.lmi_client_shell import _LmiInstance
from lmi.lmi_client_shell import _LmiInstanceName
from lmi.lmi_client_shell import _lmi_delete_indication
from lmi.lmi_client_shell import _lmi_start_indication_listener
from lmi.lmi_client_shell import _lmi_stop_indication_listener
from lmi.lmi_client_shell import _lmi_print_indications
from lmi.lmi_client_shell import _lmi_print_indication_listeners
from lmi.lmi_client_shell import _lmi_indications_list
from lmi.lmi_client_shell import _lmi_indication_listeners_list

def get_item_or_default(dic, item, default):
    return dic[item] if item in dic else default

def _lmi_displayhook(o):
    if o is None:
        return
    __builtin__._ = None
    if isinstance(o, LmiReturnValue) and o.use_display_hook:
        o.use_display_hook = False
        if (type(o.rval) == int and o.rval == 0) or (type(o.rval) == bool and o.rval == True) \
            or (o.rval == 4096 and o.instance_classname == "LMI_PowerManagementService"):
            result_str = "ok"
        else:
            result_str = "fail"
        sys.stdout.write("%s: %s (%s)" % (o.hostname, result_str, str(o.rval)))
        if o.errorstr:
            sys.stdout.write(": %s" % o.errorstr)
        sys.stdout.write("\n")
    else:
        print o
    __builtin__._ = o

sys.displayhook = _lmi_displayhook

class _LmiShellOptions(object):
    def __init__(self, argv):
        self._argv = argv
        self._script_name = ""
        self._script_argv = []
        self._config_file = ""
        self._good = True
        self._inspect = False
        self._help = False
        for a in self._argv[1:]:
            if not self._script_name and a in ("-h", "--help"):
                self._help = True
                return
            elif not self._script_name and a in ("-i", "--inspect"):
                self._inspect = True
            elif not self._script_name and not a.startswith("-"):
                self._script_name = a
            elif self._script_name:
                self._script_argv.append(a)
            else:
                self._good = False
                break
    @property
    def config_file(self):
        return self._config_file

    @property
    def good(self):
        return self._good

    @property
    def help(self):
        return self._help

    @property
    def interactive(self):
        return not self._script_name

    @property
    def inspect(self):
        return self._inspect

    @property
    def script_name(self):
        return self._script_name

    @property
    def script_args(self):
        return [self._script_name] + self._script_argv

    def print_usage(self):
        sys.stdout.write("Usage: %s [options] [script] [script-options]\n" % os.path.basename(self._argv[0]))
        sys.stdout.write("\nOptions:\n")
        sys.stdout.write("  -h, --help            print this message\n")
        sys.stdout.write("  -i, --interact        inspect interactively after running a script\n")

class _LmiCompleter(rlcompleter.Completer):
    def __init__(self, namespace = None):
        rlcompleter.Completer.__init__(self, namespace)
        self._last_complete = []

    def __complete_object_methods(self, obj, text):
        result = filter(lambda m: not m.startswith("_") \
            and callable(getattr(obj, m)) \
            and m.lower().startswith(text.lower()), dir(obj))
        return result

    def __complete_object_properties(self, obj, text):
        result = filter(lambda m: not m.startswith("_") \
            and not callable(getattr(obj, m)) \
            and m.lower().startswith(text.lower()), dir(obj))
        return result

    def _callable_postfix(self, val, word):
        if hasattr(val, '__call__') \
            and not isinstance(val, (_LmiNamespace, _LmiClass, _LmiInstance, _LmiInstanceName, LmiReturnValue)):
            word = word + "("
        return word

    def complete(self, text, state):
        if not text:
            return ("\t", None)[state]
        if state > 0:
            return self._last_complete[state]
        self._last_complete = []
        members = text.split(".")
        if members[0] in self.namespace:
            cmd = ".".join(members[0:-1])
            to_complete = members[-1]
            expr = eval(cmd, self.namespace)
            methods = self.__complete_object_methods(expr, to_complete)
            properties = self.__complete_object_properties(expr, to_complete)
            if isinstance(expr, (LmiConnection, _LmiNamespaceRoot)):
                for n in expr.namespaces:
                    if n.lower().startswith(to_complete.lower()):
                        self._last_complete.append(cmd + "." + n)
                methods = [x for x in methods if x not in expr.namespaces]
            elif isinstance(expr, _LmiNamespace):
                for c in expr.classes():
                    if c.lower().startswith(to_complete.lower()):
                        self._last_complete.append(cmd + "." + c)
            elif isinstance(expr, _LmiInstance):
                for m in expr.methods():
                    if m.lower().startswith(to_complete.lower()):
                        self._last_complete.append(cmd + "." + m + "(")
                for p in expr.properties():
                    if p.lower().startswith(to_complete.lower()):
                        self._last_complete.append(cmd + "." + p)
            elif isinstance(expr, LmiReturnValue):
                for p in expr.properties():
                    if p.lower().startswith(to_complete.lower()):
                        self._last_complete.append(cmd + "." + p)
            self._last_complete.extend(cmd + "." + m + "(" for m in methods)
            self._last_complete.extend(cmd + "." + p for p in properties)
            return self._last_complete[state]
        return rlcompleter.Completer.complete(self, text, state)

class LmiInteractiveShellConfig(object):
    DEFAULT_CONFIG_FILE = "~/.lmishellrc"
    DEFAULT_HISTORY_FILE = "~/.lmi_shell_history"
    DEFAULT_HISTORY_LENGTH = -1
    DEFAULT_USE_CACHE = True

    def __init__(self):
        try:
            conf = {}
            execfile(os.path.expanduser(LmiInteractiveShellConfig.DEFAULT_CONFIG_FILE), conf)
            self._history_file = os.path.expanduser(get_item_or_default(conf, "history_file",
                LmiInteractiveShellConfig.DEFAULT_HISTORY_FILE))
            self._history_length = get_item_or_default(conf, "history_length",
                LmiInteractiveShellConfig.DEFAULT_HISTORY_LENGTH)
            self._use_cache = get_item_or_default(conf, "use_cache",
                LmiInteractiveShellConfig.DEFAULT_USE_CACHE)
        except (SyntaxError, IOError), e:
            if isinstance(e, SyntaxError):
                sys.stderr.write("Error: %s\n" % e)
            self._history_file = os.path.expanduser(LmiInteractiveShellConfig.DEFAULT_HISTORY_FILE)
            self._history_length = LmiInteractiveShellConfig.DEFAULT_HISTORY_LENGTH
            self._use_cache = LmiInteractiveShellConfig.DEFAULT_USE_CACHE

    @property
    def history_file(self):
        return self._history_file

    @property
    def history_length(self):
        return self._history_length

    @property
    def use_cache(self):
        return self._use_cache

class LmiInteractiveShell(code.InteractiveConsole):
    DEFAULT_LOCALS = {
        "LmiConnection" : LmiConnection,
        "use_exceptions" : LmiBaseClient._set_use_exceptions,
        "delete_indication" : _lmi_delete_indication,
        "start_indication_listener" : _lmi_start_indication_listener,
        "stop_indication_listener" : _lmi_stop_indication_listener,
        "print_indications" : _lmi_print_indications,
        "print_indication_listeners" : _lmi_print_indication_listeners,
        "indications" : _lmi_indications_list,
        "indication_listeners" : _lmi_indication_listeners_list
    }

    def __init__(self, prompt, more_prompt, locals = None, use_cache = True):
        sys.ps1 = prompt
        sys.ps2 = more_prompt
        if locals is None:
            locals = {}
        else:
            for (k, v) in locals.iteritems():
                if isinstance(v, LmiConnection):
                    locals[k]._client.interactive = True
        locals = dict(locals.items() + LmiInteractiveShell.DEFAULT_LOCALS.items())
        locals["clear_history"] = self.clear_history
        locals["use_display_sugar"] = _use_display_sugar
        code.InteractiveConsole.__init__(self, locals)
        self._completer = _LmiCompleter(self.locals)
        config = LmiInteractiveShellConfig()
        self._history_file = config.history_file
        self._history_length = config.history_length
        self._use_cache = config.use_cache
        locals["connect"] = lambda h, u = "", p = "": _connect(h, u, p, True, self._use_cache)
        readline.set_completer(self._completer.complete)
        readline.parse_and_bind('tab: complete')

    def interact(self):
        self.load_history()
        try:
            sys.ps1
        except AttributeError:
            sys.ps1 = ">>> "
        try:
            sys.ps2
        except AttributeError:
            sys.ps2 = "... "
        more = 0
        while 1:
            try:
                if more:
                    prompt = sys.ps2
                else:
                    prompt = sys.ps1
                try:
                    line = self.raw_input(prompt)
                    # Can be None if sys.stdin was redefined
                    encoding = getattr(sys.stdin, "encoding", None)
                    if encoding and not isinstance(line, unicode):
                        line = line.decode(encoding)
                except EOFError:
                    self.write("\n")
                    break
                else:
                    more = self.push(line)
            except KeyboardInterrupt:
                self.write("\n")
                self.resetbuffer()
                more = 0
        self.save_history()

    def load_history(self):
        if self._history_length == 0 or not os.path.exists(self._history_file):
            return
        readline.read_history_file(self._history_file)
        if self._history_length > 0 and readline.get_current_history_length() > self._history_length:
            readline.set_history_length(length)
            readline.write_history_file(self._history_file)
            readline.read_history_file(self._history_file)

    def save_history(self):
        if self._history_length == 0:
            return
        elif self._history_length > 0:
            readline.set_history_length(self._history_length)
        try:
            readline.write_history_file(self._history_file)
        except IOError, e:
            pass

    def clear_history(self):
        readline.clear_history()

def __lmi_raw_input(prompt, use_echo = True):
    if not use_echo:
        os.system("stty -echo")
    try:
        result = raw_input(prompt)
    except EOFError, e:
        if not use_echo:
            os.system("stty echo")
        sys.stdout.write("\n")
        return None
    except KeyboardInterrupt, e:
        if not use_echo:
            os.system("stty echo")
        raise
    if not use_echo:
        os.system("stty echo")
        sys.stdout.write("\n")
    if result:
        cur_hist_len = readline.get_current_history_length()
        readline.remove_history_item(cur_hist_len - 1)
    return result

def _lmi_interact(locals = None):
    console = LmiInteractiveShell("> ", "... ", locals)
    console.interact()

def _connect(hostname, username = "", password = "", interactive = False, use_cache = True):
    connection = None
    netloc = urlparse.urlparse(hostname).netloc
    destination = netloc if netloc else hostname
    if os.getuid() == 0 and destination in ("localhost", "127.0.0.1", "::1") and \
        os.path.exists("/var/run/tog-pegasus/cimxml.socket") and \
        not username and not password:
        connection = LmiConnection(hostname, None, None, interactive,
            use_cache, LmiBaseClient.CONN_TYPE_PEGASUS_UDS)
        if not connection.verify_credentials():
            connection = None
    if connection is None:
        try:
            if not username:
                username = __lmi_raw_input("username: ", True)
            if not password:
                password = __lmi_raw_input("password: ", False)
        except KeyboardInterrupt, e:
            sys.stdout.write("\n")
            return None
        connection = LmiConnection(hostname, username, password, interactive,
            use_cache, LmiBaseClient.CONN_TYPE_WBEM)
        if not connection.verify_credentials():
            return None
    return connection

def _use_display_sugar(use = True):
    sys.displayhook = _lmi_displayhook if use else sys.__displayhook__

if __name__ == "__main__":
    options = _LmiShellOptions(sys.argv)
    if not options.good:
        sys.stderr.write("Wrong tool usage!\n\n")
        options.print_usage()
        sys.exit(1)

    if options.help:
        options.print_usage()
        sys.exit(0)

    if options.interactive:
        _lmi_interact()
    else:
        config = LmiInteractiveShellConfig()
        use_cache = config.use_cache
        locals = copy.deepcopy(LmiInteractiveShell.DEFAULT_LOCALS)
        locals["connect"] = lambda h, u = "", p = "": _connect(h, u, p, False, use_cache)
        sys.argv = options.script_args
        try:
            execfile(options.script_name, locals)
        except SystemExit, e:
            if not options.inspect:
                sys.exit(e.code)
        if options.inspect:
            _lmi_interact(locals)
    sys.exit(0)
