/**
 * The program can be configured with a rc file such as .nhairc, .nhairc.js, .nhairc.yml, etc.
 * see {@link https://www.npmjs.com/package/cosmiconfig|cosmiconfig} for allowed file names.
 * This is documentation for the various config options.
 *
 * @module Config
 */

const path = require("path");
let {cosmiconfigSync} = require("cosmiconfig");
const cosmiconfigOpts = {};

const defaultConfigMap = new Map(
    [
        /**
         * Name of the application, mostly for cosmetic purposes.
         *
         * @name app-name
         * @type {string}
         * @default nhai
         */
        ["app-name", "nhai"],
        /**
         * Version of the application picked up from `package.json`, for cosmetic purposes.
         *
         * @name app-version
         * @type {string}
         */
        ["app-version", require("../package.json").version],
        /**
         * Whether the debugger should break on the first event after the program starts.
         *
         * @name debug-break-on-entry
         * @type {boolean}
         * @default false
         */
        ["debug-break-on-entry", false],
        /**
         * The environment won't keep running if a breakpoint is hit.
         *
         * @name debug-sync-environment
         * @type {boolean}
         * @default true
         */
        ["debug-sync-environment", true],
        /**
         * Whether the environment is sychronous (e.g. turn-based) or asynchronous (e.g. real-time). Note that asynchronous is currently untested.
         *
         * @name environment-synchronous
         * @type {boolean}
         * @default true
         */
        ["environment-synchronous", true],
        /**
         * If the environment is synchronous, how long to wait before worrying that we haven't gotten a `nextTick()`.
         *
         * @name environment-sync-watchdog-timeout
         * @type {number}
         * @default 3000
         */
        ["environment-sync-watchdog-timeout", 3000],
        /**
         * The interval of ticks in a synchronous environment.
         *
         * @name environment-async-time
         * @type {number}
         * @default 100
         */
        ["environment-async-time", 100],
        /**
         * The database name to use within the Graph Database.
         *
         * @name graphdb-name
         * @type {string}
         * @default nhai
         */
        ["graphdb-name", "nhai"],
        /**
         * Path to directory where HTML templates are stored.
         *
         * @name html-template-dir
         * @type {string}
         * @default assets/hbs
         */
        ["html-template-dir", path.resolve(__dirname, "../assets/hbs")],
        /**
         * The location / country used for interpreting date / time. Used for logging and potentially other timestamps.
         *
         * @name locale
         * @type {string}
         * @default default
         */
        ["locale", "default"],
        /**
         * For `error` and `fatal` messages, always log the stack regardless of whether an Error was logged.
         *
         * @name log-error-stack
         * @type {boolean}
         * @default false
         */
        ["log-error-stack", false],
        /**
         * Maximum number of lines to print from the stack dump. Applies globally.
         *
         * @name log-error-stack-length
         * @type {integer}
         * @default 64
         */
        ["log-error-stack-length", 64],
        /**
         * Default level of messages that should be printed: trace, debug, info, warn, error, fatal.
         *
         * @name log-level
         * @type {string}
         * @default trace
         */
        ["log-level", "trace"],

        // ["log-src", false], // TODO this is slow; whether the logger should print the source code file and line.

        /**
         * Whether Log.init should monkey patch console.log
         *
         * @name log-patch-console
         * @type {boolean}
         * @default true
         */
        ["log-patch-console", true],
        /**
         * Print a "Starting..." msg when logger is initialized.
         *
         * @name log-start-msg
         * @type {boolean}
         * @default false
         */
        ["log-start-msg", false],
        /**
         * Force color logging, even if not ta TTY (can be messy but good for testing).
         *
         * @name log-force-color
         * @type {boolean}
         * @default true
         */
        ["log-force-color", true],
        /**
         * Whether to write log messages to a file.
         *
         * @name log-file-enabled
         * @type {boolean}
         * @default false
         */
        ["log-file-enabled", false],
        /**
         * First part of the file name when writting log files
         *
         * @name log-file-prefix
         * @type {string}
         * @default nhai-
         */
        ["log-file-prefix", "nhai-"],
        /**
         * Last part of the file name when writing log files
         *
         * @name log-file-suffix
         * @type {string}
         * @default .log
         */
        ["log-file-suffix", ".log"],
        /**
         * Path to the directory for storing log files
         *
         * @name log-file-path
         * @type {string}
         * @default .
         */
        ["log-file-path", "."],
        /**
         * Seed for the PRNG / DRNG
         *
         * @name random-seed
         * @type {string}
         * @default goodluck!
         */
        ["random-seed", "goodluck!"],
        /**
         * The redis graph database server IP address
         *
         * @name redisgraph-server
         * @type {string}
         * @default 127.0.0.1
         */
        ["redisgraph-server", "127.0.0.1"],
        /**
         * The redis graph database server port.
         *
         * @name redisgraph-port
         * @type {string}
         * @default 6379
         */
        ["redisgraph-port", 6379],
        /**
         * The redis graph database connection options.
         *
         * @name redisgraph-options
         * @type {object}
         * @default undefined
         */
        ["redisgraph-options", undefined],
        /**
         * The directory holding JSON schemas to validate various data types
         *
         * @name schema-dir
         * @type {string}
         */
        ["schema-dir", path.resolve(__dirname, "../assets/schema")],

        // ["shell-enabled", true], // whether to start the shell
        // ["shell-prompt", "$"], // default shell prompt symbol
    ],
);

let configMap = new Map();
let configFiles = [];
let loadComplete = false;

/**
 * The global configuration object for getting and setting configuration values.
 */
class Config {
    /**
     * Initializes the configuration, reading config files and such
     */
    static async init() {
        if (Config.isLoaded) {
            return;
        }

        configFiles.length = 0;
        configMap = new Map(defaultConfigMap.entries());

        let appName;
        do {
            appName = Config.get("app-name");
            await loadAppConfig();
        } while (appName !== Config.get("app-name"));
    }

    /** the path to the configuration file that was loaded, or null if none was loaded */
    static get fileList() {
        return configFiles;
    }

    /**
     * Returns a Map containing the global configuration values
     *
     * @returns {Map} A map containing key / value pairs of configuration settings
     */
    static getConfig() {
        return configMap;
    }

    /**
     * Gets the configuration value for the specified key
     *
     * @param {string} key - The name of the configuration value to retrieve
     * @returns {*}     The configuration value
     */
    static get(key) {
        // checkType("Config.get", "key", key, "string");
        return configMap.get(key);
    }

    /**
     * Sets the configuration value for the specified key
     *
     * @param {string} key - The name of the configuration value to assign
     * @param {*}      val - The value to assign
     */
    static set(key, val) {
        // checkType("Config.set", "key", key, "string");
        configMap.set(key, val);
    }

    /**
     * Reset all config values to their defaults and remove any non-default values
     */
    static reset() {
        configMap.clear();
        configFiles.length = 0;

        for (let entry of defaultConfigMap.entries()) {
            configMap.set(... entry);
        }

        loadComplete = false;
    }

    /**
     * Load a config using the specified Object as the key-value config settings
     *
     * @param   {object} confObj An Object where every key is a config parameter name and the associated value is the config value
     */
    static load(confObj) {
        const {checkType} = require("./Utility");
        checkType("Config.load", "confObj", confObj, "object");

        for (let key of Object.keys(confObj)) {
            // console.info(`merging: '${key}' = '${confObj[key]}'`);
            Config.set(key, confObj[key]);
        }

        loadComplete = true;

        return this.getConfig();
    }

    /** boolean indicating whether a configuration has been loaded */
    static get isLoaded() {
        return loadComplete;
    }
}

async function loadAppConfig() {
    let appName = Config.get("app-name");
    let explorerSync = cosmiconfigSync(appName, cosmiconfigOpts);

    let configResult = explorerSync.search();
    if (!configResult) {
        return null;
    }

    // console.info("found config:", configResult);

    if (typeof configResult.config !== "object" && typeof configResult.config !== "undefined") {
        throw new Error(`Error loading config (did not produce a config Object): ${configResult.filename}`);
    } else {
        Config.load(configResult.config);
    }

    configFiles.push(configResult);
    return configResult.config;
}

// set defaults
Config.reset();

module.exports = {Config};