#!/usr/libexec/isilon/isi

import sys
import os
import errno
import shlex
import cmdline
import opt
import papi.papi as papi
import papi.conn as conn
import papi.container as container
import controllers.cmd
import controllers.isi
import views.helpoutput as helpoutput

import legacy.classic as classic

check_opt = cmdline.Option(
  names=("check",),
  type=cmdline.Flag,
  doc=None)


OPTARGS = [
  opt.debug_opt,
  opt.shell_opt,
  opt.timeout_opt,
  check_opt,
  # --help option is implicitly added by cmdline.Options
  # --zsh option is implicitly added by cmdline.Options
  # --pdb option is implicitly added by cmdline.Options
]
OPTPARAMS = []

optargs = OPTARGS
optparams = OPTPARAMS
global_optvals = cmdline.Options(optargs, optparams)

class UsageError(Exception):

    def __init__(self):
        Exception.__init__(self, "Missing arguments")


def execute(level, argv, shell):
    global global_optvals
    classic_help = lambda arg: '-h' if arg == '--help' else arg
    noun_level = level
    old_level = level

    # locate NOUN [..NOUN] [VERB]
    i = 0
    while not level.terminal and i < len(argv) and argv[i][0] != '-':
        word = argv[i]
        i += 1

        classic_word = classic.substr_match(word, level)
        if hasattr(level, "ALIAS"):
            try:
                alias_word = level.ALIAS[word]
                if alias_word in level.CLASSIC:
                    classic_word = alias_word
            except KeyError:
                pass
        if classic_word is not None:
            classic_isi = level.CLASSIC.get(classic_word)
            if classic_isi:
                if os.geteuid() != 0:
                    raise Exception("Commands not enabled for role-based "\
                            "administration require root user access.")
                classic_args =  [classic_help(a) for a in argv[i:]]
                ret = classic.onefs.execute(\
                    ['isi'] + classic_isi.split(' ') + classic_args, True)
                if ret:
                    raise classic.ClassicError(ret)
                return noun_level

        level = controllers.cmd.locate(level, word)
        if not level.terminal:
            noun_level = level

    argv = argv[i:]
    optvals = global_optvals
    file_matching_keywords = None

    # parse its command line arguments
    if level.terminal:
        optargs = level.OPTARGS
        optparams = None
        optparam = None
        dep_params = None
        if hasattr(level, 'OPTPARAMS'):
            optparams = level.OPTPARAMS
        if hasattr(level, 'OPTPARAM'):
            optparam = level.OPTPARAM
        if hasattr(level, 'DEPRECATED_PARAMS'):
            dep_params = level.DEPRECATED_PARAMS
        if hasattr(level, 'SORTS'):
            optargs.append(opt.sort_opt(level.SORTS))
            optargs.append(opt.sort_desc_opt)
        if hasattr(level, 'FILE_MATCHING_KEYWORDS'):
            file_matching_keywords = level.FILE_MATCHING_KEYWORDS
        optvals = cmdline.Options(optargs, optparams, optparam,
                file_matching_keywords=file_matching_keywords,
                deprecated_params=dep_params)

    try:
        argv = optvals.parse(argv)
    except cmdline.OptionError,e:
        raise controllers.cmd.CommandError(str(e),level,e.options)
    h = controllers.cmd.get_help(level, optvals)
    fmt = helpoutput.HelpFormatter(h)

    if 'zsh' in optvals:
        fmt.zsh()
        return noun_level

    if 'help' in optvals:
        fmt.full_help(shell)
        return noun_level

    if not level.terminal:
        if 'check' in optvals:
            check(level)
        else:
            fmt.full_help(shell)
            if not shell:
                raise UsageError()
        return level

    if argv:
        raise controllers.cmd.CommandError(\
            "extraneous command line arguments {0}".format(argv),
            level,optvals)

    # issue deprecated usage warning if neccessary
    if optvals.used_deprecated_params():
        warn = 'Note: the command syntax "{0} {1}" has been deprecated.\n'\
               'Run "{0} --help" to view the current syntax.\n'.\
               format(" ".join(level.__name__.split(".")[1:]),\
               " ".join(['<' + p + '>' for p in level.DEPRECATED_PARAMS]))
        sys.stderr.write(warn)

    # execute it
    params = dict(optvals)

    if hasattr(level, 'DATA'):
        uri = papi.URI(params).format(level.DATA[0])
        key = level.DATA[1]
        if len(level.DATA) > 2 and level.DATA[2].upper() == "RAN":
	    conn.conn = conn.RAN_IFACE(debug=global_optvals['debug'],
                timeout=global_optvals.get('timeout'))

	    data = container.RANData(conn.conn, uri, key)
        else:
	    conn.conn = conn.iface(debug=global_optvals['debug'],
                timeout=global_optvals.get('timeout'))
	    data = container.PAPIData(conn.conn, uri, key)
    else:
        data = None

    try:
        if 'pdb' in optvals:
            import pdb
            del params['pdb']
            pdb.set_trace()
        level.execute(data, **params)
    except opt.NoArgumentError as e:
        raise controllers.cmd.CommandError(str(e), level, optvals)
    except opt.UsageMessageError as e:
        raise controllers.cmd.CommandError(str(e), level, optvals)
    if data is not None and \
      (data.last_id is not None or old_level != noun_level):
        opt.id_opt.default = data.last_id
    return noun_level


def prompt_string(level):
    if opt.id_opt.default is not None:
        prompt = '{name} ({default_id})* '
    else:
        prompt = '{name}* '

    return  prompt.format(name = level.__name__.split('.')[-1],
                          default_id = opt.id_opt.default);


def shell(level):
    while True:
        try:
            cmdline = raw_input(prompt_string(level))
            argv = shlex.split(cmdline)
            if not argv:
                level = controllers.cmd.up(level)
            else:
                try:
                    level = execute(level, argv, True)

                except Exception, e:
                    print e

        except EOFError:
            break


def check(level):
    if not hasattr(level, 'TEST'):
        print "({0} has no test script)".format(level.__name__)
    else:
        try:
            for cmdline in level.TEST:
                print prompt_string(level) + cmdline
                argv = shlex.split(cmdline)
                execute(level, argv, False)
        except:
            print "FAIL: {0}".format(level.__name__)
            raise

    opt.id_opt.default = None

    print "PASS: {0}".format(level.__name__)

    if hasattr(level, 'NOUNS'):
        for noun in level.NOUNS:
            if hasattr(level, "CLASSIC") and noun in level.CLASSIC:
                continue
            check(controllers.cmd.locate(level, noun))

def run_cli(module):
    try:
        try:
            argv = global_optvals.parse(sys.argv[1:])
        except cmdline.OptionError,e:
            raise controllers.cmd.CommandError(str(e), module, e.options)

        if global_optvals['shell']:
            shell(controllers.isi)
        else:
            level = execute(module, argv, False)

    except KeyboardInterrupt:
        print "Interrupted"
        sys.exit(130)

    except IOError, e:
        if e.errno == errno.EPIPE:
            # pipe closed, quit gracefully
            sys.exit(0)
        elif global_optvals['debug']:
            raise

        sys.exit(e.errno)

    except classic.ClassicError as e:
        if global_optvals['debug']:
            raise

        sys.exit(e.code)

    except UsageError as e:
        sys.exit(2)

    except controllers.cmd.CommandError as e:
        level = e.level
        optvals = e.options
        h = controllers.cmd.get_help(level,optvals)
        fmt = helpoutput.HelpFormatter(h)
        command = " ".join(level.__name__.split(".")[1:])
        sys.stderr.write(str(e[0]).rstrip() + "\n")
        old_stdout = sys.stdout
        sys.stdout = sys.stderr
        fmt.minimal_help()
        fmt.show_file_matching_criteria()
        print "See '{0} --help' for more information.".format(command)
        sys.stdout = old_stdout
        sys.exit(2)

    except Exception as e:
        err_msg = str(e) or repr(e)
        sys.stderr.write(err_msg + "\n")

        if global_optvals['debug']:
            raise

        sys.exit(1)

    # vim: set filetype=python : #

if __name__ == '__main__':
    run_cli(controllers.isi)
