/**
 * event-manager.js
 * Copyright (c) 2000 - 2015 Samsung Electronics Co., Ltd. All rights reserved.
 *
 * Contact:
 * DongHee Yang <donghee.yang@samsung.com>
 * Sungmin Kim <sm.art.kim@samsung.com>
 * Jiil Hyoun <jiil.hyoun@samsung.com>
 * Jonghwan Park <iwin100.park@samsung.com>
 * Kitae Kim <kt920.kim@samsung.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Contributors:
 * - S-Core Co., Ltd
**/

var util = require('util');
var async = require('async');
var EventEmitter = require('events').EventEmitter;
var uuid = require('node-uuid');
var _ = require('underscore');

var dibs = require('./dibs.js');


module.exports.create = function (parent) {
    return new EventManager(parent);
};


function EventManager(parent) {
    var self = this;
    var parentServer = parent;

    // For managing event listener
    var eventListeners = {};
    var eventEmitter = new EventEmitter();
    eventEmitter.on('custom-server-event', function (evtObj) {
        self.executeEventListeners(evtObj, function (err) {
            // Do nothing
        });
    });


    // add event listener
    this.addEventListener = function (evtName, evtOpts, listener, callback) {
        if (!evtOpts) {
            evtOpts = {};
        }

        var evtListener = {
            id: uuid.v4(),
            server: dibs.thisServer.id,
            event: evtName,
            options: evtOpts,
            func: listener
        };

        self.addEventListener2(evtListener, callback);
    };


    this.addEventListener2 = function (evtListener, callback) {
        var evtName = evtListener.event;

        async.series([
            function (cb) {
                if (dibs.thisServer.id !== parentServer.id) {
                    dibs.getServer(parentServer.id).__addEventListener(evtListener, cb);
                } else {
                    cb(null);
                }
            },
            function (cb) {
                if (!eventListeners[evtName]) {
                    eventListeners[evtName] = [];
                }
                dibs.thisServer.log.info('[' + evtListener.id + '] Adding event listener ...' + evtListener.server + '=>' + parentServer.id + ': ' + evtListener.event + ' ' + util.inspect(evtListener.options));
                eventListeners[evtName].push(evtListener);
                cb(null);
            }],
            function (err) {
                callback(err, evtListener);
            });
    };


    // remove event listener
    this.removeEventListener = function (evtListener, callback) {
        async.series([
            function (cb) {
                if (dibs.thisServer.id !== parentServer.id) {
                    dibs.getServer(parentServer.id).__removeEventListener(
                        evtListener, cb);
                } else {
                    cb(null);
                }
            },
            function (cb) {
                // check event name
                if (!eventListeners[evtListener.event]) {
                    return cb(null);
                }

                // check event listener exists
                var matched = eventListeners[evtListener.event].filter(function (l) {
                    return l.id === evtListener.id;
                });

                if (matched.length === 0) {
                    return cb(null);
                }

                // remove from listener list
                dibs.thisServer.log.info('[' + evtListener.id + '] Removing event listener ...' + evtListener.server + ': ' + evtListener.event);
                var index = eventListeners[evtListener.event].indexOf(matched[0]);
                eventListeners[evtListener.event].splice(index, 1);

                // if listener list is empty, remove the event
                if (eventListeners[evtListener.event].length === 0) {
                    delete eventListeners[evtListener.event];
                }
                cb(null);
            }],
            function (err) {
                callback(err);
            });
    };


    this.emitEvent = function (evtObj) {
        eventEmitter.emit('custom-server-event', evtObj);
    };


    // execute event listener
    this.executeEventListeners = function (evtObj, callback) {
        // redirect if different server
        if (evtObj._emitServerId && (parentServer.id !== evtObj._emitServerId)) {
            return dibs.getServer(evtObj._emitServerId).executeEventListeners(evtObj, callback);
        }

        // check event is valid
        if (!eventListeners[evtObj.event]) {
            return callback(null);
        }

        // execute  event listeners
        async.each(eventListeners[evtObj.event],
            function (evtListener, cb) {
                // filter out unmatched event
                var unmatched = Object.keys(evtObj).filter(function (key) {
                    // filter out unmatched event
                    return (evtListener.options[key] && evtObj[key] &&
                        evtListener.options[key] !== evtObj[key]);
                });
                if (unmatched.length > 0) {
                    _.each(unmatched, function (l) {
                        dibs.thisServer.log.info('[' + evtListener.id + '] Ignore emit event listener... ' + evtListener.event + ': ' + l + ' ' + util.inspect(evtListener.options[l]) + ', emit:' + util.inspect(evtObj[l]));
                    });
                    return cb(null);
                }

                if (_.difference(_.keys(evtListener.options), _.keys(evtObj)).length > 0) {
                    dibs.thisServer.log.warn('[' + evtListener.id + '] ' + dibs.thisServer.id + ' uses unmatched keys for emit event... ' + evtListener.event);
                }

                // execute server
                if (dibs.thisServer.id === evtListener.server) {
                    //emit by remote server => execute only emit server event
                    if (evtObj._emitServerId) {
                        if (evtObj._evtListener.id !== evtListener.id) {
                            dibs.thisServer.log.info('[' + evtListener.id + '] Ignore emit event listener... ' + evtListener.event + ': ' + evtListener.id + ' <=> ' + evtObj._evtListener.id);
                            return cb(null);
                        }
                    } else {
                        //emit by local server
                    }

                    dibs.thisServer.log.info('[' + evtListener.id + '] Executing event listener ...' + evtListener.event + ': ' + util.inspect(evtListener.options));
                    evtListener.func(evtObj, function (err) {
                        if (err) {
                            dibs.thisServer.log.warn('[' + evtListener.id + '] Executing event listener returns error...');
                            dibs.thisServer.log.warn(err);
                        }
                        // NOTE. to execute all listeners, cb must be null
                        cb(null);
                    });
                } else { // emit by remote server
                    dibs.thisServer.log.info('[' + evtListener.id + '] Emit event listener... ' + evtListener.event + ': ' + util.inspect(evtListener.options));
                    // NOTE. '_emitServerId' property will be inserted automatically
                    var newEvtObj = _.clone(evtObj);
                    newEvtObj._emitServerId = dibs.thisServer.id;
                    newEvtObj._evtListener = evtListener;

                    executeRemoteEventListener(newEvtObj, 1, cb);
                }
            },
            function (err) {
                if (err) {
                    dibs.thisServer.log.error('Error Executing event listener... error: ' + util.inspect(err));
                }
                callback(err);
            });
    };


    // get list of listeners
    this.getEventListeners = function (event, callback) {
        if (!eventListeners[event]) {
            return callback(null, []);
        }

        callback(null, eventListeners[event]);
    };

    function executeRemoteEventListener(evtObj, retry, callback) {
        dibs.getServer(evtObj._evtListener.server).__executeEventListeners(evtObj, function (err) {
            if (err) { // RPC error
                dibs.thisServer.log.warn('[' + evtObj._evtListener.id + '] Error emit event listener...' + evtObj._evtListener.event + ' error: ' + util.inspect(err));
                setTimeout(function () {
                    if (retry <= 1000) {
                        retry = retry + 1;
                        dibs.thisServer.log.info('[' + evtObj._evtListener.id + '] Retry(' + retry + ') emit event listener... ' + evtObj._evtListener.event + ': ' + util.inspect(evtObj._evtListener.options));
                        executeRemoteEventListener(evtObj, retry, callback);
                    } else {
                        dibs.thisServer.log.error('[' + evtObj._evtListener.id + '] Failed retry(' + retry + ') emit event listener... ' + evtObj._evtListener.event + ': ' + util.inspect(evtObj._evtListener.options));
                    }
                }, 3000);
            }
        });
    }
}
