var fs = require('fs');
var timers = require('./timer');
var jsbot = require('./jsbot/jsbot');
require('./snippets');

var DBot = function(timers) {
    // Load external files
    var requiredConfigKeys = [ 'name', 'servers', 'admins', 'moduleNames', 'language', 'debugMode' ];
    try {
        this.config = JSON.parse(fs.readFileSync('config.json', 'utf-8'));
    } catch(err) {
        console.log('Config file is screwed up. Attempting to load defaults.');
        try {
            this.config = JSON.parse(fs.readFileSync('config.json.sample', 'utf-8'));
        } catch(err) {
            console.log('Error loading sample config. Bugger off. Stopping.');
            process.exit();
        }
    }
    requiredConfigKeys.each(function(key) {
        if(!this.config.hasOwnProperty(key)) {
            console.log('Error: Please set a value for ' + key + ' in ' +
                'config.json. Stopping.');
            process.exit();
        }
    }.bind(this));

    var rawDB;
    try {
        var rawDB = fs.readFileSync('db.json', 'utf-8');
    } catch(err) {
        this.db = {};  // If no db file, make empty one
    }

    try {
        if(!this.db) {  // If it wasn't empty 
            this.db = JSON.parse(rawDB);
        }
    } catch(err) {
        console.log('Syntax error in db.json. Stopping: ' + err);
        process.exit();
    }

    // Load Strings file
    try {
        this.strings = JSON.parse(fs.readFileSync('strings.json', 'utf-8'));
    } catch(err) {
        console.log('Probably a syntax error in strings.json: ' + err);
        this.strings = {};
    }

    // Initialise run-time resources
    this.usage = {};
    this.sessionData = {};
    this.timers = timers.create();

    // Populate bot properties with config data
    // Create JSBot and connect to each server
    this.instance = jsbot.createJSBot(this.config.name);
    for(var name in this.config.servers) {
        if(this.config.servers.hasOwnProperty(name)) {
            var server = this.config.servers[name];
            this.instance.addConnection(name, server.server, server.port,
                    this.config.admin, function(event) {
                var server = this.config.servers[event.server];
                for(var i=0;i<server.channels.length;i++) {
                    this.instance.join(event, server.channels[i]);
                }
            }.bind(this), server.nickserv, server.password);
        }
    }

    // Load the modules and connect to the server
    this.reloadModules();
    this.instance.connectAll();
};

// Say something in a channel
DBot.prototype.say = function(server, channel, message) {
    this.instance.say(server, channel, message);
};

// Format given stored string in config language
DBot.prototype.t = function(string, formatData) {
    var formattedString;
    if(this.strings.hasOwnProperty(string)) {
        var lang = this.config.language;
        if(!this.strings[string].hasOwnProperty(lang)) {
            lang = "english"; 
        }

        formattedString = this.strings[string][lang].format(formatData);
    } else {
        formattedString = 'String not found. Something has gone screwy. Maybe.';
    }
    
    return formattedString;
};

/*DBot.prototype.act = function(channel, data) {
    this.instance.send('PRIVMSG', channel, ':\001ACTION ' + data + '\001');
}*/

// Save the database file
DBot.prototype.save = function() {
    fs.writeFile('db.json', JSON.stringify(this.db, null, '    '));
};

// Hot-reload module files.
DBot.prototype.reloadModules = function() {
    if(this.modules) { // Run 'onDestroy' code for each module if it exists.
        this.modules.each(function(module) {
            if(module.onDestroy) {
                module.onDestroy();
            }
        });
    }

    this.rawModules = [];
    this.modules = {};
    this.commands = {};
    this.commandMap = {}; // Map of which commands belong to which modules
    this.usage = {};
    this.timers.clearTimers();

    try {
        this.strings = JSON.parse(fs.readFileSync('strings.json', 'utf-8'));
    } catch(err) {
        this.strings = {};
    }

    var moduleNames = this.config.moduleNames;

    // Enforce having command. it can still be reloaded, but dbot _will not_ 
    //  function without it, so not having it should be impossible
    if(!moduleNames.include("command")) {
        moduleNames.push("command");
    }

    // Reload Javascript snippets
    var path = require.resolve('./snippets');
    delete require.cache[path];
    require('./snippets');

    this.instance.removeListeners();

    moduleNames.each(function(name) {
        var moduleDir = './modules/' + name + '/';
        var cacheKey = require.resolve(moduleDir + name);
        delete require.cache[cacheKey];

        try {
            // Load the module config data
            try {
                var config = JSON.parse(fs.readFileSync(moduleDir + 'config.json', 'utf-8'))
                this.config[name] = config;
                for(var i=0;i<config.dbKeys.length;i++) {
                    if(!this.db.hasOwnProperty(config.dbKeys[i])) {
                        this.db[config.dbKeys[i]] = {};
                    }
                }
            } catch(err) {
                // Invalid or no config data
            }

            // Load the module itself
            var rawModule = require(moduleDir + name);
            var module = rawModule.fetch(this);
            this.rawModules.push(rawModule);

            if(module.listener) {
                var listenOn = module.on;
                if(!(listenOn instanceof Array)) {
                    listenOn = [listenOn];
                }

                listenOn.each(function(on) {
                    this.instance.addListener(on, module.name, module.listener);
                }.bind(this));
            }

            if(module.onLoad) {
                module.onLoad();
            }

            // Load module commands
            if(module.commands) {
                var newCommands = module.commands;
                for(key in newCommands) {
                    if(newCommands.hasOwnProperty(key) && Object.prototype.isFunction(newCommands[key])) {
                        this.commands[key] = newCommands[key];
                        this.commandMap[key] = name;
                    }
                }
            }

            // Load the module usage data
            try {
                var usage = JSON.parse(fs.readFileSync(moduleDir + 'usage.json', 'utf-8'));
                for(key in usage) {
                    if(usage.hasOwnProperty(key)) {
                        if(this.usage.hasOwnProperty(key)) {
                            console.log('Usage key clash for ' + key + ' in ' + name);
                        } else {
                            this.usage[key] = usage[key];
                        }
                    }
                }
            } catch(err) {
                // Invalid or no usage info
            }

            // Load the module string data
            try {
                var strings = JSON.parse(fs.readFileSync(moduleDir + 'strings.json', 'utf-8'));
                for(key in strings) {
                    if(strings.hasOwnProperty(key)) {
                        if(this.strings.hasOwnProperty(key)) {
                            console.log('Strings key clash for ' + key + ' in ' + name);
                        } else {
                            this.strings[key] = strings[key];
                        }
                    }
                }
            } catch(err) {
                // Invalid or no string info
            }

            module.toString = function() {
                return this.name;
            }
            this.modules[module.name] = module;
        } catch(err) {
            console.log(this.t('module_load_error', {'moduleName': name}));
            if(this.config.debugMode) {
                console.log('MODULE ERROR (' + name + '): ' + err.stack );
            }
            else {
                console.log('MODULE ERROR (' + name + '): ' + err );
            }
        }
    }.bind(this));
    this.save();
};

DBot.prototype.cleanNick = function(key) {
    key = key.toLowerCase();
    while(key.endsWith("_")) {
        if(this.db.quoteArrs.hasOwnProperty(key)) {
            return key;
        }
        key = key.substring(0, key.length-1);
    }
    return key;
}

new DBot(timers);