#!/usr/bin/env python
#coding=utf8

'''
CloudControl CLI command module
'''

import os, os.path
import sys
import re
import pprint
import ConfigParser
import code
from optparse import OptionParser

import cccli
from cccli.exception import *
from cccli.printer import color

from sjrpc.client import SimpleRpcClient
from sjrpc.utils import ConnectionProxy
from sjrpc.core.exceptions import *

class Command(object):

    def __init__(self, argv, cli):
        # check argv
        if len(argv) == 0:
            raise cmdBadName()
        # check valid command chars
        if not re.match("^[a-zA-Z0-9]+", argv[0]):
            raise cmdBadName()
        cmdlist = [ x[4:] for x in dir(self) if x.startswith("cmd_") ]
        matchlist = [ x for x in cmdlist if re.match("%s.+"%argv[0], x) ]
        if argv[0] in cmdlist:
            pass
        elif len(matchlist) == 1:
            argv[0] = matchlist[0]
        else:
            raise cmdBadName()
        self._cmd = getattr(self, "cmd_%s"%argv[0])
        self._argv = argv
        self.cli = cli
        self.printer = cli.printer

    @classmethod
    def usage(cls, cmdname):
        '''Return usage of a command'''
        fname = "cmd_%s"%cmdname
        if not hasattr(cls, fname):
            raise cmdBadName(cmdname)
        if hasattr(getattr(cls, fname), "usage"):
            return getattr(getattr(cls, fname), "usage")
        return ""

    def call(self):
        '''Run command'''
        if re.match("^[a-zA-Z0-9]+$", self._argv[0]):
            name = "cmd_%s"%self._argv[0]
            if hasattr(self, name):
                cmd = getattr(self, name)
                return cmd(self._argv)
        raise cmdBadName(self._argv[0])

    def cmd_exit(self, argv):
        '''Quit application with respect'''
        raise SystemExit()
    cmd_exit.usage = "exit"
    cmd_exit.desc = "Quit application with respect"

    def cmd_quit(self, argv):
        '''Quit application with respect'''
        raise SystemExit()
    cmd_quit.usage = "quit"
    cmd_quit.desc = "Quit application with respect"

    def cmd_version(self, argv):
        '''Print cli version'''
        self.printer.out(cccli.version)
    cmd_version.usage = "version"
    cmd_version.desc = "Print cli version"

    def cmd_usage(self, argv):
        '''Print usage of a command'''
        if len(argv) != 2:
            raise cmdBadArgument()
        usage = Command.usage(argv[1])
        if usage != "":
            self.printer.out("usage: %s"%usage)
        else:
            self.printer.out("No usage.")
    cmd_usage.usage = "usage [command]"
    cmd_usage.desc = "Print usage of a command"

    def cmd_help(self, argv):
        '''Print help'''
        if len(argv) == 1:
            # build command list
            cmdlist = list()
            doclist = list()
            for x in dir(self):
                m = re.match("^cmd_([a-zA-Z0-9]+)$", x)
                if m:
                    cmdlist.append(m.group(1))
                    if hasattr(getattr(self, x), "__doc__"):
                        doclist.append(getattr(getattr(self, x), "__doc__"))
            # printing commands list
            width = max(map(len, cmdlist)) + 3
            self.printer.out("%sCommands:%s"%(color["lwhite"], color["reset"]))
            for c, d in zip(cmdlist, doclist):
                line = "%s"%c
                line = line.ljust(width,)
                line += "- %s"%d
                self.printer.out(line)
        elif len(argv) == 2:
            fname = "cmd_%s"%argv[1]
            if hasattr(self, fname):
                if hasattr(getattr(self, fname), "__doc__"):
                    self.printer.out("Description: %s"%getattr(getattr(self, fname), "__doc__"))
                if hasattr(getattr(self, fname), "usage"):
                    self.printer.out("Usage: %s"%getattr(getattr(self, fname), "usage"))
                if hasattr(getattr(self, fname), "details"):
                    Self.Printer.out("Details: %s"%getattr(getattr(self, fname), "details"))
            else:
                raise cmdBadArgument(argv[1])
        else:
            raise cmdBadArgument()
    cmd_help.usage = "help [command]"
    cmd_help.desc = "Print help about a command"

    def cmd_alias(self, argv):
        '''Show or create alias'''
        if len(argv) == 1:
            for n, v in self.cli.alias.items():
                self.printer.out("%s=%s"%(n, v))
        elif len(argv) == 2:
            if argv[1] not in self.cli.alias:
                raise cmdBadArgument(argv[1])
            self.printer.out("%s=%s"%(argv[1], self.cli.alias[argv[1]]))
        elif len(argv) == 3:
            self.cli.alias[argv[1]] = argv[2]
            self.cli.alias.save(self.cli.settings.get("alias", ""))
        else:
            raise cmdBadArgument()
    cmd_alias.usage = "alias [name] [value]"
    cmd_alias.desc = "Show or create aliases"

    def cmd_unalias(self, argv):
        '''Remove an alias'''
        if len(argv) != 2:
            raise cmdBadArgument()
        if argv[1] not in self.cli.alias:
            raise cmdBadArgument("%s: No such alias"%argv[1])
        del self.cli.alias[argv[1]]
        self.cli.alias.save(self.cli.settings.get("alias", ""))
    cmd_unalias.usage = "unalias [name]"
    cmd_unalias.desc = "Remove an aliases"

    def cmd_rcmd(self, argv):
        '''Show remote commands'''
        for cmds in self.cli.rpc.call("list_commands"):
            self.printer.out("%s"%cmds["name"])
    cmd_rcmd.usage = "rcmd"
    cmd_rcmd.desc = "Print remote command list"

    def cmd_history(self, argv):
        '''Show history of commands'''
        if not self.printer.history:
            raise cmdError("not available")
        for l in self.printer.history:
            self.printer.out(l)
    cmd_history.usage = "history"
    cmd_history.desc = "Show commands history"

    def cmd_list(self, argv):
        '''List objects'''
        try:
            oparser = OptionParser(prog="list")
            oparser.add_option("-t", action="store_true", dest="table")
            (options, args) = oparser.parse_args(argv[1:])
        except SystemExit:
            return
        if len(args) == 0:
            args.append("a")
        try:
            objs = self.cli.rpc.call("list", str.join("", args))
        except RpcError as e:
            raise cmdError("RPCError: %s"%str(e))
        if len(objs) == 0:
            return
        if options.table:
            self._list_table(objs)
        else:
            self._list(objs)
    cmd_list.usage = "list [-t] [--help] tql"

    def _list(self, objs):
        for o in objs:
            id = o.pop("id")
            tags = " ".join([ "%s%s:%s%s"%(color["green"], t, color["reset"], v) for (t,v) in o.items() ])
            self.printer.out("%sid:%s%s%s %s"%(color["green"], color["yellow"], id, color["reset"], tags))

    def _list_table(self, objs):
        # get all tag list
        tags = dict()
        for o in objs:
            for t,v in o.items():
                tags[t] = max(len(str(v)), tags.get(t, len(str(t))))
        # extract id info
        idsize = tags.pop("id")
        # print titles
        self.printer.out(color["green"], nl="")
        self.printer.out("id".ljust(idsize+1), nl=" ")
        for t,v in tags.items():
            self.printer.out(t.ljust(v), nl=" ")
        self.printer.out(color["reset"])
        # print obj
        for obj in objs:
            self.printer.out("%s%s%s"%(color["yellow"], obj.pop("id").ljust(idsize+1), color["reset"]) ,nl=" ")
            for (t, v) in tags.items():
                self.printer.out(str(obj.get(t, "")).ljust(v) ,nl=" ")
            self.printer.out()

    def _vm_action(self, argv, filters=None):
        '''All command about vm are the same'''
        try:
            oparser = OptionParser(prog=argv[0])
            oparser.add_option("--raw", action="store_true", dest="raw",
                               help="Don't append filter on request")
            oparser.add_option("--direct", action="store_true", dest="direct",
                               help="Directly send tql to server (don't list before)")
            oparser.add_option("--force", action="store_true", dest="force",
                               help="Don't ask confirmation (Dangerous)")
            (options, args) = oparser.parse_args(argv[1:])
        except SystemExit:
            return
        if len(args) == 0:
            raise cmdBadArgument()
        tql = str.join("", args)
        # append securty options by command name
        if filters is not None and not options.raw:
            tql += filters
        if options.direct:
            try:
                objs = self.cli.rpc.call(argv[0], tql)
            except RpcError as e:
                raise cmdError("RPCError: %s"%str(e))
        else:
            # get objects id
            try:
                objs = self.cli.rpc.call("list", tql)
            except RpcError as e:
                raise cmdError("RPCError: %s"%str(e))
            # no result, goodbye
            if len(objs) == 0:
                raise cmdWarning("tql: '%s': No result."%tql)
            self.printer.out("You will %s:"%argv[0])
            for obj in objs:
                self.printer.out("%sid:%s%s%s"%(color["green"],color["yellow"],obj["id"],color["reset"]))
            self.printer.out("%sCount: %s%s"%(color["green"],color["reset"], len(objs)))
            # be sure boby want do that
            if not options.force:
                self.printer.out("%sProceed?%s"%(color["lred"], color["reset"]))
                if self.printer.ask("Answer (yes/NO): ") != "yes":
                    raise cmdWarning("Aborted")
                if len(objs) > 5:
                    self.printer.out("%sYou request is on more than 5 objets!%s"%(color["yellow"], color["reset"]))
                    self.printer.out("%sAre you really sure?%s"%(color["lred"], color["reset"]))
                    if self.printer.ask("Answer (Sir, yes Sir!): ") != "Sir, yes Sir!":
                        raise cmdWarning("Bad Answer. Aborted")
            # execute action for each object
            for obj in objs:
                self.printer.out("%s%s%s %s%s%s"%(color["lblue"],
                                                  argv[0],
                                                  color["reset"],
                                                  color["yellow"],
                                                  obj["id"],
                                                  color["reset"]))
                try:
                    self.cli.rpc.call(argv[0], "id:%s"%obj["id"])
                except RpcError as e:
                    self.printer.error("RPCError: %s"%str(e))

    def cmd_start(self, argv):
        '''Start objects'''
        self._vm_action(argv, "&role=vm&status=stopped")
    cmd_start.usage = "start [--raw] [--direct] [--force] [--help] tql"

    def cmd_stop(self, argv):
        '''Stop objects'''
        self._vm_action(argv, "&role=vm&status=running")
    cmd_stop.usage = "stop [--raw] [--direct] [--force] [--help] tql"

    def cmd_pause(self, argv):
        '''Pause objects'''
        self._vm_action(argv, "&role=vm&status=running")
    cmd_pause.usage = "pause [--raw] [--direct] [--force] [--help] tql"

    def cmd_resume(self, argv):
        '''Resume objects'''
        self._vm_action(argv, "&role=vm&status=stalled")
    cmd_resume.usage = "resume [--raw] [--direct] [--force] [--help] tql"

    def cmd_destroy(self, argv):
        '''Force objects to stop'''
        self._vm_action(argv, "&role=vm&status!=stopped")
    cmd_destroy.usage = "destroy [--raw] [--direct] [--force] [--help] tql"

    def cmd_clear(self, argv):
        '''Clear tty'''
        self.printer.out("\033[H\033[2J", nl="")
    cmd_clear.usage = "clear"

    def cmd_uptime(self, argv):
        '''Show connection uptime'''
        if len(argv) == 1:
            argv.append(self.cli.settings["login"])
        tql = "a~(%s)$con"%"|".join(argv[1:])
        try:
            objs = self.cli.rpc.call("list",tql)
        except RpcError as e:
            raise cmdError("RPCError: %s"%str(e))
        for o in objs:
            self.printer.out("%s: %ss"%(o["a"],o["con"]))
    cmd_uptime.usage = "uptime [login]"

    def cmd_tags(self, argv):
        '''List static tags on an account (current by default)'''
        if len(argv) == 1:
            argv.append(self.cli.settings["login"])
        for a in argv[1:]:
            try:
                tl = self.cli.rpc.call("tags", a)
                tags = " ".join([ "%s%s:%s%s"%(color["green"], t, color["reset"], v) for (t,v) in tl.items() ])
                self.printer.out("%sa:%s%s%s %s"%(color["green"], color["yellow"], a, color["reset"], tags))
            except RpcError as e:
                raise cmdError("RPCError: %s"%str(e))
    cmd_tags.usage = "tags [account]"

    def cmd_addtag(self, argv):
        '''Add/Modify a static tag on an account'''
        if len(argv) != 4:
            raise cmdBadArgument()
        try:
            self.cli.rpc.call("addtag", argv[1], argv[2], argv[3])
        except RpcError as e:
            raise cmdError("RPCError: %s"%str(e))
    cmd_addtag.usage = "addtag <account> <tag> <value>"

    def cmd_deltag(self, argv):
        '''Delete a static tag from an account'''
        if len(argv) != 3:
            raise cmdBadArgument()
        try:
            self.cli.rpc.call("deltag", argv[1], argv[2])
        except RpcError as e:
            raise cmdError("RPCError: %s"%str(e))
    cmd_deltag.usage = "deltag <account> <tag>"

    def cmd_rights(self, argv):
        '''List account rights (current by default)'''
        # Parse argline
        try:
            oparser = OptionParser(prog=argv[0])
            oparser.add_option("--raw", action="store_true", dest="raw",
                               help="Don't append filter on request")
            (options, args) = oparser.parse_args(argv[1:])
        except SystemExit:
            return
        # append current login if nothing asked
        if len(args) == 0:
            tql = "a=%s"%self.cli.settings["login"]
        else:
            tql = "".join(args)
        # update tql if mode
        if not options.raw:
            tql += "&a"
        # ask server
        try:
            al = self.cli.rpc.call("rights", tql)
        except RpcError as e:
            raise cmdError("RPCError: %s"%str(e))
        # display answer
        for (a, rl) in al.items():
            self.printer.out("%srights of %s%s"%(color["lblue"], a, color["reset"]))
            for i,r in enumerate(rl):
                tags = " ".join([ "%s%s:%s%s"%(color["green"], t, color["reset"], v) for (t,v) in r.items() ])
                self.printer.out("[%s] %s"%(i,tags))
    cmd_rights.usage = "rights [--raw] [--help] [tql]"

    def cmd_addright(self, argv):
        '''Add/edit a right'''
        if len(argv) != 5:
            raise cmdBadArgument()
        try:
            self.cli.rpc.call("addright", argv[1], argv[2], argv[3], argv[4])
        except RpcError as e:
            raise cmdError("RPCError: %s"%str(e))
    cmd_addright.usage = "addright <account> <right tql> <method> <allow>"

    def cmd_delright(self, argv):
        '''Delete a right'''
        if len(argv) != 3:
            raise cmdBadArgument()
        try:
            self.cli.rpc.call("delright", argv[1], int(argv[2]))
        except RpcError as e:
            raise cmdError("RPCError: %s"%str(e))
    cmd_delright.usage = "delright <account> <index>"

    def cmd_addaccount(self, argv):
        '''Create an account'''
        if len(argv) != 3:
            raise cmdBadArgument()
        try:
            self.cli.rpc.call("addaccount", argv[1], argv[2])
        except RpcError as e:
            raise cmdError("RPCError: %s"%str(e))
    cmd_addaccount.usage = "addaccount <account> <role>"

    def cmd_delaccount(self, argv):
        '''Delete an account'''
        if len(argv) != 2:
            raise cmdBadArgument()
        try:
            self.cli.rpc.call("delaccount", argv[1])
        except RpcError as e:
            raise cmdError("RPCError: %s"%str(e))
    cmd_delaccount.usage = "delaccount <account>"

    def cmd_passwd(self, argv):
        '''Change account password'''
        if len(argv) == 1:
            argv.append("a=%s"%self.cli.settings["login"])
        if len(argv) == 2:
            if not self.cli.interactive:
                raise cmdError("You must give a password argument in non interactive mode!")
            a = self.printer.getpass("Password: ")
            b = self.printer.getpass("Again: ")
            if a != b:
                raise cmdError("You don't type twice the same password. Aborted")
            argv.append(a)
        elif len(argv) == 3:
            if self.cli.interactive:
                s = "You cannot set password with clear argument in interactive mode.\n"
                s += "*** Think to clean your history! ***"
                raise cmdError(s)
        else:
            raise cmdBadArgument()
        try:
            self.cli.rpc.call("passwd", argv[1], argv[2])
        except RpcError as e:
            raise cmdError("RPCError: %s"%str(e))
    cmd_passwd.usage = "passwd [tql] [password]"

    def cmd_expert(self, argv):
        '''Switch in expert mode'''
        h = list(self.printer.history)
        self.printer.history.read(self.cli.settings.get("expert", ""))
        try:
            local = dict()
            local["cli"] = self.cli
            local["rpc"] = self.cli.rpc
            local["proxy"] = ConnectionProxy(self.cli.rpc)
            c = code.InteractiveConsole(local)
            c.interact("Use Ctrl+D to go back in CLI. Type dir() to see variables.")
        finally:
            self.printer.history.write(self.cli.settings.get("expert", ""))
            self.printer.history.load(h)
    cmd_expert.usage = "expert"

    def cmd_whoami(self, argv):
        '''Show connection login'''
        self.printer.out(self.cli.settings["login"])
    cmd_whoami.usage = "whoami"


class Alias(dict):
    ''' Alias wrapper'''
    def load(self, filename):
        '''load alias from file'''
        if os.access(filename, os.R_OK):
            fparser = ConfigParser.RawConfigParser()
            fparser.read(filename)
            if fparser.has_section("alias"):
                self.clear()
                self.update(fparser.items("alias"))

    def save(self, filename):
        '''save alias on file'''
        if os.access(filename, os.R_OK or os.W_OK):
            fparser = ConfigParser.RawConfigParser()
            fparser.read(filename)
            fparser.remove_section("alias")
            fparser.add_section("alias")
            for n,v in self.items():
                fparser.set("alias", n, v)
            fparser.write(open(filename, "w"))