"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.StateBookmark");
//        logger.setLevel(Logger.DEBUG);
        
        /**
         * StateHistory stores a history of a serialized state
         * and provides an API to switch between these states
         * 
         * A user simply updates the value of currentSerializedState attribute
         * and calls undo(), redo() or reset() to move along the history
         * There is no direct access to undo and redo stacks, but it is possible
         * to know about them by calling canUndo() and canRedo()
         * 
         * serialize / unserialize deal with the object of the following structure
         *      {
         *          undoStack: [{recent}, {less recent}, ... {the oldest}]
         *          redoStack: [{next}, {the following}, ... {the last}]
         *          currentSerializedState: {}
         *      }
         *      
         * attribute compoundChangeDetector is a function that helps decide if the changes between
         *      the given three serialized states can be treated as a single change.
         *      This is needed to avoid too many undo states on simple actions like text typing
         *      compoundChangeDetector returns true if the change between the first, the second
         *      and the third object can be considered as a single compound change
         *      (i.e. there is no need to create an extra undo action)
         */
        ContextModule.StateHistory = Backbone.Model.extend({
            defaults: {
                maxStackSize: 50,
                currentSerializedState: undefined,
                compoundChangeDetector: undefined
            },

            _NONE: 0,
            _UNDO: 1,
            _REDO: 2,
            _UNSERIALIZE: 3,
            
            /**
             * @memberOf App.ContextModule.StateBookmark
             */
            initialize: function() {
                this._undoStack = []; 
                this._redoStack = [];
                this._currentSerializedStateChangeMode = this._NONE;
                
                this.on("change:maxStackSize", this._trimStacks, this);
                this.on("change:currentSerializedState", this._registerNewSerializedState, this);
            },

            _registerNewSerializedState: function() {
                var previousSerialisedState = this.previous("currentSerializedState");
                if (this._currentSerializedStateChangeMode == this._UNDO) {
                    this._redoStack.unshift(previousSerialisedState);
                } else if (this._currentSerializedStateChangeMode == this._REDO) {
                    this._undoStack.unshift(previousSerialisedState);
                } else if (this._currentSerializedStateChangeMode == this._UNSERIALIZE) {
                } else {
                    if (!_.isEqual(previousSerialisedState, this.attributes.currentSerializedState)) {
                        if (this._undoStack.length == 0
                            || !_.isFunction(this.attributes.compoundChangeDetector)
                            || !this.attributes.compoundChangeDetector.call(currentSerializedState, previousSerialisedState, this._undoStack[0]))
                        {
                            this._undoStack.unshift(previousSerialisedState);
                        }
                        this._redoStack = [];
                    }
                }
                this._trimStacks();
            },
            
            getCurrentSerializedState: function() {
                
            },
            
            undo: function() {
                if (this._undoStack.length) {
                    this._currentSerializedStateChangeMode = this._UNDO;
                    this.set("currentSerializedState", this._undoStack.shift());
                    this._currentSerializedStateChangeMode = this._NONE;
                } else {
                    throw "Undo was called when undo stack was empty";
                }
            },
            
            redo: function() {
                if (this._redoStack.length) {
                    this._currentSerializedStateChangeMode = this._REDO;
                    this.set("currentSerializedState", this._redoStack.shift());
                    this._currentSerializedStateChangeMode = this._NONE;
                } else {
                    throw "Redo was called when undo stack was empty";
                }
            },
            
            reset: function() {
                var stacksWereNotEmpty = this._undoStack.length || this._redoStack.length;
                this._undoStack = [];
                this._redoStack = [];
                if (stacksWereNotEmpty) {
                    this.trigger("change");
                }
            },
            
            canUndo: function() {
                return !!this._undoStack.length;
            },
            
            canRedo: function() {
                return !!this._redoStack.length;
            },
            
            serialize: function() {
//                logger.debug("method called: StateHistory::serialize");

                var result = {
                        currentSerializedState: this.attributes.currentSerializedState,
                        undoStack: _.clone(this._undoStack),
                        redoStack: _.clone(this._redoStack),
                };

                return result;
            },

            unserialize: function(serializedAttributes) {
                this._currentSerializedStateChangeMode = this._UNSERIALIZE;
                
                var fixedSerializedAttributes = serializedAttributes;
                if (!_.isSimpleObject(serializedAttributes)) {
                    fixedSerializedAttributes = {};
                }
                
                this._undoStack = _.isArray(fixedSerializedAttributes.undoStack) ?
                        fixedSerializedAttributes.undoStack : [];
                this._redoStack = _.isArray(fixedSerializedAttributes.redoStack) ?
                        fixedSerializedAttributes.redoStack : [];
                this._trimStacks();

                this.set("currentSerializedState", fixedSerializedAttributes.currentSerializedState);

                this._currentSerializedStateChangeMode = this._NONE;
            },
            
            _trimStacks: function() {
                if (this._undoStack.length > this.attributes.maxStackSize) {
                    this._undoStack = this._undoStack.slice(0, this.attributes.maxStackSize);
                }
                if (this._redoStack.length > this.attributes.maxStackSize) {
                    this._redoStack = this._redoStack.slice(0, this.attributes.maxStackSize);
                }
            }
        });

    });
}, Logger);
