/**
 * @typedef {Object} PluginBase
 * @property {PluginBase.init} init Initialize the plugin. Throws an Error if the Plugin does not implement it!
 * @property {PluginBase.super} super Returns the parent PluginBase proxy.
 * @property {PluginBase.getName} getName Returns the name of the plugin
 * @property {PluginBase.getElement} getElement Returns the element which registered the plugin.
 * @property {PluginBase.getOptions} getOptions Returns the options of the plugin. The method returns a copy of the options object and not a reference.
 * @property {PluginBase.getOption} getOption Returns the value of a single option.
 * @property {PluginBase.registerPlugin} registerPlugin Register Plugin Definition
 * @property {String} _name
 * @property {JQuery<HTMLElement>} $el
 * @property {Object} DEFAULTS Default Options
 * @property {Events} Events Events
 * @property {Logger} Logger Logger
 */

/**
 * @callback PluginBase.init
 * @throws {Error} if plugin does not have an init function
 * @param {HTMLElement} element
 * @param {Object} options
 */

/**
 * @callback PluginBase.super
 * @returns {PluginBase}
 */

/**
 * @callback PluginBase.getName
 * @returns {String}
 */

/**
 * @callback PluginBase.getElement
 * @returns {JQuery<HTMLElement>}
 */

/**
 * @callback PluginBase.getOptions
 * @instance
 * @returns {Object}
 */

/**
 * @callback PluginBase.getOption
 * @param {String} key Option key.
 * @returns {any}
 */

/**
 * @callback PluginBase.registerPlugin
 * @param {string} name Name of the plugin
 * @param {object|PluginBase} pluginClass Class definition of the plugin
 */

(function($, document, window, viewport) {
    'use strict';

    /**
     * Plugin Base Class
     * @param {String} name The Name of the Plugin
     * @param {HTMLElement} element
     * @param {Object} options
     * @param {boolean} reinitialize True when the plugin should be reinitialized
     */
    const PluginBase = function(name, element, options, reinitialize) {
        /**
         * @property {String} _name - Name of the Plugin
         */
        this._name = name;

        /** @type {Events} Events */
        this.Events = window.easytemplate.Events;

        /** @type {Logger} Logger */
        this.Logger = window.easytemplate.Logger;

        this.$el = $(element);
        this.options = $.extend({}, this.DEFAULTS || {}, options);

        this.init(element, options);
        this.Events.publish('easytemplate/Plugin/' + name + '/onInit', [this]);

        if (reinitialize) {
            this.reinit(element, options);
            this.Events.publish('easytemplate/Plugin/' + name + '/onReInit', [this]);
        }
    };

    PluginBase.prototype = {
        DEFAULTS: {},
        constructor: PluginBase,

        /**
         * Call parent instance
         * @returns {any}
         */
        super: function() {
            return new Proxy(this, {
                get(target, prop) {
                    return Object.getPrototypeOf(target)[prop].bind(target);
                }
            });
        },

        /**
         * @throws {Error} if plugin does not have an init function
         * @param {HTMLElement} element
         * @param {Object} options
         */
        init: function(element, options) {
            throw new Error('Plugin ' + this.getName() + ' has to have an init function!');
        },

        /**
         * Called when reinitializing the plugin
         * @param {HTMLElement} element
         * @param {Object} options
         */
        reinit: function(element, option) {
        },

        /**
         * Returns the name of the plugin.
         * @returns {String}
         */
        getName: function () {
            return this._name;
        },

        /**
         * Returns the element which registered the plugin.
         * @returns {JQuery<HTMLElement>}
         */
        getElement: function () {
            return this.$el;
        },

        /**
         * Returns the options of the plugin. The method returns a copy of the options object and not a reference.
         * @returns {Object}
         */
        getOptions: function () {
            return $.extend({}, this.options);
        },

        /**
         * Returns the value of a single option.
         * @param {String} key Option key.
         * @returns {any}
         */
        getOption: function (key) {
            return this.options[key];
        },
    };

    // STATIC FUNCTIONS
    // ================
    /**
     * Register Plugin Definition
     * @param {string} name Name of the plugin
     * @param {object} pluginClass Class definition of the plugin
     */
    PluginBase.registerPlugin = function(name, pluginClass) {
        /**
         * Register jQuery Plugin
         * @param {string} name Name of the plugin
         * @param {object} options Plugin Options
         * @param {boolean} reinitialize True when the plugin should be reinitialized
         */
        $.fn[name] = function(name, options, reinitialize) {
            return this.each(function() {
                // extend the base plugin and register it as a jquery plugin
                var element = this;
                var Plugin = function () {
                    PluginBase.call(this, name, element, options, reinitialize || false);
                };

                /** @type {PluginBase} */
                Plugin.prototype = $.extend(Object.create(PluginBase.prototype), { constructor: Plugin }, pluginClass);
                var plugin = new Plugin();

                plugin.Events.publish('easytemplate/Plugin/' + name + '/onRegister', [plugin]);

                return plugin;
            });
        };
    };

    // Expose to global namespace
    window.easytemplate = window.easytemplate || {};
    window.easytemplate.PluginBase = PluginBase;

})(jQuery, document, window, ResponsiveBootstrapToolkit);