#!/usr/bin/env python

import os
import sys

from optparse import OptionParser

import ansigenome.constants as c
import ansigenome.ui as ui
import ansigenome.test_helpers as th
import ansigenome.utils as utils

from ansigenome.config import Config
from ansigenome.init import Init
from ansigenome.run import Run
from ansigenome.scan import Scan


def get_action(args):
    """
    Get the action the user wants to execute from the
    sys argv list.
    """
    for i in range(0, len(args)):
        arg = args[i]
        if arg in c.VALID_ACTIONS:
            del args[i]
            return arg
    return None


def get_opt(options, k, defval=""):
    """
    Returns an option from an Optparse values instance.
    """
    try:
        data = getattr(options, k)
    except:
        return defval
    if k == "roles_path":
        if os.pathsep in data:
            data = data.split(os.pathsep)[0]
    return data


def build_option_parser(action):
    """
    Builds an option parser object based on the action
    the user wants to execute.
    """
    usage = ui.usage()
    epilog = ui.epilogue(os.path.basename(sys.argv[0]))

    args = " ".join(sys.argv)
    version = utils.get_version(os.path.join(c.PACKAGE_RESOURCE,
                                             os.pardir, os.pardir, "VERSION"))

    OptionParser.format_epilog = lambda self, formatter: self.epilog
    parser = OptionParser(usage=usage, epilog=epilog,
                          version="%prog {0}".format(version))

    if not action and "--version" not in sys.argv:
        parser.print_help()
        sys.exit()

    usage_prefix = "Usage: %%prog %s" % action

    if action == "config":
        parser.set_usage("{0} [-o OUT_FILE]".format(usage_prefix))
    elif action == "scan":
        parser.set_usage("{0} ROLES_PATH".format(usage_prefix))
    elif action == "gendoc":
        parser.set_usage("{0} ROLES_PATH".format(usage_prefix))
    elif action == "genmeta":
        parser.set_usage("{0} ROLES_PATH".format(usage_prefix))
    elif action == "export":
        # Adjust the help based on the export type
        export_type = "graph"
        formats = "dot | png"
        usage = "[-s SIZE] [-d DPI] [-g FLAGS]"

        if "-t reqs" in args or "--type reqs" in args:
            export_type = "reqs"
            formats = "txt | yml"
            usage = "[-r READ_VERSION]"
        elif "-t dump" in args or "--type dump" in args:
            export_type = "dump"
            formats = "json"
            usage = ""

        parser.set_usage("{0} ROLES_PATH [-t TYPE] "
                         "[-f FORMAT] {1} [-o OUT_FILE]".format(usage_prefix,
                                                                usage))
        parser.add_option("-t", "--type",
                          dest="type",
                          default="graph",
                          help="output types: graph (default) | reqs | dump")
        parser.add_option("-f", "--format",
                          dest="format",
                          help="output formats: {0}".format(formats))
        if export_type == "graph":
            parser.add_option("-s", "--size",
                              dest="size",
                              help="size of the graph: '15,8' (default)")
            parser.add_option("-d", "--dpi",
                              dest="dpi",
                              help="dpi of the graph: 130 (default)")
            parser.add_option("-g", "--flags",
                              dest="flags",
                              help="send custom flags to the graphviz command")
        elif export_type == "reqs":
            parser.add_option("-r", "--read-version",
                              dest="read_version",
                              action="store_true",
                              help="read in the version from a VERSION file")
    elif action == "init":
        parser.set_usage("{0} ROLE_PATH "
                         "[-c GALAXY_CATEGORIES]".format(usage_prefix))
        parser.add_option("-c", "--galaxy-categories",
                          dest="galaxy_categories",
                          help="comma separated string of galaxy categories")
    elif action == "run":
        parser.set_usage("{0} ROLES_PATH -m COMMAND".format
                         (usage_prefix))
        parser.add_option("-m", "--command", dest="command",
                          help="execute this shell command on each role")
    if action in ("scan", "gendoc", "genmeta", "export", "run"):
        parser.add_option("-l", "--limit", dest="limit",
                          help="comma separated string of roles to white list")
    if action in ("config", "export"):
        parser.add_option("-o", "--out", dest="out_file",
                          help="output file path")

    return parser


def execute_config(args, options, config, parser):
    """
    Execute the config action.
    """
    Config(args, options, config)


def execute_scan(args, options, config, parser):
    """
    Execute the scan action.
    """
    check_roles_path(args, parser)
    Scan(args, options, config)


def execute_gendoc(args, options, config, parser):
    """
    Execute the gendoc action.
    """
    check_roles_path(args, parser)
    Scan(args, options, config, gendoc=True)


def execute_genmeta(args, options, config, parser):
    """
    Execute the genmeta action.
    """
    check_roles_path(args, parser)
    Scan(args, options, config, genmeta=True)


def execute_export(args, options, config, parser):
    """
    Execute the export action.
    """
    check_roles_path(args, parser)
    Scan(args, options, config, export=True)


def execute_run(args, options, config, parser):
    """
    Execute the run action.
    """
    if options.command is None:
        parser.print_help()
        sys.exit()

    check_roles_path(args, parser)
    Run(args, options, config)


def execute_init(args, options, config, parser):
    """
    Execute the init action.
    """
    if len(args) == 0:
        parser.print_help()
        sys.exit()

    Init(args, options, config)


def check_roles_path(args, parser):
    """
    Use the default role path or the user supplied roles path.
    """
    found_path = False

    if len(args) == 0:
        roles_path = os.path.join(os.getcwd(), "playbooks", "roles")

        if os.path.exists(roles_path):
            found_path = True

        if not found_path:
            roles_path = os.getcwd()
            found_path = True
    else:
        roles_path = os.path.abspath(args[0])

    if not os.path.exists(roles_path):
        ui.error(c.MESSAGES["path_missing"], roles_path)
        sys.exit(1)

    if len(args) == 0:
        # there are no args, so add it at as the first arg
        args.append(roles_path)
    else:
        # replace the old argument with the new one
        args[0] = roles_path


def load_config():
    """
    Load properties from the yaml file.
    """
    default_config_path = os.path.join(c.CONFIG_DEFAULT_PATH, c.CONFIG_FILE)
    pwd_config_path = os.path.join(os.getcwd(), c.CONFIG_FILE)
    found_config = False

    if os.path.exists(pwd_config_path):
        this_config = pwd_config_path
        found_config = True
    elif not found_config and os.path.exists(default_config_path):
        this_config = default_config_path
    else:
        # There are no configs at all, so let's help the user make one.
        Config([], {}, {})
        sys.exit(0)

    return (utils.yaml_load(this_config, err_quit=True), this_config)


def load_test_config():
    """
    Load the test config when running tests.
    """
    test_path = os.path.join(c.TEST_PATH, c.CONFIG_FILE)
    th.create_ansigenome_config(test_path)

    return (utils.yaml_load(test_path, err_quit=True), test_path)


def validate_config(config, path):
    """
    Enforce that certain keys exist in the config.
    """
    config_keys = utils.keys_in_dict(config, "", [])
    default_config_keys = utils.keys_in_dict(c.CONFIG_DEFAULTS, "", [])
    missing_keys = []

    for key in default_config_keys:
        if key not in config_keys:
            missing_keys.append(key)

    if missing_keys:
        merge_missing_config_keys(config, missing_keys, path)


def merge_missing_config_keys(config, missing_keys, path):
    """
    Add any missing config keys to the config.
    """
    ui.warn(c.MESSAGES["config_upgraded"])
    ui.ok(missing_keys)
    print

    config = utils.yaml_load(path)

    # merge in empty keys, then add the real keys.
    merged_config = dict(config.items() + c.CONFIG_DEFAULTS.items())
    merged_config.update(config)

    utils.write_config(path, merged_config)


def main():
    """
    Main entry point to the application.
    """
    action = get_action(sys.argv)
    parser = build_option_parser(action)
    (options, args) = parser.parse_args()

    # A little bit hacky but this lets us use an isolated test config.
    if os.getenv("TEST"):
        (config, config_path) = load_test_config()
    else:
        (config, config_path) = load_config()

    # skip validating the config when generating a config
    if action != "config":
        config = validate_config(config, config_path)

    # reload the config
    reloaded_config = utils.yaml_load(config_path)

    args = utils.stripped_args(args)

    if 1:
        fn = globals()["execute_%s" % action]
        fn(args, options, reloaded_config, parser)


if __name__ == "__main__":
    main()
