/**
 * plugin-manager.js
 * Copyright (c) 2000 - 2017 Samsung Electronics Co., Ltd. All rights reserved.
 *
 * Contact:
 * Sungmin Kim <sm.art.kim@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 fs = require('fs');
var path = require('path');
var yaml = require('js-yaml');
var _ = require('underscore');

var Plugin = require('./plugin.js');

/**
 * @module core/plugin/plugin-manager
 */

/**
 * @constructor
 * @memberOf module:core/plugin/plugin-manager
 */

function PluginManager() {
    var self = this;

    /** HASH (plugin name:string -> plugin:module:core/plugin/plugin.Plugin) */
    var plugins = {};
    /** HASH (plugin name:string -> plugin:module:core/plugin/plugin.Plugin) */
    var activePlugins = {};
    /** type {Array.<module:core/plugin/extension-point.ExtensionPoint>} */
    var extensionPoints = [];

    /**
     * @method initialize
     * @param {string} locations
     * @memberOf module:core/plugin/plugin-manager.PluginManager
     */
    this.initialize = function (locations, type) {
        // read plugin.json in search loacations
        _.each(locations, function (location) {
            _.each(fs.readdirSync(location), function (dir) {
                var ppath = location + '/' + dir + '/plugin.json';
                if (fs.existsSync(ppath) && fs.lstatSync(ppath).isFile()) {
                    var plugin = Plugin.create(ppath);
                    if (plugin) {
                        if (!type || (type && _.indexOf(type, plugin.type) > -1)) {
                            plugins[plugin.name] = plugin;
                        }
                    }
                }
            });
        });

        // verify plugin dependencies
        _.each(plugins, function (plugin) {
            _.each(plugin.deps, function (dep) {
                if (!plugins[dep]) {
                    throw new Error('Broken dependency (' + dep + ' not exist)');
                }
            });
        });

        // generate extension point list
        _.each(plugins, function (plugin) {
            _.each(plugin.extensionPoints, function (extensionPoint) {
                extensionPoints.push(extensionPoint);
            });
        });

        // make relationship between extenstion point and extensions
        _.each(plugins, function (plugin) {
            _.each(plugin.extensions, function (extension) {
                var exp = _.find(extensionPoints, function (extensionPoint) {
                    return extensionPoint.id === extension.extensionPointId;
                });

                if (exp) {
                    exp.addExtension(extension);
                }
            });
        });
    };


    /**
     * @method getExtensions
     * @param {string} expId - extension id
     * @returns {module:core/plugin/extension.Extension}
     * @memberOf module:core/plugin/plugin-manager.PluginManager
     */
    this.getExtensions = function (expId) {
        var exp = getExtensionPointInternal(expId);
        if (exp !== null) {
            self.loadPlugin(exp.getPlugin().name);
            var exts = exp.getExtensions();
            for (var i = 0; i < exts.length; i++) {
                self.loadPlugin(exts[i].getPlugin().name);
            }
            return exts;
        } else {
            return null;
        }
    };


    /**
     * @method getAllPlugins
     * @returns {object} - HASH(plugin name:string -> plugin:{@link module:core/plugin/plugin.Plugin})
     * @memberOf module:core/plugin/plugin-manager.PluginManager
     */
    this.getAllPlugins = function () {
        return plugins;
    };


    /**
     * @method getActivePlugins
     * @returns {object} - HASH(plugin name:string -> plugin:{@link module:core/plugin/plugin.Plugin})
     * @memberOf module:core/plugin/plugin-manager.PluginManager
     */
    this.getActivePlugins = function () {
        return activePlugins;
    };


    /**
     * @method getAllExtensionPoints
     * @returns {Array.<module:core/plugin/extension-point.ExtensionPoint>}
     * @memberOf module:core/plugin/plugin-manager.PluginManager
     */
    this.getAllExtensionPoints = function () {
        return extensionPoints;
    };


    /**
     * @method loadPlugin
     * @param {string} pname - plugin name
     * @returns {object} - loaded plugin object
     * @memberOf module:core/plugin/plugin-manager.PluginManager
     */
    this.loadPlugin = function (pname) {
        if (activePlugins[pname] !== undefined) {
            return activePlugins[pname];
        } else {
            for (var name in plugins) {
                var p = plugins[name];

                if (p.name === pname) {
                    var result = null;
                    // load my deps
                    for (var i = 0; i < p.deps.length; i++) {
                        self.loadPlugin(p.deps[i]);
                    }

                    // load me
                    result = p.load();
                    if (result !== null) {
                        activePlugins[pname] = result;
                        updateExtension(p, result);
                    } else {
                        throw new Error('DIBS plugin \'' + pname + '\' cannot be loaded!');
                    }

                    return result;
                }
            }
            throw new Error('DIBS plugin \'' + pname + '\' does not exist!');
        }
    };


    /**
     * @method updateExtension
     * @param {module:core/plugin/plugin.Plugin} p - plugin
     * @param {object} activePlugin - HASH(plugin name:string -> plugin:{@link module:core/plugin/plugin.Plugin})
     * @memberOf module:core/plugin/plugin-manager.PluginManager
     */
    function updateExtension(p, activePlugin) {
        for (var i = 0; i < p.extensions.length; i++) {
            var ext = p.extensions[i];
            var exp = getExtensionPointInternal(ext.extensionPointId);
            for (var key in ext.attributes) {
                var type = exp.getSchemaType(key);
                var value = ext.getAttribute(key);

                if (type === 'module') {
                    var mpath = path.join(p.pluginPath, value);
                    if (path.extname(mpath) == '.yaml') {
                        ext[key] = yaml.safeLoad(fs.readFileSync(mpath, 'utf-8'));
                    } else {
                        ext[key] = require(mpath);
                    }
                } else if (type === 'string' || type === 'object' || type === 'boolean') {
                    ext[key] = value;
                } else if (type === 'function') {
                    if (activePlugin[value] !== undefined) {
                        ext[key] = activePlugin[value];
                    } else {
                        throw new Error('DIBS plugin \'' + value + '\' need activator.js to use type \'function\'!');
                    }
                } else {
                    throw new Error('Cannot read extension\'s attribute type (' + type + ')');
                }
            }
        }
    }


    /**
     * @method getExtensionPointinternal
     * @param {string} expId - extension point id
     * @returns {module:core/plugin/extension-point.ExtensionPoint}
     * @memberOf module:core/plugin/plugin-manager.PluginManager
     */
    function getExtensionPointInternal(expId) {
        for (var i = 0; i < extensionPoints.length; i++) {
            var exp = extensionPoints[i];
            if (exp.id === expId) {
                return exp;
            }
        }
        return null;
    }
}

module.exports = new PluginManager();
