/*
 * JBoss, Home of Professional Open Source
 * Copyright 2013, Red Hat, Inc. and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

/**
 * @author Pavel Yaschenko
 */

window.RichFaces = window.RichFaces || {};
RichFaces.jQuery = RichFaces.jQuery || window.jQuery;

(function ($, rf, params) {

    rf.blankFunction = function () {
    }; //TODO: add it to global library

    /**
     * @class Base class for all components.
     * All RichFaces components should use this class as base or another RichFaces class which based on it.
     *
     <pre><code>
     //Inheritance example:
     (function ($, richfaces, params) {

     // Constructor definition
     richfaces.MyComponent = function(componentId, [options]) {
     // call constructor of parent class
     $super.constructor.call(this, componentId, [options]);

     <span style="color:red">
     // call this.attachToDom method to attach component to dom element
     // its required for the client side API calls and to clean up after ajax request or page unload:
     // destroy method will be called if component attached to dom
     this.attachToDom(componentId);
     </span>
     };

     // define private method
     var myPrivateMethod = function () {
     }

     // Extend component class and add protected methods from parent class to our container
     richfaces.BaseComponent.extend(richfaces.BaseComponent, richfaces.MyComponent);

     // define super class link
     var $super = richfaces.MyComponent.$super;

     // Add new properties and methods
     $.extend(richfaces.MyComponent.prototype, (function (params) {
     return {
     name:"MyComponent",
     f:function (){alert("hello"),
     // destroy method definition for clean up
     destroy: function () {
     // clean up code here

     // call parent's destroy method
     $super.destroy.call(this);
     }
     }
     };
     })(params));
     })(jQuery, RichFaces);
     </code></pre>
     *
     * @memberOf RichFaces
     * @name BaseComponent
     *
     * @constructor
     * @param {String} componentId - component id
     * */
    rf.BaseComponent = function(componentId) {
        this.id = componentId;
        this.options = this.options || {};
    };

    var $p = {};

    var extend = function (parent, child, h) {
        h = h || {};
        var F = rf.blankFunction;
        F.prototype = parent.prototype;
        child.prototype = new F();
        child.prototype.constructor = child;
        child.$super = parent.prototype;
        if (child.$super == rf.BaseComponent.prototype) {
            var r = jQuery.extend({}, $p, h || {});
        }

        var _parent = child;

        // create wrapper with protected methods and variables
        child.extend = function (_child, _h) {
            _h = _h || {};
            var _r = jQuery.extend({}, r || h || {}, _h || {});
            return extend(_parent, _child, _r);
        }
        return r || h;
    };

    /**
     * Method extends child class prototype with parent prototype
     * and return the object with parent's protected methods
     *
     * @function
     * @name RichFaces.BaseComponent.extend
     *
     * @return {object}
     * */
    rf.BaseComponent.extend = function(child, h) {
        return extend(rf.BaseComponent, child, h);
    };


    /**
     * Easy way to create a subclass.
     *
     * Example:
     *
     * RichFaces.ui.MyClass = RichFaces.BaseComponent.extendClass({
     *     // Class name
     *     name: "MyClass",
     *
     *     // Constructor
     *     init : function (...) {
     *         // ...
     *     },
     *
     *     // public api
     *     publicFunction : function () {
     *         // ...
     *     },
     *
     *     // private api
     *     // names of private methods should start with '__' (2 underscore symbols)
     *     __privateFunction : function () {
     *         // ...
     *     },
     *
     *     __overrideMethod : function () {
     *         // if you need to use method from parent class use link to parent prototype
     *         // like in previous solution with extend method
     *         $super.__overrideMethod.call(this, ...params...);
     *
     *         //...
     *     }
     *
     * });
     *
     * RichFaces.ui.MySecondClass = RichFaces.ui.MyClass({
     *     //
     *     name : "MySecondClass",
     *
     *     // Constructor
     *     init : function (...) {
     *         // ...
     *     }
     *
     * })
     *
     * */
    rf.BaseComponent.extendClass = function (methods) {
        var DerivedClass = methods.init || rf.blankFunction;
        var SupperClass = this;

        SupperClass.extend(DerivedClass);

        DerivedClass.extendClass = SupperClass.extendClass;

        $.extend(DerivedClass.prototype, methods);

        return DerivedClass;
    };

    $.extend(rf.BaseComponent.prototype, (function (params) {
        return {
            /**
             * Component name.
             *
             * @name RichFaces.BaseComponent#name
             * @type String
             * */
            name: "BaseComponent",

            /**
             * Method for converting object to string
             *
             * @function
             * @name RichFaces.BaseComponent#toString
             *
             * @return {String}
             * */
            toString: function() {
                var result = [];
                if (this.constructor.$super) {
                    result[result.length] = this.constructor.$super.toString();
                }
                result[result.length] = this.name;
                return result.join(', ');
            },

            /** TODO: add jsdocs and qunit tests
             *
             */
            getValue: function() {
                return;
            },

            /**
             * Method returns element's id for event handlers binding.
             * Event API calls this method when binding by component object as selector was used.
             *
             * @function
             * @name RichFaces.BaseComponent#getEventElement
             *
             * @return {String}
             * */
            getEventElement: function() {
                return this.id;
            },

            /**
             * Attach component object to DOM element by component id, DOM element or jQuery object and returns the element
             * Its required for the client side API calls and to clean up after ajax request or document unload by
             * calling destroy method
             *
             * @function
             * @name RichFaces.BaseComponent#attachToDom
             * @param {string|DOMElement|jQuery} source - component id, DOM element or DOM elements wrapped by jQuery
             *
             * @return {DOMElement}
             * */
            attachToDom: function(source) {
                source = source || this.id;
                var element = rf.getDomElement(source);
                if (element) {
                    var container = element[rf.RICH_CONTAINER] = element[rf.RICH_CONTAINER] || {};
                    container.component = this;
                }
                return element;
            },

            /**
             * Detach component object from DOM element by component id, DOM element or jQuery object
             *
             * @function
             * @name RichFaces.BaseComponent#detach
             * @param {string|DOMElement|jQuery} source - component id, DOM element or DOM elements wrapped by jQuery
             *
             * */
            detach: function(source) {
                source = source || this.id;
                var element = rf.getDomElement(source);
                element && element[rf.RICH_CONTAINER] && (element[rf.RICH_CONTAINER].component = null);
            },

            /**
             * Invokes event on on the DOM element
             * @param eventType event type, e.g. "click"
             * @param element DOM element object
             * @param event jQuery Event
             * @param data additional data used for event handler
             * @return true if an event is successfully invoked
             */
            invokeEvent: function(eventType, element, event, data) {
                var handlerResult, result;
                var eventObj = $.extend({}, event, {type: eventType});

                if (!eventObj) {
                    if (document.createEventObject) {
                        eventObj = document.createEventObject();
                        eventObj.type = eventType;
                    }
                    else if (document.createEvent) {
                        eventObj = document.createEvent('Events');
                        eventObj.initEvent(eventType, true, false);
                    }
                }
                eventObj[rf.RICH_CONTAINER] = {component:this, data: data};

                var eventHandler = this.options['on' + eventType];

                if (typeof eventHandler == "function") {
                    handlerResult = eventHandler.call(element, eventObj);
                }

                if (rf.Event) {
                    result = rf.Event.callHandler(this, eventType, data);
                }

                if (result != false && handlerResult != false) result = true;

                return result;
            },

            /**
             * Destroy method. Will be called before remove component from the page
             *
             * @function
             * @name RichFaces.BaseComponent#destroy
             *
             * */
            destroy: function() {
            }
        };
    })(params));

    rf.BaseNonVisualComponent = function(componentId) {
        this.id = componentId;
        this.options = this.options || {};
    };

    rf.BaseNonVisualComponent.extend = function(child, h) {
        return extend(rf.BaseNonVisualComponent, child, h);
    };

    rf.BaseNonVisualComponent.extendClass = function (methods) {
        var DerivedClass = methods.init || rf.blankFunction;
        var SupperClass = this;

        SupperClass.extend(DerivedClass);

        DerivedClass.extendClass = SupperClass.extendClass;

        $.extend(DerivedClass.prototype, methods);

        return DerivedClass;
    };

    $.extend(rf.BaseNonVisualComponent.prototype, (function (params) {
        return {
            name: "BaseNonVisualComponent",

            toString: function() {
                var result = [];
                if (this.constructor.$super) {
                    result[result.length] = this.constructor.$super.toString();
                }
                result[result.length] = this.name;
                return result.join(', ');
            },

            getValue: function() {
                return;
            },
            /**
             * Attach component object to DOM element by component id, DOM element or jQuery object and returns the element
             * Its required for the client side API calls and to clean up after ajax request or document unload by
             * calling destroy method
             *
             * @function
             * @name RichFaces.BaseNonVisualComponent#attachToDom
             * @param {string|DOMElement|jQuery} source - component id, DOM element or DOM elements wrapped by jQuery
             *
             * @return {DOMElement}
             * */
            attachToDom: function(source) {
                source = source || this.id;
                var element = rf.getDomElement(source);
                if (element) {
                    var container = element[rf.RICH_CONTAINER] = element[rf.RICH_CONTAINER] || {};
                    if (container.attachedComponents) {
                        container.attachedComponents[this.name] = this;
                    } else {
                        container.attachedComponents = {};
                        container.attachedComponents[this.name] = this;
                    }
                }
                return element;
            },

            /**
             * Detach component object from DOM element by component id, DOM element or jQuery object
             *
             * @function
             * @name RichFaces.BaseNonVisualComponent#detach
             * @param {string|DOMElement|jQuery} source - component id, DOM element or DOM elements wrapped by jQuery
             *
             * */
            detach: function(source) {
                source = source || this.id;
                var element = rf.getDomElement(source);
                element && element[rf.RICH_CONTAINER] && (element[rf.RICH_CONTAINER].attachedComponents[this.name] = null);
            },

            /**
             * Destroy method. Will be called before remove component from the page
             *
             * @function
             * @name RichFaces.BaseNonVisualComponent#destroy
             *
             * */
            destroy: function() {
            }
        };
    })(params));


})(jQuery, window.RichFaces || (window.RichFaces = {}));

// RichFaces Base class for ui components
(function($, rf) {

    rf.ui = rf.ui || {};

    // Constructor definition
    rf.ui.Base = function(componentId, options, defaultOptions) {
        this.namespace = "." + rf.Event.createNamespace(this.name, componentId);
        // call constructor of parent class
        $super.constructor.call(this, componentId);
        this.options = $.extend(this.options, defaultOptions, options);
        this.attachToDom();
        this.__bindEventHandlers();
    };

    // Extend component class and add protected methods from parent class to our container
    rf.BaseComponent.extend(rf.ui.Base);

    // define super class link
    var $super = rf.ui.Base.$super;

    $.extend(rf.ui.Base.prototype, {
            __bindEventHandlers: function () {
            },
            destroy: function () {
                rf.Event.unbindById(this.id, this.namespace);
                $super.destroy.call(this);
            }
        });

})(RichFaces.jQuery, RichFaces);