"use strict";

App.module("ContextModule", function(ContextModule, App, Backbone, Marionette, $, _, Logger) {

    // Define private variables
    var logger = null;

    ContextModule.addInitializer(function(options){

        logger = Logger.get("ContextModule.ConfigGrid");
        logger.setLevel(Logger.WARN);

        /**
         * ConfigGrid stores the configuration of a grid that consists of entityConfigs and viewConfigs
         * (both are Backbone.Collection of Config)
         *
         * In real situation entityConfigs are music collection configs and music recording configs
         *
         * two sub-collections can be interacted directly (without proxy methods)
         *
         * The grid can be given a read-only type on creation in order to easily to distinguish between collection and recording grid later
         * getType() method is available for this purpose. The type of the grid is not being serialized or unserialized
         *
         * The following events are triggered:
         *
         *      change_layout
         *          when both or any of the two collections of Config gets new objects, looses objects or shuffles
         *          (but not when parameters in individual parameter bags change)
         *
         *      change_entity:c123
         *      change_view:c123
         *          when a particular parameter bag changes (c123 is replaced with a corresponding client id of an entity or a view)
         *
         *      change_entity_neighbours:c123
         *      change_view_neighbours:c123
         *          when a Config, which is right before or right after the given parameter bag, changes
         *          This includes cases when neighbours are added or removed
         *
         *      change_selection
         *          when selectedEntityConfigClientId or (and) selectedViewConfigClientId change
         *
         *      change
         *          this event is triggered together with any of the above ones, but
         *          maximum once during a complex operation such as unserialize
         */
        ContextModule.ConfigGrid = Backbone.Model.extend({
            defaults: {
                entityConfigs: null,
                viewConfigs: null,
                selectedEntityConfigClientId: null,
                selectedViewConfigClientId: null
            },

            /**
             * @memberOf App.ContextModule.ConfigGrid
             */
            initialize: function(type) {

                this.type = type;

                this.attributes.entityConfigs = new ContextModule.ConfigCollection(null, {
                    comparator: false,
                    configGridType: type,
                    dimension: "entity",
                });
                this.attributes.viewConfigs = new ContextModule.ConfigCollection(null, {
                    comparator: false,
                    configGridType: type,
                    dimension: "view",
                });

                // Shortcuts for entity configs and view configs (for quicker access)
                this.entityConfigs = this.attributes.entityConfigs;
                this.viewConfigs = this.attributes.viewConfigs;

                this._modificationPropagationEnabled = true;
                this._configCollectionsWereModified = false;
                this._configsWereModified = false;
                this._modifiedEntityConfigClientIds = [];
                this._modifiedViewConfigClientIds = [];
                this._lastSavedOrderedEntityClientIds = _.pluck(this.attributes.entityConfigs.models, "cid");
                this._lastSavedOrderedViewClientIds = _.pluck(this.attributes.viewConfigs.models, "cid");
                this._lastSavedSelectedEntityConfigClientId = this.attributes.selectedEntityConfigClientId;
                this._lastSavedSelectedViewConfigClientId = this.attributes.selectedViewConfigClientId;

                this.entityConfigs.bind("add remove reset sort", this._registerModificationOfConfigCollectionForEntities, this);
                this.viewConfigs.bind("add remove reset sort", this._registerModificationOfConfigCollectionForViews, this);

                this.entityConfigs.bind("change", this._registerModificationOfConfig, this);
                this.viewConfigs.bind("change", this._registerModificationOfConfig, this);

                this.bind("change:selectedEntityConfigClientId", this._registerModificationOfAtomicProperty);
                this.bind("change:selectedViewConfigClientId", this._registerModificationOfAtomicProperty);
                this.bind("change:entityWidth", this._registerModificationOfStandardAtomicProperty);
            },

            /**
             * @memberOf App.ContextModule.ConfigGrid
             */
            getType: function() {
                return this.type;
            },

            /**
             * @memberOf App.ContextModule.ConfigGrid
             */
            getPrevEntityNeighbour: function(entityConfig) {
                return this._getNeighbour(this.attributes.entityConfigs, entityConfig, -1);
            },

            /**
             * @memberOf App.ContextModule.ConfigGrid
             */
            getNextEntityNeighbour: function(entityConfig) {
                return this._getNeighbour(this.attributes.entityConfigs, entityConfig, 1);
            },

            /**
             * @memberOf App.ContextModule.ConfigGrid
             */
            getPrevViewNeighbour: function(viewConfig) {
                return this._getNeighbour(this.attributes.viewConfigs, viewConfig, -1);
            },

            /**
             * @memberOf App.ContextModule.ConfigGrid
             */
            getNextViewNeighbour: function(viewConfig) {
                return this._getNeighbour(this.attributes.viewConfigs, viewConfig, 1);
            },

            /**
             * @memberOf App.ContextModule.ConfigGrid
             */
            relocateEntityConfig: function(entityConfig, indexOrNextConfigOrNextConfigClientId) {
                return this._relocate(this.attributes.entityConfigs, entityConfig, indexOrNextConfigOrNextConfigClientId);
            },

            /**
             * @memberOf App.ContextModule.ConfigGrid
             */
            relocateViewConfig: function(viewConfig, indexOrNextConfigOrNextConfigClientId) {
                return this._relocate(this.attributes.viewConfigs, viewConfig, indexOrNextConfigOrNextConfigClientId);
            },

            /**
             * @memberOf App.ContextModule.ConfigGrid
             */
            serialize: function() {
                logger.debug("method called: ConfigGrid::serialize");
                var _this = this;

                var result = {
                        entityConfigs: this.attributes.entityConfigs.map(function(config){ return config.serialize(); }),
                        viewConfigs: this.attributes.viewConfigs.map(function(config){ return config.serialize(); })
                };

                if (this.attributes.selectedEntityConfigClientId) {
                    result.selectedEntityConfigClientId = this.attributes.selectedEntityConfigClientId;
                }
                if (this.attributes.selectedViewConfigClientId) {
                    result.selectedViewConfigClientId = this.attributes.selectedViewConfigClientId;
                }
                if (this.attributes.entityWidth) {
                    result.entityWidth = this.attributes.entityWidth;
                }

                return result;
            },

            getSelectedEntityConfig: function() {
                return this.attributes.entityConfigs.get(this.attributes.selectedEntityConfigClientId);
            },

            getSelectedViewConfig: function() {
                return this.attributes.viewConfigs.get(this.attributes.selectedViewConfigClientId);
            },

//            getSelectedConfigAtGivenDimension: function(dimension) {
//                if (dimension == "entity") {
//                    return this.getSelectedEntityConfig();
//                } else if (dimension == "view") {
//                    return this.getSelectedViewConfig();
//                }
//            },
//
            addEntityAndSelectIt: function(entityConfig, indexOrNextConfigOrNextConfigClientId) {
                this._addAndSelect(this.attributes.entityConfigs, entityConfig, _.isUndefined(indexOrNextConfigOrNextConfigClientId) ? null : indexOrNextConfigOrNextConfigClientId);
            },

            addViewAndSelectIt: function(viewConfig, indexOrNextConfigOrNextConfigClientId) {
                this._addAndSelect(this.attributes.viewConfigs, viewConfig, _.isUndefined(indexOrNextConfigOrNextConfigClientId) ? null : indexOrNextConfigOrNextConfigClientId);
            },

            removeEntityAndSelectNeighbour: function(entityConfig) {
                this._removeAndSelectNeighbour(this.attributes.entityConfigs, entityConfig);
            },

            removeViewAndSelectNeighbour: function(viewConfig) {
                this._removeAndSelectNeighbour(this.attributes.viewConfigs, viewConfig);
            },

            _addAndSelect: function(configCollection, config, indexOrNextConfigOrNextConfigClientId) {
                this._modificationPropagationEnabled = false;

                configCollection.add(config);
                if (configCollection == this.attributes.entityConfigs) {
                    this.attributes.selectedEntityConfigClientId = config.getClientId();
                } else {
                    this.attributes.selectedViewConfigClientId = config.getClientId();
                }
                this._relocate(configCollection, config, indexOrNextConfigOrNextConfigClientId);

                this._triggerModificationEventsIfNeeded();
                this._modificationPropagationEnabled = true;

            },

            _removeAndSelectNeighbour: function(configCollection, config) {
                this._modificationPropagationEnabled = false;

                var neighbourToSelect = this._getNeighbour(configCollection, config, 1);
                if (!neighbourToSelect) {
                    neighbourToSelect = this._getNeighbour(configCollection, config, -1);
                }
                configCollection.remove(config);
                if (configCollection == this.attributes.entityConfigs) {
                    this.attributes.selectedEntityConfigClientId = neighbourToSelect ? neighbourToSelect.getClientId() : null;
                } else {
                    this.attributes.selectedViewConfigClientId = neighbourToSelect ? neighbourToSelect.getClientId() : null;
                }

                this._triggerModificationEventsIfNeeded();
                this._modificationPropagationEnabled = true;

            },

            /**
             * @memberOf App.ContextModule.ConfigGrid
             */
            unserialize: function(serializedAttributes) {
                logger.debug("method called: ConfigGrid::unserialize");

                this._modificationPropagationEnabled = false;

                var fixedSerializedAttributes = serializedAttributes;
                if (!_.isSimpleObject(serializedAttributes)) {
                    logger.warn("ConfigGrid::unserialize called for not an object: ", serializedAttributes);
                    fixedSerializedAttributes = {};
                }

                // entityConfigs
                var newConfigs = [];
                var fixedSerializedConfigs = fixedSerializedAttributes.entityConfigs;
                if (!_.isArray(fixedSerializedConfigs)) {
                    if (_.isSimpleObject(serializedAttributes)) {
                        logger.warn("ConfigGrid::unserialize called for an object with faulty entityConfigs: ", fixedSerializedConfigs);
                    }
                    fixedSerializedConfigs = [];
                };
                for (var i = 0; i < fixedSerializedConfigs.length; i++) {
                    var serializedConfig = fixedSerializedConfigs[i];
                    var config = this.attributes.entityConfigs.get(serializedConfig.clientId);
                    if (!config) {
                        config = new App.ContextModule.Config(serializedConfig);
                    } else {
                        config.unserialize(serializedConfig);
                    }
                    newConfigs.push(config);
                }
                this.attributes.entityConfigs.reset(newConfigs);

                // viewConfigs
                var newConfigs = [];
                var fixedSerializedConfigs = fixedSerializedAttributes.viewConfigs;
                if (!_.isArray(fixedSerializedConfigs)) {
                    if (_.isSimpleObject(serializedAttributes)) {
                        logger.warn("ConfigGrid::unserialize called for an object with faulty viewConfigs: ", fixedSerializedConfigs);
                    }
                    fixedSerializedConfigs = [];
                };
                for (var i = 0; i < fixedSerializedConfigs.length; i++) {
                    var serializedConfig = fixedSerializedConfigs[i];
                    var config = this.attributes.viewConfigs.get(serializedConfig.clientId);
                    if (!config) {
                        config = new App.ContextModule.Config(serializedConfig);
                    } else {
                        config.unserialize(serializedConfig);
                    }
                    newConfigs.push(config);
                }
                this.attributes.viewConfigs.reset(newConfigs);

                // selectedEntityConfigClientId, selectedViewConfigClientId
                this.attributes.selectedEntityConfigClientId = fixedSerializedAttributes.selectedEntityConfigClientId;
                this.attributes.selectedViewConfigClientId = fixedSerializedAttributes.selectedViewConfigClientId;

                this.attributes.entityWidth = fixedSerializedAttributes.entityWidth;

                this._triggerModificationEventsIfNeeded();
                this._modificationPropagationEnabled = true;
            },

            _getNeighbour: function(configCollection, config, offset) {
                var index = configCollection.indexOf(config);
                if (index === -1) {
                    throw _.str.sprintf("Can't find config %s", JSON.stringify(config.serialize()));
                }
                return configCollection.at(index + offset);
            },

            _relocate: function(configCollection, config, indexOrNextConfigOrNextConfigClientId) {
                var clientIds = _.pluck(configCollection.models, "cid");
                var nextConfigClientId = null;

                if (_.isNumber(indexOrNextConfigOrNextConfigClientId) && indexOrNextConfigOrNextConfigClientId != clientIds.length) {
                    nextConfigClientId = clientIds[indexOrNextConfigOrNextConfigClientId];
                }
                if (_.isObject(indexOrNextConfigOrNextConfigClientId)) {
                    nextConfigClientId = indexOrNextConfigOrNextConfigClientId.getClientId();
                }
                if (_.isString(indexOrNextConfigOrNextConfigClientId)) {
                    if (clientIds.indexOf(indexOrNextConfigOrNextConfigClientId) !== -1) {
                        nextConfigClientId = indexOrNextConfigOrNextConfigClientId;
                    }
                }
                if (!nextConfigClientId && !_.isNull(indexOrNextConfigOrNextConfigClientId) && indexOrNextConfigOrNextConfigClientId != configCollection.size()) {
                    throw _.str.sprintf("Wrong value for indexOrNextConfigOrNextConfigClientId %s", indexOrNextConfigOrNextConfigClientId);
                }

                var configClientId = config.getClientId();
                if (!configClientId || clientIds.indexOf(configClientId) == -1) {
                    var flattenedConfig = config;
                    if (_.isObject(flattenedConfig)) {
                        flattenedConfig = JSON.stringify(flattenedConfig);
                    }
                    throw _.str.sprintf("Config %s with cid %s is either not a Config or does not belong to a corresponding configCollection with cids [%s]", flattenedConfig, _.isObject(config ) ? config.getClientId() : undefined, clientIds.join(", "));
                }
                if (configCollection.get(configClientId) !== config) {
                    throw _.str.sprintf("Config %s with cid %s is is a clone of what is stored in the grid. Relocation is not possible.", JSON.stringify(config), config.cid);
                }
                var newClientIds = _.without(clientIds, configClientId);
                if (_.isNull(nextConfigClientId)) {
                    newClientIds.push(configClientId);
                } else {
                    var nextConfigIndex = newClientIds.indexOf(nextConfigClientId);
                    //if (nextConfigIndex)
                    var tempClientIds = newClientIds.slice(0, nextConfigIndex);
                    tempClientIds.push(configClientId);
                    newClientIds = tempClientIds.concat(newClientIds.slice(nextConfigIndex));
                }

                var oldComparator = configCollection.comparator;
                configCollection.comparator = function(model) {
                    return newClientIds.indexOf(model.getClientId());
                };
                configCollection.sort();
                configCollection.comparator = oldComparator;
            },

            _registerModificationOfConfigCollectionForEntities: function(modelOrModels, options) {
                if (!_.isEqual(this._lastSavedOrderedEntityClientIds, _.pluck(this.attributes.entityConfigs.models, "cid"))) {
                    this._configCollectionsWereModified = true;
                    if (this._modificationPropagationEnabled) {
                        this._triggerModificationEventsIfNeeded();
                    };
                }
            },

            _registerModificationOfConfigCollectionForViews: function(modelOrModels, options) {
                if (!_.isEqual(this._lastSavedOrderedViewClientIds, _.pluck(this.attributes.viewConfigs.models, "cid"))) {
                    this._configCollectionsWereModified = true;
                    if (this._modificationPropagationEnabled) {
                        this._triggerModificationEventsIfNeeded();
                    };
                }
            },

            _registerModificationOfConfig: function() {
                for (var i = 0; i < this.attributes.entityConfigs.length; i++) {
                    if(this.attributes.entityConfigs.at(i).hasChanged()) {
                        this._configsWereModified = true;
                        this._modifiedEntityConfigClientIds.push(this.attributes.entityConfigs.at(i).getClientId());
                    }
                }
                for (var i = 0; i < this.attributes.viewConfigs.length; i++) {
                    if(this.attributes.viewConfigs.at(i).hasChanged()) {
                        this._configsWereModified = true;
                        this._modifiedViewConfigClientIds.push(this.attributes.viewConfigs.at(i).getClientId());
                    }
                }
                if (this._modificationPropagationEnabled) {
                    this._triggerModificationEventsIfNeeded();
                };
            },

            _registerModificationOfAtomicProperty: function() {
                if (this._modificationPropagationEnabled) {
                    this._triggerModificationEventsIfNeeded(true);
                };
            },

            _registerModificationOfStandardAtomicProperty: function() {
                if (this._modificationPropagationEnabled) {
                    this._triggerModificationEventsIfNeeded();
                };
            },

            _triggerModificationEventsIfNeeded: function(specialCaseForRegisterModificationOfSelection) {

                var triggeredAtLeastSomething = false;
                if (this._configCollectionsWereModified) {
                    triggeredAtLeastSomething = true;
                    this.trigger("change_layout");
                }

                var newOrderedEntityClientIds = null;
                var newOrderedViewClientIds = null;
                if (this._configCollectionsWereModified || this._modifiedEntityConfigClientIds.length || this._modifiedViewConfigClientIds.length) {
                    newOrderedEntityClientIds = _.pluck(this.attributes.entityConfigs.models, "cid");
                    newOrderedViewClientIds = _.pluck(this.attributes.viewConfigs.models, "cid");

                    // change_entity:c123
                    if (this._modifiedEntityConfigClientIds.length) {
                        for (var i = 0; i < newOrderedEntityClientIds.length; i++) {
                            if (_.indexOf(this._modifiedEntityConfigClientIds, newOrderedEntityClientIds[i]) !== -1) {
                                triggeredAtLeastSomething = true;
                                this.trigger("change_entity:" + newOrderedEntityClientIds[i]);
                            }
                        }
                    }
                    // change_view:c123
                    if (this._modifiedViewConfigClientIds.length) {
                        for (var i = 0; i < newOrderedViewClientIds.length; i++) {
                            if (_.indexOf(this._modifiedViewConfigClientIds, newOrderedViewClientIds[i]) !== -1) {
                                triggeredAtLeastSomething = true;
                                this.trigger("change_view:" + newOrderedViewClientIds[i]);
                            }
                        }
                    }

                    // change_entity_neighbours:c123
                    for (var i = 0; i < newOrderedEntityClientIds.length; i++) {
                        var entityClientId = newOrderedEntityClientIds[i];
                        var oldEntityIndex = this._lastSavedOrderedEntityClientIds.indexOf(entityClientId);
                        if (oldEntityIndex == -1) {
                            continue;
                        }
                        var newPrevEntityClientId = newOrderedEntityClientIds[i - 1];
                        var newNextEntityClientId = newOrderedEntityClientIds[i + 1];
                        var oldPrevEntityClientId = this._lastSavedOrderedEntityClientIds[oldEntityIndex - 1];
                        var oldNextEntityClientId = this._lastSavedOrderedEntityClientIds[oldEntityIndex + 1];

                        if (newPrevEntityClientId != oldPrevEntityClientId
                         || newNextEntityClientId != oldNextEntityClientId
                         || this._modifiedEntityConfigClientIds.indexOf(newPrevEntityClientId) !== -1
                         || this._modifiedEntityConfigClientIds.indexOf(newNextEntityClientId) !== -1
                         ) {
                            triggeredAtLeastSomething = true;
                            this.trigger("change_entity_neighbours:" + entityClientId);
                        };
                    }

                    // change_view_neighbours:c123
                    for (var i = 0; i < newOrderedViewClientIds.length; i++) {
                        var viewClientId = newOrderedViewClientIds[i];
                        var oldViewIndex = this._lastSavedOrderedViewClientIds.indexOf(viewClientId);
                        if (oldViewIndex == -1) {
                            continue;
                        }
                        var newPrevViewClientId = newOrderedViewClientIds[i - 1];
                        var newNextViewClientId = newOrderedViewClientIds[i + 1];
                        var oldPrevViewClientId = this._lastSavedOrderedViewClientIds[oldViewIndex - 1];
                        var oldNextViewClientId = this._lastSavedOrderedViewClientIds[oldViewIndex + 1];

                        if (newPrevViewClientId != oldPrevViewClientId
                         || newNextViewClientId != oldNextViewClientId
                         || this._modifiedViewConfigClientIds.indexOf(newPrevViewClientId) !== -1
                         || this._modifiedViewConfigClientIds.indexOf(newNextViewClientId) !== -1
                         ) {
                            triggeredAtLeastSomething = true;
                            this.trigger("change_view_neighbours:" + viewClientId);
                        };
                    };
                }

                this._lastSavedOrderedEntityClientIds = _.pluck(this.attributes.entityConfigs.models, "cid");
                this._lastSavedOrderedViewClientIds = _.pluck(this.attributes.viewConfigs.models, "cid");

                // Fix selection
                if (!this.attributes.entityConfigs.get(this.attributes.selectedEntityConfigClientId)) {
                    this.attributes.selectedEntityConfigClientId = null;
                }
                if (!this.attributes.viewConfigs.get(this.attributes.selectedViewConfigClientId)) {
                    this.attributes.selectedViewConfigClientId = null;
                }
                if (this._lastSavedSelectedEntityConfigClientId  != this.attributes.selectedEntityConfigClientId
                 || this._lastSavedSelectedViewConfigClientId  != this.attributes.selectedViewConfigClientId
                    ) {
                    if (!specialCaseForRegisterModificationOfSelection) {
                        triggeredAtLeastSomething = true;
                    }
                    this.trigger("change_selection");
                }

                if (this._lastSavedEntityWidth  != this.attributes.entityWidth) {
                    triggeredAtLeastSomething = true;
                }

                this._configCollectionsWereModified = false;
                this._configsWereModified = false;
                this._selectionWasModified = false;
                this._modifiedEntityConfigClientIds = [];
                this._modifiedViewConfigClientIds = [];
                this._lastSavedSelectedEntityConfigClientId  = this.attributes.selectedEntityConfigClientId;
                this._lastSavedSelectedViewConfigClientId    = this.attributes.selectedViewConfigClientId;
                this._lastSavedEntityWidth    = this.attributes.entityWidth;

                if (triggeredAtLeastSomething) {
                    this.trigger("change");
                }
            },
        });
    });
}, Logger);
