let { logger } = require('./logging');
let { modules } = require('./module/index');
let trie = require('trie-prefix-tree');
let { getShortestPrefix } = require('./utility');
let help = require('./module/help');
let message = require('./message');
let utility = require('./utility');
let { initModule } = require('./module/abstract');

let sentinelValue = '!';

class Engine {
    constructor(config, bot, modules) {
        this.config = config;
        this.bot = bot;
        this.modules = modules;
        this.moduleMap = new Map();
        this.commands = [];
        this.commandMap = new Map();
        this.commandRadixTree = trie([]);
    }

    initModules() {
        this.modules.forEach((mod) => {
            logger.info("Loading module: %s", mod.name);
            initModule(mod);
            console.log("Recognized commands: %s", mod.getRecognizedCommands())
            this.moduleMap.set(mod.command, mod);
            this.commandMap.set(mod.command, mod);
            this.commandRadixTree.addWord(mod.command);
        });

        this.modules.forEach((mod) => {
            let shortCharCommand = getShortestPrefix(this.commandRadixTree, mod.command, 1);
            let short3CharCommand = getShortestPrefix(this.commandRadixTree, mod.command, 3);
            let shortCommandAliases = [shortCharCommand, short3CharCommand];
            logger.info("Adding short command %s for module: %s", shortCommandAliases, mod.name);
            shortCommandAliases.forEach( (commandAlias) => {
                this.commandMap.set(commandAlias, mod);
            })
        });

        this.helpModule = help.create(this.moduleMap)
        initModule(this.helpModule);
        this.moduleMap.set(this.helpModule.command, this.helpModule);
        this.commandMap.set('help', this.helpModule);
        this.commands = Array.from(this.commandMap.keys()).sort()

        logger.info("Bound modules to keywords: %o", this.moduleMap);
    }

    init() {
        logger.info("Initializing modules");
        this.initModules();

        /* Bind Message Parsing */
        let engine = this;
        let handleMessages = (event, room, toStartOfTimeline) => {
            /* Don't process messages from self */
            if (event.sender.userId !== this.config.userId) {
                /* don't process messages that aren't of type m.room.message */
                if (event.getType() !== "m.room.message") {
                    logger.debug("Recieved message of type: %s", event.getType());
                    return;
                } else {
                    let messageBody = event.event.content.body;
                    logger.debug("[%s] %s", room.name, messageBody);
                    if (messageBody.indexOf(sentinelValue) === 0) {
                        let command = messageBody.split(' ')[0].substring(1);
                        if (engine.commandMap.has(command)) {
                            engine.commandMap.get(command).handleMessage(event, room, sendResponseMessageCallback(engine.bot));
                        } else {
                            let responseMessage = "The following commands are recognized"
                            responseMessage += "\n" + engine.commands.join(", ")
                            responseMessage += "\nAdditional information can be discovered with !help <command>"
                            sendResponseMessage(engine.bot, room, responseMessage);
                        }
                    }
                }
            }
        }

        this.bot.init(handleMessages);

        /* Capture Exit Conditions */

        ["SIGINT", "SIGTERM"].forEach((signature) => {
            process.on(signature, async () => {
                await this.bot.sendStatusShutdown()
                    .then(() => {
                        logger.info("Gracefully stopping Matrix SDK Client")
                        this.bot.client.stopClient();
                    });
                process.exit(0);
            });
        });

        process.on('exit', () => {
            logger.info("Shutting Down");
        });

        return this;
    }

    run() {
        this.bot.connect();
        return this;
    }
}

/**
 * Handle the callback sending messages via the bot
 * 
 * @param {*} bot 
 * @param {*} room 
 * @param {*} responseMessage 
 */
function sendResponseMessage(bot, room, responseMessage) {
    logger.debug("Responding to room: %s", room.roomId);
    if (responseMessage instanceof Object) {
        bot.client.sendMessage(room.roomId, responseMessage);
    } else if (utility.isString(responseMessage)) {
        bot.client.sendMessage(room.roomId, message.createBasic(responseMessage));
    } else {
        logger.error("Unable to process response message: %s", responseMessage);
    }
}

/**
 * Wrapper to produce a callback function that can be passed to the modules
 * 
 * @param {*} bot 
 */
function sendResponseMessageCallback(bot) {
    return (room, responseMessage) => {
        sendResponseMessage(bot, room, responseMessage);
    }
}

function create(config, bot) {
    return new Engine(config, bot, modules)
}

exports.create = create;