/// class PD.Web.UI.Controls.Control

PD.Web.UI.Controls.Control =
Class.extend({

    //===================================
    // FIELDS
    //===================================

    _id: null,
    _$node: null,
    _container: null,
    _children: null,
    _isRendered: null,
    _insertChildrenAt: null,
    _insertSelfAt: null,
    _options: null,
    _parent: null,
    subscriptions: null,

    //===================================
    // CONSTRUCTOR
    //===================================

    init: function (options) {

        this._eventHandlerNames = new PD.BCL.List([
            "onInit",
            "onFrame",
            "onRender",
            "onActivate",
            "onComplete",
        ]);

        this._id = uuid.v4();

        this._options = options || {};

        this._wrapEventMethods();

        this._isRendered = false;

        this.onConstruction(options);

        this._setDomAttachment(options);

    },

    //===================================
    // PROPERTIES
    //===================================

    container: function (value) {
        return this._container;
    },

    get_children: function () {
        this._ensureChildren();
        return this._children;
    },

    get_node: function () {
        return this._$node;
    },

    get_insertChildrenAt: function () {
        return this._insertChildrenAt;
    },

    get_options: function () {
        return this._options;
    },

    get_id: function () {
        return this._id;
    },

    get_uid: function () {
        return `UID${this.get_id()}`;
    },

    //===================================
    // EVENT HANDLERS
    //===================================

    onRequestEventArgs: function (e) {
        return new PD.Web.UI.ControlEventArgs({
            parent: e.parent
        });
    },

    onConstruction: function (e) {
        //overridable
    },

    onInit: function (e) {
        //overridable
    },

    onFrame: function (e) {
        this.setFrame(this);
    },

    onRender: function (e) {
        this._attachToDOM(e);
    },

    onActivate: function (e) {
        //overridable
    },

    onComplete: function (e) {
        //overridable
    },

    onRemove: function (e) {
        //overridable
    },

    //===================================
    // PUBLIC METHODS
    //===================================

    remove: function () {
        this.get_children().each(function (index, child) {
            child.remove();
        });
        this.onRemove({});
        this.get_node().remove();
        this.subscriptions.clear();
    },

    redraw: function () {
        //overridable
    },

    //===================================
    // PROTECTED METHODS
    //===================================

    setFrame: function (data, insertChildrenAt) {
        var frame = this.parseTemplate(data);
        this._$node = $(frame);
        this._$node.attr("id", this._id);
        if (this._options.css) {
            this._$node.css(this._options.css);
        }
        this._insertChildrenAt = insertChildrenAt;
    },

    template: function (data) {
        var template = this._optionTemplate();
        return template ? template : "<div></div>";
    },

    parseTemplate: function (data) {

        var result = this.template(data);

        if (result == null) {
            throw "Call to method \"template\" returned a NULL value.";
        }

        switch (true) {
            case result instanceof jQuery:
                result = result;
            case PD.Type.isArray(result):
                result = result.join("");
                break;
            case PD.Type.isString(result):
                result = result;
                break;
            default:
                throw "The value returned by the \"template\" method is invalid.";
        }

        return result;

    },

    //===================================
    // PRIVATE METHODS
    //===================================

    _onCompleteExit: function () {
        this._isRendered = true;
    },

    _ensureChildren: function () {
        if (this._children === null) {
            this._children = new PD.Web.UI.Controls.ControlCollection(this);
        }
    },

    _wrapEventMethods: function () {

        var that = this,
            fn = null;

        fn = that["onInit"];
        this._wrapAndAssignEventMethod("onInit", function (e) {
            that.subscriptions = new PD.Web.UI.Controls.ControlEventSubscriptionManager({
                eventBus: PD.context.get_default().get_events()
            });
            fn.call(that, e);
        });

        this._wrapAndAssignEventMethod("onFrame");
        this._wrapAndAssignEventMethod("onRender");
        this._wrapAndAssignEventMethod("onActivate");
        this._wrapAndAssignEventMethod("onComplete");

    },

    _wrapAndAssignEventMethod: function (methodName, fn) {

        this[methodName] = this._wrapEventMethod({
            name: methodName,
            fn: fn || this[methodName],
            fnEnter: this["_" + methodName + "Enter"],
            fnExit: this["_" + methodName + "Exit"]
        });

    },

    _wrapEventMethod: function (methodInfo) {

        var that = this;

        return function (e) {

            if (methodInfo.fnEnter != null) {
                methodInfo.fnEnter.call(that, e);
            }

            methodInfo.fn.call(that, e);

            that.get_children().fire(methodInfo.name);

            if (methodInfo.fnExit != null) {
                methodInfo.fnExit.call(that, e);
            }

        };

    },

    _invokeEventMethods: function (parent) {

        var that = this,
            args = this.onRequestEventArgs({
                parent: parent
            });

        this._parent = parent;

        this._eventHandlerNames.each(function (index, methodName) {
            that[methodName](args);
        });

    },

    _optionTemplate: function () {

        var result = this.get_options()["template"];

        if (result && PD.Type.isArray(result)) {
            result = result.join("");
        }

        return result;

    },

    _attachToDOM: function (e) {

        if (this.container() !== null) {
            this.container().append(this.get_node());
            return;
        }

        var children = e.get_parent().get_children(),
            $parentNode = (this._insertSelfAt != null) ? e.get_node().find(this._insertSelfAt) : e.get_insertAtNode(),
            $insertNode = $(),
            countOfChildren = children.count(),
            insertAtIndex = children.indexOf(this),
            insertAtChild = null;

        if (insertAtIndex < countOfChildren) {
            insertAtChild = children.getAtIndex(insertAtIndex + 1);
            if (insertAtChild != null) {
                $insertNode = $parentNode.children("#" + insertAtChild.get_id());
            }
        }

        if ($insertNode.length === 1) {
            $insertNode.before(this.get_node());
        }
        else {
            $parentNode.append(this.get_node());
        }

    },

    _setDomAttachment: function (options) {

        if (options == null) {
            return;
        }

        if (options.container != null) {
            this._setContainerDomNodeAttachment(options);
        }

        if (options.insertAt) {
            this._setParentDomNodeAttachment(options);
        }

    },

    _setContainerDomNodeAttachment: function (options) {

        if (PD.Type.isString(options.container)) {
            this._container = $(options.container);
        }
        else {
            this._container = options.container;
        }

        this._eventBus = new PD.Web.UI.EventBus();
        this._invokeEventMethods(this);

    },

    _setParentDomNodeAttachment: function (options) {
        this._insertSelfAt = options.insertAt;
    }

});