require('require-yaml');
const loopback = require('loopback');
const boot = require('loopback-boot');
const DataSource = require('loopback-datasource-juggler').DataSource;
const fs = require('fs-extra');
const i18n = require('i18n');
const path = require('path');

let _resolveConnector = DataSource._resolveConnector;
DataSource._resolveConnector = function(...args) {
    let name = args[0];
    let testPath = `${__dirname}/connectors/${name}.js`;

    if (fs.existsSync(testPath)) {
        return {
            connector: require(testPath),
            error: null
        };
    }

    return _resolveConnector.apply(this, args);
};

let app = loopback();
module.exports = app;

// Configuration

let rootDir = __dirname;
let lbDir = path.resolve(`${rootDir}/..`);
let appDir = process.cwd();
let localeDir = `${lbDir}/locale`;
let modulesDir = `${appDir}/modules`;

// Internationalization

app.disconnect = async function() {
    let promises = [];
    for (let ds in this.dataSources)
        promises.push(this.dataSources[ds].disconnect());
    return await Promise.all(promises);
};

app.start = function(bootOptions, port, callback) {
    let onListen = () => {
        let baseUrl = this.get('url').replace(/\/$/, '');
        let explorerPath = this.get('loopback-component-explorer').mountPath;
        console.log(`Browse your REST API at: %s`, `${baseUrl}${explorerPath}`);
        this.emit('started');
        callback && callback();
    };

    this.boot(bootOptions, err => {
        if (err) throw err;
        let args = port ? [port, onListen] : [onListen];
        return this.listen(...args);
    });
};

app.boot = function(bootOptions, callback) {
    // Internatinalization

    if (fs.existsSync(localeDir)) {
        i18n.configure({
            directory: localeDir,
            defaultLocale: 'es'
        });

        this.use(i18n.init);
    }

    // Initialization

    let config = require('./config.json');

    for (let key in config)
        this.set(key, config[key]);

    let modelConfigFiles = [
        `${__dirname}/model-config.json`
    ];
    let modelSources = [
        `${lbDir}/common/models`,
        `${lbDir}/server/models`,
        `${__dirname}/../common/models`
    ];
    let mixinDirs = [
        `${lbDir}/common/mixins`,
        `${lbDir}/server/mixins`,
        `${__dirname}/../common/mixins`
    ];
    let bootDirs = [
        `${__dirname}/boot`
    ];

    addPath(`${appDir}/back`);

    let modules = fs.readdirSync(modulesDir);
    for (let mod of modules)
        addPath(`${modulesDir}/${mod}/back`);

    function addPath(path) {
        modelConfigFiles.push(`${path}/model-config.json`);
        modelSources.push(`${path}/models`);
        mixinDirs.push(`${path}/mixins`);
        bootDirs.push(`${path}/boot`);
    }

    let models = {};
    for (file of modelConfigFiles) {
        if (fs.existsSync(file)) {
            let fileModels = require(file);
            for (let key in fileModels) {
                if (models[key])
                    console.warn(`Redeclaration of '${key}' at ${file}`);
            }
            Object.assign(models, fileModels);
        }
    }

    let myBootOptions = {
        appRootDir: __dirname,
        appConfigRootDir: rootDir,
        modelsRootDir: rootDir,
        models: models,
        modelSources: modelSources,
        mixinDirs: mixinDirs,
        bootDirs: bootDirs
    };

    if (fs.existsSync(`/etc/salix`))
        myBootOptions.dsRootDir = `/etc/salix`;

    Object.assign(myBootOptions, bootOptions);
    boot(this, myBootOptions, callback);
};

if (require.main === module)
    app.start();