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

    it("is promptly created", function() {
        var testedConfig = new App.ContextModule.Config();

        expect(testedConfig).not.toBe(null);

        expect(testedConfig.get("parameters")).not.toBe(null);
        expect(testedConfig.get("parameters").keys().length).toEqual(0);

        expect(testedConfig.get("plannedParameterUpdates")).not.toBe(null);
        expect(testedConfig.get("plannedParameterUpdates").keys().length).toEqual(0);

        expect(_.isString(testedConfig.getClientId())).toBe(true);
    });


    it("is promptly created with parameters", function() {
        var testedConfig = new App.ContextModule.Config({
            parameters: {
                a: 10,
                b: "test"
            }
        });

        expect(testedConfig.getParameterValue("a")).toEqual(10);
        expect(testedConfig.getParameterValue("b")).toEqual("test");
        expect(testedConfig.getParameterValue("c")).toEqual(undefined);
    });

    it("is promptly created with parameters and planned parameter updates", function() {
        var testedConfig = new App.ContextModule.Config({
            parameters: {
                a: 10,
                b: "test"
            },
            plannedParameterUpdates: {
                a: 20,
                c: true
            }
        });

        expect(testedConfig.getParameterValue("a")).toEqual(10);
        expect(testedConfig.getParameterValue("b")).toEqual("test");
        expect(testedConfig.getParameterValue("c")).toEqual(undefined);

        expect(testedConfig.getPlannedParameterValue("a")).toEqual(20);
        expect(testedConfig.getPlannedParameterValue("b")).toEqual("test");
        expect(testedConfig.getPlannedParameterValue("c")).toEqual(true);
    });

    it("sets and gets parameters instantly one by one", function() {
        var testedConfig = new App.ContextModule.Config();

        testedConfig.updateParameter("p1", 10);
        testedConfig.updateParameter("p2", null);
        testedConfig.updateParameter("p3", "string");
        testedConfig.updateParameter("p4", false);
        testedConfig.updateParameter("p5", ["array"]);

        expect(testedConfig.getParameterValue("p1")).toEqual(10);
        expect(testedConfig.getParameterValue("p2")).toEqual(null);
        expect(testedConfig.getParameterValue("p3")).toEqual("string");
        expect(testedConfig.getParameterValue("p4")).toEqual(false);
        expect(testedConfig.getParameterValue("p5")).toEqual(["array"]);
        expect(_.size(testedConfig.attributes.parameters.attributes)).toEqual(5);

        testedConfig.updateParameter("p5", undefined);
        expect(testedConfig.getParameterValue("p5")).toEqual(undefined);

        expect(_.size(testedConfig.attributes.parameters.attributes)).toEqual(4);

        expect(function() {
            testedConfig.updateParameter();
        }).toThrow();
        expect(function() {
            testedConfig.updateParameter(1, 2);
        }).toThrow();
        expect(function() {
            testedConfig.updateParameter(["a", "b", "c"]);
        }).toThrow();
        expect(function() {
            testedConfig.updateParameter({x: 1, y: 2});
        }).toThrow();
    });

    it("sets and gets parameters instantly in bulk", function() {
        var testedConfig = new App.ContextModule.Config();

        testedConfig.updateParameters({
                "p1": 10,
                "p2": 42,
                "p3": false,
                "p100": undefined
            });
        expect(_.size(testedConfig.attributes.parameters.attributes)).toEqual(3);

        testedConfig.updateParameters({
            "p2": null,
            "p3": "string",
            "p4": false,
            "p5": ["array"]
            });

        expect(testedConfig.getParameterValue("p1")).toEqual(10);
        expect(testedConfig.getParameterValue("p2")).toEqual(null);
        expect(testedConfig.getParameterValue("p3")).toEqual("string");
        expect(testedConfig.getParameterValue("p4")).toEqual(false);
        expect(testedConfig.getParameterValue("p5")).toEqual(["array"]);
        expect(_.size(testedConfig.attributes.parameters.attributes)).toEqual(5);

        testedConfig.updateParameters({
            "p3": undefined,
            "p4": undefined,
            "p5": undefined
        });
        expect(testedConfig.getParameterValue("p3")).toEqual(undefined);
        expect(testedConfig.getParameterValue("p4")).toEqual(undefined);
        expect(testedConfig.getParameterValue("p5")).toEqual(undefined);
        expect(_.size(testedConfig.attributes.parameters.attributes)).toEqual(2);

        expect(function() {
            testedConfig.updateParameters();
        }).toThrow();
        expect(function() {
            testedConfig.updateParameters("test", 2);
        }).toThrow();
        expect(function() {
            testedConfig.updateParameters(["a", "b", "c"]);
        }).toThrow();
    });

    it("sets and gets planned parameter updates and applies the updates one by one", function() {
        var testedConfig = new App.ContextModule.Config();

        testedConfig.planParameterUpdate("p1", 10);
        expect(testedConfig.get("plannedParameterUpdates").keys().length).toEqual(1);
        testedConfig.planParameterUpdate("p2", "test");
        expect(testedConfig.get("plannedParameterUpdates").keys().length).toEqual(2);
        testedConfig.planParameterUpdate("p3", undefined);
        expect(testedConfig.get("plannedParameterUpdates").keys().length).toEqual(2);

        expect(testedConfig.get("parameters").keys().length).toEqual(0);

        expect(testedConfig.isPlannedToUpdate("p2")).toBe(true);
        testedConfig.cancelPlannedParameterUpdate("p2");
        expect(testedConfig.isPlannedToUpdate("p2")).toBe(false);
        expect(testedConfig.get("plannedParameterUpdates").keys().length).toEqual(1);
        expect(testedConfig.isPlannedToUpdate("p1")).toBe(true);
        expect(testedConfig.isPlannedToUpdate("p3")).toBe(false);

        expect(testedConfig.hasPlannedParameterUpdates()).toBe(true);

        testedConfig.applyPlannedParameterUpdates();

        testedConfig.getParameterValue("myVar");
        testedConfig.getPlannedParameterValue("myVar");
        testedConfig.isPlannedToUpdate("myVar");

        testedConfig.applyPlannedParameterUpdates();

        expect(function() {
            testedConfig.planParameterUpdate();
        }).toThrow();
        expect(function() {
            testedConfig.planParameterUpdate(1, 2);
        }).toThrow();
        expect(function() {
            testedConfig.planParameterUpdate(["a", "b", "c"]);
        }).toThrow();
        expect(function() {
            testedConfig.planParameterUpdate({x: 1, y: 2});
        }).toThrow();

    });

    it("sets and gets planned parameter updates and applies the updates in bulk", function() {
        var testedConfig = new App.ContextModule.Config();

        testedConfig.planParameterUpdates({"p1": 10});
        expect(testedConfig.get("plannedParameterUpdates").keys().length).toEqual(1);
        testedConfig.planParameterUpdates({"p2": "test", "p3": false, "p4": undefined});
        expect(testedConfig.get("plannedParameterUpdates").keys().length).toEqual(3);

        expect(testedConfig.get("parameters").keys().length).toEqual(0);

        expect(testedConfig.isPlannedToUpdate("p2")).toBe(true);
        testedConfig.planParameterUpdates({"p3": undefined});
        expect(testedConfig.get("plannedParameterUpdates").keys().length).toEqual(2);
        expect(testedConfig.isPlannedToUpdate("p1")).toBe(true);
        expect(testedConfig.isPlannedToUpdate("p2")).toBe(true);
        expect(testedConfig.isPlannedToUpdate("p3")).toBe(false);

        expect(testedConfig.hasPlannedParameterUpdates()).toBe(true);

        testedConfig.applyPlannedParameterUpdates();

        expect(function() {
            testedConfig.planParameterUpdates();
        }).toThrow();
        expect(function() {
            testedConfig.planParameterUpdates("test", 2);
        }).toThrow();
        expect(function() {
            testedConfig.planParameterUpdates(["a", "b", "c"]);
        }).toThrow();
    });

    it("sets planned parameter updates when they are undefined", function() {
        var testedConfig = new App.ContextModule.Config();
        testedConfig.updateParameters({
            "p1": "a",
            "p2": "b"
        });

        var spy = jasmine.createSpy();
        testedConfig.on("change:plannedParameterUpdates", spy);
        expect(spy.calls.count()).toEqual(0);

        var hashForPlannedParameterUpdatesBeforeUpdates1 = testedConfig.getHashForPlannedParameterUpdates();

        testedConfig.planParameterUpdates({
            "p2": undefined,
            "p3": undefined,
        });
        expect(spy.calls.count()).toEqual(1);

        expect(testedConfig.get("parameters").keys().length).toEqual(2);
        expect(testedConfig.get("plannedParameterUpdates").keys().length).toEqual(1);

        expect(testedConfig.isPlannedToUpdate("p1")).toBe(false);
        expect(testedConfig.isPlannedToUpdate("p2")).toBe(true);
        expect(testedConfig.isPlannedToUpdate("p3")).toBe(false);

        expect(testedConfig.getPlannedParameterValue("p1")).toBe("a");
        expect(testedConfig.getPlannedParameterValue("p2")).toBe(undefined);
        expect(testedConfig.getPlannedParameterValue("p3")).toBe(undefined);

        var hashForPlannedParameterUpdatesBeforeUpdates2 = testedConfig.getHashForPlannedParameterUpdates();
        expect(hashForPlannedParameterUpdatesBeforeUpdates2).not.toEqual(hashForPlannedParameterUpdatesBeforeUpdates1);

        testedConfig.applyPlannedParameterUpdates();
        expect(spy.calls.count()).toEqual(2);

        expect(testedConfig.get("parameters").keys().length).toEqual(1);
        expect(testedConfig.get("plannedParameterUpdates").keys().length).toEqual(0);

        var hashForPlannedParameterUpdatesBeforeUpdates3 = testedConfig.getHashForPlannedParameterUpdates();

        expect(hashForPlannedParameterUpdatesBeforeUpdates3).not.toEqual(hashForPlannedParameterUpdatesBeforeUpdates2);
        expect(hashForPlannedParameterUpdatesBeforeUpdates3).toEqual(hashForPlannedParameterUpdatesBeforeUpdates1);
    });

    it("cancels planned changes on demand", function() {
        var testedConfig = new App.ContextModule.Config();

        testedConfig.updateParameter("p1", 10);

        expect(testedConfig.hasPlannedParameterUpdates()).toBe(false);

        testedConfig.planParameterUpdate("p1", 11);
        testedConfig.planParameterUpdate("p2", "test");

        expect(testedConfig.hasPlannedParameterUpdates()).toBe(true);

        testedConfig.cancelPlannedParameterUpdate("p2");
        expect(testedConfig.hasPlannedParameterUpdates()).toBe(true);
        testedConfig.cancelPlannedParameterUpdate("p1");
        expect(testedConfig.hasPlannedParameterUpdates()).toBe(false);

        testedConfig.planParameterUpdate("p1", 11);
        testedConfig.planParameterUpdate("p2", "test");
        expect(testedConfig.hasPlannedParameterUpdates()).toBe(true);

        testedConfig.cancelPlannedParameterUpdates();
        expect(testedConfig.hasPlannedParameterUpdates()).toBe(false);

        testedConfig.planParameterUpdate("p1", 11);
        testedConfig.planParameterUpdate("p2", "test");
        testedConfig.planParameterUpdate("p3", "test");
        testedConfig.cancelPlannedParameterUpdates(["p2", "p3"]);
        expect(testedConfig.hasPlannedParameterUpdates()).toBe(true);
        testedConfig.cancelPlannedParameterUpdates(["p100"]);
        expect(testedConfig.hasPlannedParameterUpdates()).toBe(true);
        testedConfig.cancelPlannedParameterUpdates(["p1"]);
        expect(testedConfig.hasPlannedParameterUpdates()).toBe(false);


    });

    xit("returns config grid type and dimension", function() {
        //testedConfig.getConfigGridType();
        //testedConfig.getDimension();
    });

    xit("calculates and obtains its own hashes", function() {
        //testedConfig.getHashForParameters()
        //testedConfig.getHashForPlannedParameterUpdates()
        //testedConfig.getHashForPermanent()
        //testedConfig.getHashForTemp()
        //testedConfig.getHash()
    });

    it("serializes and unserializes itself", function() {
        var testedConfig = new App.ContextModule.Config();
        var clientId = testedConfig.getClientId();
        var originalSerializedObject = {
                "clientId": clientId,
                "parameters": {
                    "p1": 10,
                    "p2": "test",
                    "p3": false,
                    "p4": null,
                    "p5": [1, "test"],
                },
                "plannedParameterUpdates": {
                    "p1": 11,
                    "p3": undefined,
                    "p4": ["10", null],
                }
            };
        testedConfig.unserialize(originalSerializedObject);

        expect(testedConfig.getParameterValue("p1")).toEqual(10);
        expect(testedConfig.getParameterValue("p2")).toEqual("test");
        expect(testedConfig.getParameterValue("p3")).toEqual(false);
        expect(testedConfig.getParameterValue("p4")).toEqual(null);
        expect(testedConfig.getParameterValue("p5")).toEqual([1, "test"]);
        expect(testedConfig.getParameterValue("p9")).toEqual(undefined);

        expect(testedConfig.getPlannedParameterValue("p1")).toEqual(11);
        expect(testedConfig.getPlannedParameterValue("p2")).toEqual("test");
        expect(testedConfig.getPlannedParameterValue("p3")).toEqual(undefined);
        expect(testedConfig.getPlannedParameterValue("p4")).toEqual(["10", null]);
        expect(testedConfig.getPlannedParameterValue("p5")).toEqual([1, "test"]);
        expect(testedConfig.getPlannedParameterValue("p9")).toEqual(undefined);

        expect(testedConfig.isPlannedToUpdate("p1")).toEqual(true);
        expect(testedConfig.isPlannedToUpdate("p2")).toEqual(false);
        expect(testedConfig.isPlannedToUpdate("p3")).toEqual(true);
        expect(testedConfig.isPlannedToUpdate("p4")).toEqual(true);
        expect(testedConfig.isPlannedToUpdate("p5")).toEqual(false);
        expect(testedConfig.isPlannedToUpdate("p9")).toEqual(false);

        expect(testedConfig.serialize()).toEqual(originalSerializedObject);

        testedConfig.applyPlannedParameterUpdates();

        expect(testedConfig.serialize()).toEqual({
            "clientId": clientId,
            "parameters": {
                "p1": 11,
                "p2": "test",
                "p4": ["10", null],
                "p5": [1, "test"]
            },
            "plannedParameterUpdates": {
            }
        });

        testedConfig.planParameterUpdate("p1", 12);
        testedConfig.planParameterUpdate("p2", undefined);
        testedConfig.planParameterUpdate("p5", [1, 2, 3]);
        testedConfig.planParameterUpdate("p9", undefined);

        expect(testedConfig.serialize()).toEqual({
            "clientId": clientId,
            "parameters": {
                "p1": 11,
                "p2": "test",
                "p4": ["10", null],
                "p5": [1, "test"]
            },
            "plannedParameterUpdates": {
                "p1": 12,
                "p2": undefined,
                "p5": [1, 2, 3]
            }
        });

        expect(function(){
            originalSerializedObject.clientId = 42;
            testedConfig.unserialize(originalSerializedObject);
        }).toThrow();
    });

    it("unserializes itself from faulty serialized objects", function() {
        // Config can be unserialized from anything...
        var faultySerializedObjectPairs = [
               [null,
                    {parameters: {}, plannedParameterUpdates: {}}],
               [undefined,
                    {parameters: {}, plannedParameterUpdates: {}}],
               [42,
                    {parameters: {}, plannedParameterUpdates: {}}],
               ["test",
                   {parameters: {}, plannedParameterUpdates: {}}],
               [{parameters: 42},
                   {parameters: {}, plannedParameterUpdates: {}}],
               [{parameters: ["foo", "bar"]},
                   {parameters: {}, plannedParameterUpdates: {}}],
               [{plannedParameterUpdates: 10},
                   {parameters: {}, plannedParameterUpdates: {}}],
               [{plannedParameterUpdates: {"x": 10}},
                {parameters: {}, plannedParameterUpdates: {"x": 10}}]
           ];


        _.each(faultySerializedObjectPairs, function(faultySerializedObjectPair) {
            var testedConfig = new App.ContextModule.Config(faultySerializedObjectPair[0]);
            faultySerializedObjectPair[1].clientId = testedConfig.getClientId();
            expect(testedConfig.serialize()).toEqual(faultySerializedObjectPair[1]);

            var testedConfig2 = new App.ContextModule.Config();
            testedConfig2.unserialize(faultySerializedObjectPair[0]);
            faultySerializedObjectPair[1].clientId = testedConfig2.getClientId();
            expect(testedConfig2.serialize()).toEqual(faultySerializedObjectPair[1]);
        });

        // ... except for when clientId does not match the internal clientId()
        var faultySerializedObjects = [
               {clientId: "wrong"},
               {clientId: 0},
               {parameters: 42, clientId: "wrong"},
               {plannedParameterUpdates: {"x": "y"}, clientId: 42},
           ];

        var testedConfig = new App.ContextModule.Config();

        _.each(faultySerializedObjects, function(faultySerializedObject) {
            expect(function() {
                testedConfig.unserialize(faultySerializedObject);
            }).toThrow();
        });
    });

    it("clones itself", function() {
        var testedConfig = new App.ContextModule.Config({
            parameters: {
                a: 10,
                b: "test"
            },
            plannedParameterUpdates: {
                a: 20,
                c: true
            }
        });

        var clonedConfig = testedConfig.clone();

        expect(clonedConfig.getClientId()).not.toEqual(testedConfig.getClientId());

        expect(clonedConfig.getParameterValue("a")).toEqual(10);
        expect(clonedConfig.getParameterValue("b")).toEqual("test");
        expect(clonedConfig.getParameterValue("c")).toEqual(undefined);

        expect(clonedConfig.getPlannedParameterValue("a")).toEqual(20);
        expect(clonedConfig.getPlannedParameterValue("b")).toEqual("test");
        expect(clonedConfig.getPlannedParameterValue("c")).toEqual(true);

        clonedConfig.updateParameter("a", 42);
        clonedConfig.planParameterUpdate("a", 43);

        expect(clonedConfig.getParameterValue("a")).toEqual(42);
        expect(clonedConfig.getPlannedParameterValue("a")).toEqual(43);
        expect(testedConfig.getParameterValue("a")).toEqual(10);
        expect(testedConfig.getPlannedParameterValue("a")).toEqual(20);

    });

    it("triggers events when real changes occur", function() {
        var testedConfig = new App.ContextModule.Config();

        var spy = jasmine.createSpyObj("listener", ["change", "changeParameters", "changePlannedParameterUpdates"]);

        var expectSpyCallCountAndReset = function(one, two, three) {
//            console.log("~~~~~");
//            console.log("1", spy.change.calls.count(), one);
//            console.log("2", spy.changeParameters.calls.count(), two);
//            console.log("3", spy.changePlannedParameterUpdates.calls.count(), three);
            expect(spy.change.calls.count()).toEqual(one);
            expect(spy.changeParameters.calls.count()).toEqual(two);
            expect(spy.changePlannedParameterUpdates.calls.count()).toEqual(three);
            spy.changeParameters.calls.reset();
            spy.changePlannedParameterUpdates.calls.reset();
            spy.change.calls.reset();
        };

        testedConfig.on("change", spy.change, spy);
        testedConfig.on("change:parameters", spy.changeParameters, spy);
        testedConfig.on("change:plannedParameterUpdates", spy.changePlannedParameterUpdates, spy);

        expect(spy.change.calls.count()).toEqual(0);
        expect(spy.changeParameters.calls.count()).toEqual(0);
        expect(spy.changePlannedParameterUpdates.calls.count()).toEqual(0);

        testedConfig.planParameterUpdate("p1", 12);
        expectSpyCallCountAndReset(1, 0, 1);

        testedConfig.planParameterUpdate("p1", 12);
        expectSpyCallCountAndReset(0, 0, 0);

        testedConfig.planParameterUpdate("p2", 42);
        expectSpyCallCountAndReset(1, 0, 1);

        testedConfig.cancelPlannedParameterUpdate("p2");
        expectSpyCallCountAndReset(1, 0, 1);

        testedConfig.applyPlannedParameterUpdates();
        expectSpyCallCountAndReset(1, 1, 1);

        testedConfig.unserialize({"clientId": testedConfig.getClientId(), "parameters": {x: 42}, "plannedParameterUpdates": {}});
        expectSpyCallCountAndReset(1, 1, 0);
        testedConfig.unserialize({"clientId": testedConfig.getClientId(), "parameters": {x: 42}, "plannedParameterUpdates": {}});
        expectSpyCallCountAndReset(0, 0, 0);

        testedConfig.updateParameter("p10", 10);
        expectSpyCallCountAndReset(1, 1, 0);

        testedConfig.planParameterUpdate("p1", 12);
        expectSpyCallCountAndReset(1, 0, 1);
        testedConfig.updateParameter("p1", 100);
        expectSpyCallCountAndReset(1, 1, 1);

        console.log("!!!", testedConfig);
        testedConfig.cancelPlannedParameterUpdates();
        expectSpyCallCountAndReset(0, 0, 0);

        testedConfig.planParameterUpdate("p1", 12);
        expectSpyCallCountAndReset(1, 0, 1);
        testedConfig.unserialize(null);
        expectSpyCallCountAndReset(1, 1, 1);
        testedConfig.unserialize(null);
        expectSpyCallCountAndReset(0, 0, 0);

        // BULK
        testedConfig.updateParameters({"p1": 42, "p2": 0});
        expectSpyCallCountAndReset(1, 1, 0);
        testedConfig.planParameterUpdate("p1", 42);
        expectSpyCallCountAndReset(0, 0, 0);
        testedConfig.planParameterUpdates({"p1": 42});
        expectSpyCallCountAndReset(0, 0, 0);
        testedConfig.planParameterUpdates({"p1": 4, "p2": 2, "p3": 3, "p4": 4});
        expectSpyCallCountAndReset(1, 0, 1);
        testedConfig.cancelPlannedParameterUpdates(["p3", "p4"]);
        expectSpyCallCountAndReset(1, 0, 1);
        testedConfig.cancelPlannedParameterUpdates(["p3", "p4", "p42"]);
        expectSpyCallCountAndReset(0, 0, 0);
        testedConfig.applyPlannedParameterUpdates();
        expectSpyCallCountAndReset(1, 1, 1);
        testedConfig.planParameterUpdates({"p1": 4, "p2": 2, "p3": 3, "p4": 4});
        expectSpyCallCountAndReset(1, 0, 1);
        testedConfig.cancelPlannedParameterUpdates();
        expectSpyCallCountAndReset(1, 0, 1);

        expect(testedConfig.hasChanged()).toEqual(false);
        testedConfig.on("change", function() {
            expect(testedConfig.hasChanged()).toEqual(true);
        });

        testedConfig.updateParameter("test", 42);

        expect(testedConfig.hasChanged()).toEqual(false);

    });
});
