"use strict";

describe("ContextModule.StateHistory", function() {

    it("is promptly created", function() {
        var testedStateHistory = new App.ContextModule.StateHistory();
        
        expect(testedStateHistory).not.toBe(null);
        expect(testedStateHistory.get("maxStackSize")).toEqual(50);
        expect(testedStateHistory.get("currentSerializedState")).toEqual(undefined);
        expect(testedStateHistory.get("compoundChangeDetector")).toEqual(undefined);
    });
    
    it("is promptly created with attributes", function() {
        var testedStateHistory = new App.ContextModule.StateHistory({
            maxStackSize: 40,
            currentSerializedState: 42,
            compoundChangeDetector: function() {}
        });
        
        expect(testedStateHistory).not.toBe(null);
        expect(testedStateHistory.get("maxStackSize")).toEqual(40);
        expect(testedStateHistory.get("currentSerializedState")).toEqual(42);
        expect(typeof testedStateHistory.get("compoundChangeDetector")).toEqual("function");
    });

    it("undoes and redoes", function() {
        var testedStateHistory = new App.ContextModule.StateHistory({
            currentSerializedState: {"a": 42},
        });
        expect(testedStateHistory.canUndo()).toBe(false);
        expect(testedStateHistory.canRedo()).toBe(false);

        testedStateHistory.set("currentSerializedState", {"a": 42});
        expect(testedStateHistory.get("currentSerializedState")).toEqual({"a": 42});
        expect(testedStateHistory.canUndo()).toBe(false);
        expect(testedStateHistory.canRedo()).toBe(false);
        expect(function() {
            testedStateHistory.undo();
        }).toThrow();
        expect(function() {
            testedStateHistory.redo();
        }).toThrow();
        
        testedStateHistory.set("currentSerializedState", {"a": 43, "b": 44});
        expect(testedStateHistory.get("currentSerializedState")).toEqual({"a": 43, "b": 44});
        expect(testedStateHistory.canUndo()).toBe(true);
        expect(testedStateHistory.canRedo()).toBe(false);
        
        // The same object does not create a new state in the undo stack
        testedStateHistory.set("currentSerializedState", {"a": 42});
        expect(testedStateHistory.get("currentSerializedState")).toEqual({"a": 42});
        expect(testedStateHistory.canUndo()).toBe(true);
        expect(testedStateHistory.canRedo()).toBe(false);
        
        testedStateHistory.undo();
        expect(testedStateHistory.get("currentSerializedState")).toEqual({"a": 43, "b": 44});
        expect(testedStateHistory.canUndo()).toBe(true);
        expect(testedStateHistory.canRedo()).toBe(true);

        testedStateHistory.undo();
        expect(testedStateHistory.get("currentSerializedState")).toEqual({"a": 42});
        expect(testedStateHistory.canUndo()).toBe(false);
        expect(testedStateHistory.canRedo()).toBe(true);
        
        expect(function() {
            testedStateHistory.undo();
        }).toThrow();
        
        testedStateHistory.redo();
        expect(testedStateHistory.get("currentSerializedState")).toEqual({"a": 43, "b": 44});
        expect(testedStateHistory.canUndo()).toBe(true);
        expect(testedStateHistory.canRedo()).toBe(true);

        testedStateHistory.redo();
        expect(testedStateHistory.get("currentSerializedState")).toEqual({"a": 42});
        expect(testedStateHistory.canUndo()).toBe(true);
        expect(testedStateHistory.canRedo()).toBe(false);
        
        testedStateHistory.undo();
        expect(testedStateHistory.get("currentSerializedState")).toEqual({"a": 43, "b": 44});
        
        // In current implementation redo stack is wasted even
        // if the new state is exactly the same as the first element there 
        testedStateHistory.set("currentSerializedState", {"a": 42});
        expect(testedStateHistory.get("currentSerializedState")).toEqual({"a": 42});
        expect(testedStateHistory.canUndo()).toBe(true);
        expect(testedStateHistory.canRedo()).toBe(false);

    });
    it("resets", function() {
        var testedStateHistory = new App.ContextModule.StateHistory({
            currentSerializedState: {"a": 42},
        });
        testedStateHistory.set("currentSerializedState", {"a": 43, "b": 44});
        testedStateHistory.set("currentSerializedState", {"a": 43, "b": 45});
        
        testedStateHistory.undo();
        expect(testedStateHistory.canUndo()).toBe(true);
        expect(testedStateHistory.canRedo()).toBe(true);

        testedStateHistory.reset();
        
        expect(testedStateHistory.canUndo()).toBe(false);
        expect(testedStateHistory.canRedo()).toBe(false);
        
    });

    it("trims undo and redo stacks", function() {
        var testedStateHistory = new App.ContextModule.StateHistory({
            maxStackSize: 10,
        });
        for (var i = 20; i >= 0; --i) {
            testedStateHistory.set("currentSerializedState", {"x": i});
        }
        for (var i = 1; i <= 10; i++) {
            testedStateHistory.undo();
            expect(testedStateHistory.get("currentSerializedState")).toEqual({"x": i});
        }
        expect(testedStateHistory.canUndo()).toBe(false);
        expect(testedStateHistory.canRedo()).toBe(true);
        expect(testedStateHistory.get("currentSerializedState")).toEqual({"x": 10});

        testedStateHistory.set("maxStackSize", 5);
        for (var i = 9; i >= 5; --i) {
            testedStateHistory.redo();
            expect(testedStateHistory.get("currentSerializedState")).toEqual({"x": i});
        }

        expect(testedStateHistory.canUndo()).toBe(true);
        expect(testedStateHistory.canRedo()).toBe(false);

        testedStateHistory.set("maxStackSize", 1);
        testedStateHistory.undo();
        expect(testedStateHistory.canUndo()).toBe(false);
        testedStateHistory.redo();
        expect(testedStateHistory.canRedo()).toBe(false);
        
        testedStateHistory.set("maxStackSize", 3);
        testedStateHistory.set("currentSerializedState", {"x": 100});
        testedStateHistory.set("currentSerializedState", {"x": 200});
        testedStateHistory.set("currentSerializedState", {"x": 300});
        testedStateHistory.undo();
        testedStateHistory.undo();
        testedStateHistory.undo();
        expect(testedStateHistory.canUndo()).toBe(false);
    });
    
    it("triggers change:currentSerializedState when needed", function() {
        var spyNames = [
                "change",
                "change:currentSerializedState",
            ];
        var spy = jasmine.createSpyObj("listener", spyNames);
        var expectSpyCallCount = function() {
            _.each(arguments, function(arg, i) {
                //console.log("___", i, "--->", spy[spyNames[i]].calls.count(), arg);
                expect(spy[spyNames[i]].calls.count()).toEqual(arg);
            });
            for (var i = arguments.length; i < spyNames.length; i++) {
                expect(spy[spyNames[i]].calls.count()).toEqual(0);
            }
        };
        var resetSpyCallCount = function() {
            _.each(spyNames, function(spyName) {
                spy[spyName].calls.reset();
            });
        };
        var expectSpyCallCountAndReset = function() {
            expectSpyCallCount.apply(null, arguments);
            resetSpyCallCount();
        };
        
        var testedStateHistory = new App.ContextModule.StateHistory({
            maxStackSize: 10,
        });

        _.each(spyNames, function(spyName) {
            testedStateHistory.on(spyName, spy[spyName]);
        });
        
        testedStateHistory.set("currentSerializedState", {"test": true});
        expectSpyCallCountAndReset(1, 1);

        testedStateHistory.set("currentSerializedState", {"test": true});
        expectSpyCallCountAndReset(0, 0);
        
        testedStateHistory.set("currentSerializedState", {"test": false});
        expectSpyCallCountAndReset(1, 1);
        
        testedStateHistory.undo();
        testedStateHistory.undo();
        expectSpyCallCountAndReset(2, 2);

        testedStateHistory.redo();
        testedStateHistory.redo();
        expectSpyCallCountAndReset(2, 2);
        
        testedStateHistory.undo();
        expectSpyCallCountAndReset(1, 1);
        
        testedStateHistory.set("currentSerializedState", {"test": true});
        expectSpyCallCountAndReset(0, 0);
        
        testedStateHistory.reset();
        expectSpyCallCountAndReset(1, 0);
        testedStateHistory.reset();
        expectSpyCallCountAndReset(0, 0);

    });
});
