/*++

Copyright (c) 2017 Minoca Corp.

    This file is licensed under the terms of the GNU General Public License
    version 3. Alternative licensing terms are available. Contact
    info@minocacorp.com for details. See the LICENSE file at the root of this
    project for complete licensing information.

Module Name:

    config.ck

Abstract:

    This module implements the config command for Santa.

Author:

    Evan Green 24-May-2017

Environment:

    Chalk

--*/

//
// ------------------------------------------------------------------- Includes
//

import io;
from getopt import gnuGetopt;
from json import dumps, loads;

from santa.config import config;
from santa.lib.santaconfig import SantaConfig;

//
// --------------------------------------------------------------------- Macros
//

//
// ---------------------------------------------------------------- Definitions
//

//
// ------------------------------------------------------ Data Type Definitions
//

//
// ----------------------------------------------- Internal Function Prototypes
//

function
_listConfig (
    actor,
    json
    );

function
_printConfigValue (
    prefix,
    value
    );

//
// -------------------------------------------------------------------- Globals
//

var description = "Get or set application parameters";

var shortOptions = "af:hjlrsu";
var longOptions = [
    "add",
    "file=",
    "help",
    "json",
    "list",
    "remove",
    "system",
    "user"
];

var usage =
    "usage: santa config [options] key\n"
    "       santa config [options] key value [value...]\n"
    "santa config gets or sets an application configuration value.\n"
    "Valid options are:\n"
    "  -f, --file=file -- Work on the specified config file.\n"
    "  -j, --json -- Print the result in JSON or read the value as JSON.\n"
    "  -s, --system -- Work on the system store rather than the user's store.\n"
    "  -u, --user -- Work on the user store instead of the combination of "
    "all stores\n"
    "  -r, --remove -- Remove the given key\n"
    "  -a, --add -- Add to the given key instead of replacing it.\n"
    "  -l, --list -- List all values found in the store.\n";

//
// ------------------------------------------------------------------ Functions
//

function
command (
    args
    )

/*++

Routine Description:

    This routine implements the config command.

Arguments:

    args - Supplies the arguments to the function.

Return Value:

    Returns an exit code.

--*/

{

    var action = null;
    var actioncount = 0;
    var actor = config;
    var argc;
    var json = false;
    var list = false;
    var name;
    var options = gnuGetopt(args[1...-1], shortOptions, longOptions);
    var previous;
    var value;

    args = options[1];
    argc = args.length();
    options = options[0];
    for (option in options) {
        name = option[0];
        value = option[1];
        if ((name == "-a") || (name == "--add")) {
            action = "add";
            actioncount += 1;

        } else if ((name == "-f") || (name == "--file")) {
            config.__init(value, {});

        } else if ((name == "-s") || (name == "--system")) {
            actor = actor._global;

        } else if ((name == "-h") || (name == "--help")) {
            Core.print(usage);
            return 1;

        } else if ((name == "-j") || (name == "--json")) {
            json = true;

        } else if ((name == "-l") || (name == "--list")) {
            list = true;

        } else if ((name == "-r") || (name == "--remove")) {
            action = "remove";
            actioncount += 1;

        } else if ((name == "-u") || (name == "--user")) {
            actor = actor._user;

        } else {
            Core.raise(ValueError("Invalid option '%s'" % name));
        }
    }

    if (list) {
        if ((argc != 0) || (actioncount != 0)) {
            Core.raise(ValueError("Expected no arguments with --list"));
        }

        _listConfig(actor, json);
        return 0;
    }

    if (actioncount > 1) {
        Core.raise(ValueError("Specify only one action"));
    }

    if (argc == 0) {
        Core.raise(ValueError("Expected an argument"));
    }

    //
    // If no actor was specified and this is not a get operation, default to
    // the user store. Otherwise the action is performed on the override store,
    // causing no changes to stick, which is probably not what the user wanted.
    //

    if ((action != null) || (argc != 1)) {
        if (actor is SantaConfig) {
            actor = actor._user;
        }
    }

    if (argc == 1) {
        if (action == "remove") {
            actor.setKey(args[0], null);
            actor.save();

        } else if (action == null) {
            value = actor.getKey(args[0]);
            if ((value is Dict) || (value is List) || (json != false)) {
                Core.print(dumps(value, 4));

            } else {
                Core.print(value.__str());
            }

        } else {
            Core.raise(ValueError("Cannot perform %s with only one argument" %
                                  action));
        }

    } else if (argc == 2) {
        value = args[1];
        if (json != false) {
            value = loads(value);

        } else {

            //
            // Convert true, false, and anything that looks like a number. To
            // override this behavior, use the --json flag.
            //

            if (value == "true") {
                value = true;

            } else if (value == "false") {
                value = false;

            } else {
                try {
                    value = Int.fromString(value);

                } except ValueError {}
            }
        }

        if (action == "add") {
            previous = actor.getKey(args[0]);
            if (previous != null) {
                if (previous is List) {
                    previous.append(value);
                    value = previous;

                } else {
                    value = [previous, value];
                }
            }

            actor.setKey(args[0], value);
            actor.save();

        } else if (action == null) {
            actor.setKey(args[0], value);
            actor.save();

        } else {
            Core.raise(ValueError("Cannot perform %s with two arguments" %
                                  action));
        }

    //
    // Many arguments.
    //

    } else {
        value = [];
        if (action == "add") {
            previous = actor.getKey(args[0]);
            if (previous != null) {
                if (previous is List) {
                    value = previous;

                } else {
                    value = [previous];
                }
            }
        }

        for (arg in args[1...-1]) {
            if (json) {
                arg = loads(arg);
            }

            value.append(arg);
        }

        if ((action == null) || (action == "add")) {
            actor.setKey(args[0], value);
            actor.save();

        } else {
            Core.raise(ValueError("Cannot perform %s with many arguments" %
                                  action));
        }
    }

    return 0;
}

//
// --------------------------------------------------------- Internal Functions
//

function
_listConfig (
    actor,
    json
    )

/*++

Routine Description:

    This routine lists the configuration for the given dictionary.

Arguments:

    actor - Supplies the configuration to work on.

    json - Supplies whether or not to print in JSON format.

Return Value:

    Returns an exit code.

--*/

{

    actor = actor.dict();
    if (json) {
        Core.print(dumps(actor, 4));

    } else {
        _printConfigValue("", actor);
    }

    return 0;
}

function
_printConfigValue (
    prefix,
    value
    )

/*++

Routine Description:

    This routine prints a configuration value, recursively.

Arguments:

    prefix - Supplies the prefix to print on all values.

    value - Supplies the value to print.

Return Value:

    None.

--*/

{

    var index;

    if (value is Dict) {
        if (prefix != "") {
            prefix += ".";
        }

        for (key in value) {
            _printConfigValue("%s%s" % [prefix, key.__str()], value[key]);
        }

    } else if (value is List) {
        for (index in 0..value.length()) {
            _printConfigValue("%s[%d]" % [prefix, index], value[index]);
        }

    } else {
        Core.print("%s = %s" % [prefix, value.__str()]);
    }

    return;
}

