var ControllerBase = function (options) {
    _.extend(this, options);

    
    this.app = !this.app ? new Backbone.Marionette.Application() : this.app;
    this.rm = this.app._regionManager;
    this.breadcrumb = !this.breadcrumb ? new BreadCrumb() : this.breadcrumb;
    this.actionButtons = new Array();
    
    this.addRegion = function (region) {

        if (!region) {
            return null;
        }
        var regionInstance = this.getRegion(region.name);

        if (regionInstance) {
            return regionInstance;
        }

        regionInstance = this.rm.addRegion(region.name, region.selector);
        regionInstance.name = region.name;
        return regionInstance;
    };

    this.getRegion = function (name) {

        if (!name) {
            return null;
        }

        var region = this.app.getRegion(name);

        if (!region) {
            return null;
        }

        region.$el = $(region.options.el);
        return region;
    };

    this.resetRegion = function (name) {

        var region = this.getRegion(name);

        if (!region) {
            return;
        }

        region.reset();

    };

    this.clearRegion = function () {

        if (!this.region) {
            return;
        }

        this.region.$el.empty();

    };

    this.show = function (regionName) {

        var regionId;

        if (regionName) {

            var region = this.getRegion(regionName);

            if (!region) {
                return;
            }
            region.$el.show();
            regionId = region.el.options;

        } else {
            var $region = $('[region="main"]', $(this.container));
            $region.show();
            regionId = $region.attr('id');
        }

        this.breadcrumb.update(regionId);
        this.renderActionButtons();
    };

    this.addStack = function (item) {

        if (!this.stack) {
            this.stack = new Array();
        }

        this.stack.push(item);

    };

    this.aplicarAspectos = function(instance) {

        $.aop.before({ target: instance, method: 'render' }, function () {
            var el = $(':focus');
            this.focusedElementId = el.attr('id');
            this.focusedElementName = el.attr('name');
            
        });

        $.aop.after({ target: instance, method: 'render' }, function () {
            parametroSistema.aplicarParametros(this.$el);
            global.applyMasks();


            if (this.focusedElementId || this.focusedElementName) {

                var focusElement = this.focusedElementId ? $('#' + this.focusedElementId) : $('[name=' + this.focusedElementName + ']');

                setTimeout($.proxy(function() {
                    focusElement.focus();
                }, this), 100);
            }

        });

        $.aop.after({ target: instance, method: '_renderChildren' }, function () {
            parametroSistema.aplicarParametros(this.$el);
        });

        $.aop.after({ target: instance, method: '_onCollectionAdd' }, function () {
            parametroSistema.aplicarParametros(this.$el);
        });

        var self = this;

        $.aop.after({ target: instance, method: 'buildChildView' }, function (result) {

            self.aplicarAspectos(result);

            return result;
        });

    };

    this.showView = function (args) {

        var self = this;

        if (!args) {
            throw 'Argumentos não informados.';
        }

        var region = null;

        if (typeof args.region != 'undefined') {
            region = this.addRegion(args.region);
        } else {
            throw 'Region não definido';
        }

        this.currentRegion = region;

        var view = null;

        if (typeof args.view != 'undefined') {
            view = args.view;
        }

        var controller = null;

        if (typeof args.controller != 'undefined') {
            controller = args.controller;
        }

        

        if (view) {

            view.typeName = 'view';

            if (this.region) {
                $('[region]', $(this.region.options.el)).hide();
                $('[region]').has(region.$el).show();
            }

            var viewArgs = {
                app: this.app,
                parent: this,
                currentRegion: region
            };

            _.extend(viewArgs, view.args);

            view.instance = new view.type(viewArgs);
            view.instance.controller = this;

            this.aplicarAspectos(view.instance);
            
            if (typeof args.antesRenderizacaoCompleta != 'undefined') {
                args.antesRenderizacaoCompleta();
            }

            region.reset();
            region.show(view.instance);
            region.$el.show();
            
            if (typeof args.showBreadCrumb == 'undefined' || args.showBreadCrumb) {
                view.instance.breadcrumbItem = this.breadcrumb.add({
                    region: region,
                    view: view,
                    controller: {
                        instance: this
                    }
                });
            } else {
                this.breadcrumb.hide();
            }

            this.addStack(view);

            global.applyMasks();

            return view.instance;
        }

        if (controller) {

            controller.typeName = 'controller';

            var controllerArgs = {
                app: this.app,
                parent: this,
                breadcrumb: this.breadcrumb,
                container: args.region.selector,
                region: region,
                currentRegion: region
            };

            _.extend(controllerArgs, controller.args);

            controller.instance = new controller.type(controllerArgs);
            ControllerBase.current = controller.instance;

            if (typeof args.actionButtons != 'undefined') {
                controller.instance.addActionButtons(args.actionButtons);
            }

            if (typeof controller.instance.useActionButtons == 'undefined' || controller.instance.useActionButtons) {
                controller.instance.renderActionButtons();
            }
            


            this.addStack(controller);

            if (typeof controller.instance.ajax != 'undefined') {
                var ajaxData = controller.instance.ajax();

                ajaxData.success = function (data) {
                    data = global.tratarRetorno(data);

                    if (data) {

                        if (self.region) {

                            var $region = $('[region]', $(self.region.options.el));

                            if ($region.length > 0) {
                                $('[region]', $(self.region.options.el)).hide();
                            } else {
                                $(self.region.options.el).hide();
                            }
                            
                            $('[region]').has(region.$el).show();
                        }

                        if (controller.instance.isKnockout()) {

                            var model = null;

                            if (args.model) {
                                model = args.model;
                            }
                            else if (controller.instance.newModel) {
                                model = controller.instance.newModel();
                            }

                            if (model) {
                                controller.instance.setModel(model);
                            }
                        }


                        region.$el.empty();
                        region.$el.html(data);

                        var callback = $.proxy(self.controllerRenderizado, self, controller, region, args);
                        callback();
                    }
                };

                global.ajax(ajaxData);

            } else {
                controller.instance.renderizar($.proxy(this.controllerRenderizado, this, controller, region, args));
            }

            return controller.instance;
        }

        return null;

    };

    this.isKnockout = function() {
        return this.framework && this.framework == 'knockout';
    };

    this.isKnockoutObject = function (instance) {
        
        if (!instance) {
            return false;
        }

        return ko.isComputed(instance) || ko.isObservable(instance);
    };

    this.isBackboneObject = function(instance) {

        if (!instance) {
            return false;
        }

        return instance instanceof Backbone.Model ||
            instance instanceof Backbone.Collection;
    };

    this.isBackbone = function() {
        return !this.framework || this.framework == 'backbone';
    };

    this.controllerRenderizado = function (controller, region, args) {

        if (typeof controller.instance.antesRenderizacaoCompleta != 'undefined') {
            controller.instance.antesRenderizacaoCompleta(region);
        }

        region.$el.show();

        if (typeof args.showBreadCrumb == 'undefined' || args.showBreadCrumb) {

            if ($('[breadcrumb-start]', region.$el).length > 0) {
                controller.instance.breadcrumbItem = this.breadcrumb.add({
                    region: {
                        name: region.name,
                        $el: $('[breadcrumb-start]', region.$el),
                        options: region.options
                    },
                    controller: controller
                });
            } else {
                controller.instance.breadcrumbItem = this.breadcrumb.add({
                    region: region,
                    controller: controller
                });
            }
        } else {
            this.breadcrumb.hide();
        }

        if (typeof controller.instance.renderizado != 'undefined') {
            var callback = $.proxy(controller.instance.renderizado, controller.instance);
            callback();
        }

        if (controller.instance.isKnockout()) {
            ko.cleanNode(region.$el[0]);
        }

        var startCallback = $.proxy(this.start, controller.instance, region.$el);
        //parametroSistema.aplicarParametros(region.$el);
        //global.applyMasks();
        //controller.instance.initializeForm();
        //controller.instance.attachEvents();

        if (controller.instance.isKnockout()) {

            if (typeof controller.instance.antesBinding != 'undefined') {
                var antesBindingCallBack = $.proxy(controller.instance.antesBinding, controller.instance);
                antesBindingCallBack();
            }

            this.applyBindings(controller.instance, region.$el[0], startCallback);

            if (typeof controller.instance.aposBinding != 'undefined') {
                var aposBindingCallBack = $.proxy(controller.instance.aposBinding, controller.instance);
                aposBindingCallBack();
            }

        } else {
            startCallback();
        }

    };

    this.applyBindings = function (model, element, startCallback) {
        if (!model) {
            model = this;
        }

        if (!element) {
            element = this.region.$el[0];
        }

        ko.cleanNode(element);
        ko.applyBindings(model, element);

        if (!startCallback) {
            startCallback = $.proxy(this.start, this, element);
        }

        startCallback();
    };

    this.start = function(region) {

        if (region && !(region instanceof jQuery)) {
            region = $(region);
        } else {
            region = $('body');
        }

        parametroSistema.aplicarParametros(region);
        global.applyMasks();
        this.initializeForm();
        this.attachEvents();

    };

    this.toJS = function (value) {

        if (!value) {
            return null;
        }


        if (this.isKnockout(value)) {
            return ko.mapping.toJS(value);
        }
        else if (this.isBackboneObject(value)) {
            return value.toJSON();
        } else {
            return value;
        }
    },

    this.attachEvents = function () {

        var events = typeof this.events != 'undefined' && this.events ? this.events : null;

        if (!events) {
            return;
        }


        for (var key in events) {

            var arr = key.split(' ');

            if (arr.length == 2) {
                $(arr[1], this.region.$el).on(arr[0], $.proxy(this[events[key]], this));
            }
        }

    };

    this.initializeForm = function () {

        if (typeof this.formConfig == 'undefined' || !this.formConfig) {
            return;
        }

        if (typeof this.formConfig.submit != 'undefined' && this.formConfig.submit) {

            var $submit = $(this.formConfig.submit.selector);

            if ($submit.length > 0) {
                $submit.click($.proxy(this.submitForm, this, 1));
            }

            if (this.formConfig.submit.submitOnEnter) {

                $(this.formConfig.selector).keypress(function(e) {
                    if (e.which == 13) {
                        e.preventDefault();
                        try {
                            $submit.trigger('click');
                        } catch (ex) {
                        }
                        return false;
                    }
                });
            }
        }

        global.configForm(this.formConfig);

    };

    this.pageClick = function (pageData) {
        this.submitForm(pageData.page);
    }

    this.submitForm = function (pagina, e) {

        var self = this;

        if (e) {
            e.preventDefault();
        }

        var $form = $(this.formConfig.selector);

        if (!$form.valid()) {
            return false;
        }

        var submitConfig = this.formConfig.submit;
        var $e = $(e.target);

        if (submitConfig.loadingText) {
            submitConfig.text = $e.html();
            $e.html(submitConfig.loadingText);
        }


        var submitCallback = typeof submitConfig.callback != 'undefined' && submitConfig.callback ? this[submitConfig.callback] : null;
        var ajax = typeof this.formConfig.ajax != 'undefined' && this.formConfig.ajax ? this.formConfig.ajax : null;


        if (submitConfig.beforeSubmit) {
            this[submitConfig.beforeSubmit]();
        }

        var afterSubmitCallback = null;

        if (submitConfig.afterSubmit) {
            afterSubmitCallback = $.proxy(this[submitConfig.afterSubmit], self);
        }

        var modelData = submitConfig.type == 'model'
            ? (submitConfig.modelType && submitConfig.modelType == 'js'
                ? ko.mapping.toJS(this.model)
                : this.model)
            : global.convertFormToJSON({
                element: $form
            });

        if (submitCallback) {
            var callback = $.proxy(submitCallback, self, modelData, e);
            callback();
        }

        if (ajax) {

            var ajaxData = {};

            if (ajax.param) {
                ajaxData[ajax.param] = modelData;
            } else {
                ajaxData = modelData;
            }

            ajax.contentType = 'application/json';
            ajax.data = JSON.stringify(ajaxData);

            ajax.success = function (data) {

                if (submitConfig.text) {
                    $e.html(submitConfig.text);
                }

                if (data) {
                    global.resolveDates(data);
                }

                if (afterSubmitCallback) {
                    afterSubmitCallback(data);
                }
            };

            global.ajax(ajax);
        } else {
            if (submitConfig.text) {
                $e.html(submitConfig.text);
            }
        }
    };

    this.addActionButton = function (args, render) {

        if (!args || typeof args.text == 'undefined' || typeof args.click == 'undefined') {
            return null;
        }

        if (this.existActionButton(args)) {

            if (global.where(this.actionButtons, { text: args.text }) == 0) {
                this.actionButtons.push(args);
            }

            return this.getActionButton(args);
        }

        this.actionButtons.push(args);

        if (render) {
            this.renderActionButtons();
        }

        return this.getActionButton(args);
    };

    this.getActionButtonById = function (id) {
        return $('div[action-buttons] button#' + id);
    };

    this.getActionButton = function (args) {
        return $('div[action-buttons] button:contains("' + args.text + '")');
    };

    this.existActionButton = function (args) {
        return $('div[action-buttons] button:contains("' + args.text + '")').length > 0;
    };

    this.addActionButtons = function (actionButtons) {

        if (!actionButtons) {
            return;
        }

        var self = this;

        $.each(actionButtons, function () {
            self.addActionButton(this, false);
        });

        this.renderActionButtons();
    };

    this.renderActionButtons = function () {

        var el = $('div[action-buttons]');

        if (!el) {
            return;
        }

        el.empty();

        if (this.actionButtons.length == 0) {
            return;
        }
        var self = this;

        $.each(this.actionButtons, function () {

            if (!self.existActionButton(this)) {
                var button = $('<button>', { id: this.id });

                if (this.class) {
                    button.addClass(this.class);
                }

                button.html(this.text);
                button.click($.proxy(this.click, self));
                el.append(button);
            }
        });


    };

    this.hide = function () {

        //if (this.breadcrumbItem) {
        //    this.breadcrumb.remove(this.breadcrumbItem);
        //}

        var self = this;

        if (this.stack && this.stack.length > 0) {
            $.each(this.stack, function () {

                switch (this.typeName) {
                    case 'view':

                        if (typeof this.instance.breadcrumbItem != 'undefined' && this.instance.breadcrumbItem) {
                            self.breadcrumb.remove(this.instance.breadcrumbItem);
                        }

                        break;
                    case 'controller':

                        this.instance.hide();

                        break;
                }

            });
        }


        if (this.region) {

            this.region.$el = $(this.region.options.el);
            this.region.$el.hide();

            this.breadcrumb.update(this.region.name);

        } else {
            this.breadcrumb.removeLast();
        }

    };

    this.createModel = function() {

        if (!this.model) {
            this.model = {};
        }

    };

    this.setModel = function (model) {
        if (model) {
            global.resolveDates(model);
            this.model = ko.mapping.fromJS(model);
        } else {
            this.createModel();
        }
    };

    this.appendToModel = function(args) {
        
        this.createModel();

        if (args.computed) {
            this.model[args.name] = ko.computed(function () {
                return args.computed();
            }, this);
            return;
        }

        if (!args.value) {
            args.value = ko.observable();
        } else {

            switch (typeof args.value) {
            case 'function':
                args.value = $.proxy(args.value, this.model);
                break;
            case 'object':

                if (Array.isArray(args.value)) {
                    //args.value = ko.observableArray(args.value);
                    args.value = ko.mapping.fromJS(args.value);
                } else {
                    args.value = ko.observable(args.value);
                }

                break;
            default:
                args.value = ko.observable(args.value);
            }
        }

        this.model[args.name] = args.value;
        
    };

    this.addEvent = function (name, model, eventSelector, eventHandler) {
        
        ControllerBase.events.addEvent(this, name, model, eventSelector, eventHandler);
    };

    if (this.initialize) {
        this.initialize();
    }

};


var EventController = {
    events: new Array(),

    getEvent: function(name) {
      
        if (!this.events || this.events.length == 0) {
            return;
        }

        var event = null;

        $.each(this.events, function (i) {
            if (this.name == name) {
                event = this;
                event.index = i;
                return false;
            }
        });

        return event;

    },

    removeEventIfExists: function(name) {
        if (!this.events || this.events.length == 0) {
            return;
        }

        var event = null;
        var index = -1;

        $.each(this.events, function(i) {
            if (this.name == name) {
                event = this;
                index = i;
                return false;
            }
        });

        if (!event) {
            return;
        }

        this.events.splice(index, 1);
        event.model.off(event.selector, event.handler);

    },

    addEvent: function(instance, name, model, eventSelector, eventHandler, context, attach) {

        if (instance.name && name.indexOf(instance.name) == -1) {
            name = instance.name + ':' + name;
        }

        this.removeEventIfExists(name);

        if (!context) {
            context = instance;
        }

        var event = {
            name: name,
            instance: instance,
            model: model,
            selector: eventSelector,
            handler: eventHandler,
            context: context
        };

        

        if (typeof attach == 'undefined' || attach) {
            this.enableEvent(event);
        }

        this.events.push(event);
    },

    disableEvents: function(model) {
        
        if (!model._events) {
            return;
        }

        var self = this;

        for (var eventName in model._events) {
            var events = model._events[eventName];

            if (events && events.length > 0) {

                $.each(events, function () {

                    var name = eventName;

                    if (model.name) {
                        name = model.name + ':' + eventName;
                    }

                    var event = self.getEvent(name);
                    
                    if (!event) {
                        event = {
                            name: name,
                            instance: model,
                            model: model,
                            selector: eventName,
                            handler: this.callback,
                            context: this.ctx
                        };
                        
                        self.events.push(event);
                    }

                    event.enabled = false;                    
                });
                
            }

            
            model.off(eventName);
        }
    },

    attachEvents: function(model) {
        
        if (!model.name || !this.events || this.events.length == 0) {
            return;
        }

        var self = this;

        for (var i = this.events.length - 1; i >= 0; i--) {
            var event = this.events[i];

            if (event.model.name == model.name && !event.enabled) {
                self.enableEvent(event);
            }
        }
        
    },
    
    enableEvent: function(event) {
        
        if (!event || event.enabled) {
            return;
        }
        
        event.model.on(event.selector, event.handler, event.context);
        event.enabled = true;

    }

};

ControllerBase.events = EventController;


var _extendControllerBase = function (protoProps, staticProps) {

    var parent = this;
    var child;

    if (protoProps && _.has(protoProps, 'constructor')) {
        child = protoProps.constructor;
    } else {
        child = function () { return parent.apply(this, arguments); };
    }

    _.extend(child, parent, staticProps);

    var Surrogate = function () { this.constructor = child; };
    Surrogate.prototype = parent.prototype;
    child.prototype = new Surrogate;

    if (protoProps) _.extend(child.prototype, protoProps);

    child.__super__ = parent.prototype;

    return child;
}


ControllerBase.extend = _extendControllerBase;

ko.bindingHandlers.enterkey = {
    init: function (element, valueAccessor, allBindings, viewModel) {
        var callback = valueAccessor();
        $(element).keypress(function (event) {
            var keyCode = (event.which ? event.which : event.keyCode);
            if (keyCode === 13) {
                callback.call(viewModel, event);
                return false;
            }
            return true;
        });
    }
};

ko.bindingHandlers.date = {

    init: function (element, valueAccessor, allBindingsAccessor) {
        var underlyingObservable = valueAccessor();

        var interceptor = ko.computed({
            read: function () {
                return global.formatDate(underlyingObservable(), 'DD/MM/YYYY', 'DD/MM/YYYY');
            },

            write: function (newValue) {
                var current = underlyingObservable();

                if (newValue !== current) {
                    underlyingObservable(newValue);
                    underlyingObservable.valueHasMutated();
                }
            }
        });

        ko.applyBindingsToNode(element, { value: interceptor });
    }
};

ko.bindingHandlers.number = {

    init: function (element, valueAccessor, allBindingsAccessor) {
        var underlyingObservable = valueAccessor();

        var interceptor = ko.computed({
            read: function () {
                return global.formatNumber(underlyingObservable());
            },

            write: function (newValue) {
                var current = underlyingObservable();

                if (newValue !== current) {
                    underlyingObservable(newValue);
                    underlyingObservable.valueHasMutated();
                }
            }
        });

        ko.applyBindingsToNode(element, { value: interceptor });
    }
};



