'use strict';

const fs = require('fs');
const _ = require('lodash');
const Promise = require('bluebird');
const EventEmitter = require('paradox-utils').EventEmitter;

const ControllerRequest = require('./controller-request');
const logger = require('paradox-logger')('controller-summoner');

class ControllerSummoner extends EventEmitter {

    constructor(manager, cluster, paths, options) {
        super();

        this.manager = manager;
        this.cluster = cluster;
        this.paths = paths || [];
        this.options = _.defaults(options, {
            timeout: 365 * 24 * 60,
            ignoreCluster: [],
            deliveryStrategy: this.defaultDeliveryStrategy.bind(this),
        });

        this.customControllers = {};

        this.controllers = {};

        if(cluster) {
            this.startCluster();
        }
    }

    defaultDeliveryStrategy(request, clients, options) {
        if(clients.length < 1) {
            return false;
        }

        if(options.ignoreCluster.includes('*')) {
            return false;
        }

        for(let i = 0; i < options.ignoreCluster.length; i++) {
            let pattern = options.ignoreCluster[i];

            if(_.isRegExp(pattern)) {
                if(pattern.test(request.controller)) {
                    return false;
                }

            } else if(request.controller == pattern) {
                return false;
            }
        }

        if(options.ignoreCluster.includes(request.controller)) {
            return false;
        }

        // TODO: filtrar el que esté offline

        clients.sort((a, b) => {
            let aQuestions = a.questions.filter(v => v.active).length;
            let bQuestions = b.questions.filter(v => v.active).length;

            if(aQuestions == bQuestions) {
                return 0;
            }

            return aQuestions > bQuestions ? 1 : -1;
        });

        return clients[0];
    }

    callActionCluster(controller, action, request, options) {
        if(!this.cluster || !this.cluster.isMaster()) {
            return this.callAction(controller, action, request, options);
        }

        return this.cluster
            .getSlaves(true)
            .then(clients => {
                return this
                    .emit('delivery', [request, clients, options, this.cluster])
                    .then(client => {
                        if(typeof client == 'undefined') {
                            return Promise
                                .try(() => {
                                    return this.options.deliveryStrategy.apply(this, [request, clients, options, this.cluster]);
                                })
                        }

                        return client;
                    })
                    .then(client => {
                        if(!client) {
                            return this.callAction(controller, action, request, options);
                        }

                        return this.cluster.askSlave(client.id, 'call_action', {
                            controller,
                            action,
                            request,
                        });
                    });
            });
    }

    callAction(controller, action, request, options) {
        options = _.defaults(options, {
            timeout: null,
        });

        let controllerClass = this.getController(controller);

        let cleanAction = _.camelCase(action);

        if(!(cleanAction in controllerClass)) {
            let err = new Error('method "' + action + '" doesn\'t exists in "' + controller + '"');
            err.code = 404;
            err.status = 404;
            err.type = 'controller';

            return Promise.reject(err);
        }

        // debug('calling controller "%s->%s" (%s)', controller, cleanAction, action);

        let overrideAction = '_' + cleanAction;

        if(overrideAction in controllerClass) {
            cleanAction = overrideAction;
        }

        let timeout = options.timeout || this.options.timeout || 365 * 24 * 60;

        if(!(request instanceof ControllerRequest)) {
            request = ControllerRequest.factoryObject(request);
        }

        return this
            .emit('action', [request, controllerClass, cleanAction])
            .then(resp => {
                if(typeof resp != 'undefined') {
                    return resp;
                }

                return Promise
                    .try(() => {
                        return controllerClass.preload();
                    })
                    .timeout(timeout)
                    .then(() => {
                        return Promise
                            .try(() => {
                                return controllerClass.invoke(cleanAction, [request]);
                            })
                            .timeout(timeout);
                    })
                    .then(res => {
                        return this
                            .emit('action-result', [res, request])
                            .then(resp => {
                                if(typeof resp != 'undefined') {
                                    return resp;
                                }

                                return res;
                            });
                    })
                    .catch(err => {
                        return this
                            .emit('action-error', [err, request])
                            .then(resp => {
                                if(typeof resp != 'undefined') {
                                    return resp;
                                }

                                return Promise.reject(err);
                            });
                    })
            });
    }

    registerController(name, controller) {
        if(!name) {
            name = 'custom.' + Date.now() + '' + this.customControllers.length;
        }

        this.customControllers[name] = controller;

        return name;
    }

    getController(className) {
        if(className in this.customControllers) {
            return this.customControllers[className];
        }

        if(this.controllers[className]) {
            return this.controllers[className];
        }

        let classFile = null; // __dirname + '/../controllers/' + className + '.js';

        this.paths.forEach(classPath => {
            if(classFile) {
                return;
            }

            let tmpClassFile = classPath + '/' + className.split(/\./g).join('/') + '.js';

            if(fs.existsSync(tmpClassFile)) {
                classFile = tmpClassFile;
            }
        });

        if(!classFile) {
            let err = new Error('controller ' + className + ' doesn\'t exists');
            err.code = 404;
            err.status = 404;
            err.type = 'controller';

            return Promise.reject(err);
        }

        let classObject = require(classFile);
        let object = new classObject();

        return this.controllers[className] = object;
    }

    /* -------------------------------------------------------------------------
     * Cluster
     * ---------------------------------------------------------------------- */

    startCluster() {
        let cluster = this.cluster;

        if(cluster.isSlave()) {
            cluster.onQuestion('call_action', data => {
                logger.debug('call_action received: ' + data.controller + ' - ' + data.action);
                return this.callAction(data.controller, data.action, data.request);
            });
        }
    }
}

module.exports = ControllerSummoner;