#!/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
        if hasattr(level, 'OPTPARAMS'):
            optparams = level.OPTPARAMS
        if hasattr(level, 'OPTPARAM'):
            optparam = level.OPTPARAM
        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)

    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)

    # execute it
    params = dict(optvals)

    if hasattr(level, 'DATA'):
        uri = papi.URI(params).format(level.DATA[0])
        key = level.DATA[1]
        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))


try:
    try:
        argv = global_optvals.parse(sys.argv[1:])
    except cmdline.OptionError,e:
        raise controllers.cmd.CommandError(str(e),controllers.isi,e.options)
    # there's only one of these right now, so no options needed
    conn.conn = conn.iface(debug=global_optvals['debug'],
            timeout=global_optvals.get('timeout'))

    if global_optvals['shell']:
        shell(controllers.isi)
    else:
        level = execute(controllers.isi, 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 : #
