#!/usr/bin/python3
# Copyright 2011-2018 Red Hat, Inc.
#
# 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Refer to the README and COPYING files for full details of the license
#
from __future__ import absolute_import
from __future__ import print_function

import getopt
import importlib
import logging
import os
import sys
import syslog
import textwrap
import traceback

import vdsm.tool


tool_modules = []
tool_command = {}

ERROR_SAME_COMMAND = """\
Warning: the command '%s' in module '%s' is being ignored because a command \
with the same name has already been registered by module '%s'.\
"""


def _listPathModules(path):
    modules = set()
    for f in os.listdir(path):
        base, ext = os.path.splitext(f)
        if ext in ('.py', '.pyc', '.pyo'):
            modules.add(base)
    return sorted(modules)


def load_modules():
    """
    Dynamically load the modules present in the tool package.

    This means that vdsm-tool doesn't need to know in advance the modules
    and the commands present in the package. The commands to be exposed in
    the command line must be decorated with:

        from vdsm.tool import expose
        @expose(command_name)

    Two global structures are maintained:

        tool_modules: an ordered list of modules and functions whose main
                      purpose is to be used for the help output
        tool_command: a lookup dictionary of the commands useful to execute
                      the commands when requested
    """
    global tool_modules, tool_command

    mod_path = os.path.dirname(vdsm.tool.__file__)
    package_name = vdsm.tool.__name__

    for mod_name in _listPathModules(mod_path):
        if mod_name.startswith("_"):
            continue

        try:
            module = importlib.import_module(package_name + '.' + mod_name)
        except Exception:
            # py module was failed to load. report to syslog and continue
            syslog.syslog("module %s could not load to vdsm-tool: %s" %
                          (mod_name, traceback.format_exc()))
            continue

        mod_cmds = []

        for cmd in [getattr(module, x) for x in dir(module)]:
            if not hasattr(cmd, "_vdsm_tool"):
                continue

            cmd_name = cmd._vdsm_tool["name"]

            if cmd_name in tool_command:
                print(textwrap.fill(
                      ERROR_SAME_COMMAND %
                      (cmd_name, mod_name,
                       tool_command[cmd_name]["module"].__name__)))
                continue
            tool_command[cmd_name] = {"module": module, "command": cmd}
            mod_cmds.append((cmd_name, cmd.__doc__))

        tool_modules.append((mod_name, mod_cmds))


def print_command_usage(cmd, stream=sys.stdout):
    print('usage:', '\n', sys.argv[0], '[options]',
          cmd.__doc__.lstrip(), file=stream)


def _usage_command(cmd_name, cmd_docs):
    print('\n', cmd_name)
    print_command_usage(tool_command[cmd_name]["command"])


def _usage_module(mod_name, mod_desc):
    print("\n", "Commands in module %s:" % mod_name)

    for cmd_name, cmd_docs in mod_desc:
        _usage_command(cmd_name, cmd_docs)


def usage_and_exit(exit_code):
    print('\n'.join(["Usage: %s [options] <action> [arguments]" % sys.argv[0],
                     "Valid options:",
                     "  -h, --help",
                     "\t\tShow this help menu.",
                     "  -l, --logfile <path>",
                     "\t\tRedirect logging to file.",
                     "  -v, --verbose",
                     "\t\tInclude warning (and errors) messages in log.",
                     "  -vv, --vverbose",
                     "\t\tInclude information (and above) messages in log.",
                     "  -vvv, --vvverbose",
                     "\t\tInclude debug (and above) messages in log.",
                     "  -a, --append",
                     "\t\tAppend to logfile instead of truncating it",
                     "(if logging to a file)."]))

    for mod_name, mod_desc in tool_modules:
        _usage_module(mod_name, mod_desc)

    print()
    sys.exit(exit_code)


def setup_logging(log_file, verbosity, append):

    level = {0: logging.ERROR,
             1: logging.WARNING,
             2: logging.INFO}.get(verbosity, logging.DEBUG)

    if log_file is not None:
        handler = logging.FileHandler(log_file, mode=(append and 'a' or 'w'))
    else:
        handler = logging.StreamHandler()
    handler.setFormatter(logging.Formatter(
        "%(threadName)s::%(levelname)s::%(asctime)s::%(module)s::" +
        "%(lineno)d::%(name)s::(%(funcName)s) %(message)s"))

    root_logger = logging.getLogger('')
    root_logger.setLevel(level)
    root_logger.addHandler(handler)


def main():
    load_modules()

    try:
        opts, args = getopt.getopt(sys.argv[1:], "hl:av",
                                   ["help", "logfile=", "append",
                                    "verbose", "vverbose", "vvverbose"])
    except getopt.GetoptError:
        usage_and_exit(1)

    log_file = None
    verbosity = 0
    append = False

    for opt in opts:
        if opt[0] in ('-h', '--help'):
            usage_and_exit(0)
        elif opt[0] in ('-l', '--logfile'):
            log_file = opt[1]
        elif opt[0] in ('-v'):
            verbosity += 1
        elif opt[0] == '--verbose':
            verbosity = 1
        elif opt[0] == '--vverbose':
            verbosity = 2
        elif opt[0] == '--vvverbose':
            verbosity = 3
        elif opt[0] in ('-a', '--append'):
            append = True

    setup_logging(log_file, verbosity, append)

    if len(args) < 1:
        usage_and_exit(1)

    cmd = args[0]

    if cmd not in tool_command:
        usage_and_exit(1)

    try:
        return tool_command[cmd]["command"](*args)
    except vdsm.tool.ExtraArgsError:
        print_command_usage(tool_command[cmd]["command"], sys.stderr)
        return 1
    except vdsm.tool.UsageError as e:
        print('Error: ', e, '\n', file=sys.stderr)
        return 1
    except Exception:
        traceback.print_exc(file=sys.stderr)
        return 1
    except KeyboardInterrupt:
        print("Interrupted", file=sys.stderr)
        return 1


if __name__ == "__main__":
    sys.exit(main())
