/// <reference path="../Scripts/References.js" />

(function (upshot, ko, undefined) {

    module("ChangeTracking");

    var observability = upshot.observability;

    test("Explicit commit multiple property edits", 5, function () {
        var submitCount = 0;
        var propertyChangedCount = 0;

        var dc = createTestContext(function () {
            submitCount++;
        }, true);

        var entitySet = dc.getEntitySet("Product");
        var prod = entitySet.getEntities()[0];
        var state = entitySet.getEntityState(prod);
        equal(state, upshot.EntityState.Unmodified);

        entitySet.bind("propertyChanged", function () {
            propertyChangedCount++;
        });

        $.observable(prod).property("Name", "xyz");
        $.observable(prod).property("Name", "foo");
        $.observable(prod).property("Price", 9.99);

        equal(propertyChangedCount, 3);

        state = entitySet.getEntityState(prod);
        equal(state, upshot.EntityState.ClientUpdated);

        equal(submitCount, 0);

        dc.commitChanges();

        equal(submitCount, 1);
    });

    asyncTest("Implicit commit multiple property edits", 5, function () {
        var submitCount = 0;
        var propertyChangedCount = 0;

        var dc = createTestContext(function () {
            submitCount++;
        }, false);

        var entitySet = dc.getEntitySet("Product");
        var prod = entitySet.getEntities()[0];
        var state = entitySet.getEntityState(prod);
        equal(state, upshot.EntityState.Unmodified);

        entitySet.bind("propertyChanged", function () {
            propertyChangedCount++;
        });

        $.observable(prod).property("Name", "xyz");
        $.observable(prod).property("Name", "foo");
        $.observable(prod).property("Price", 9.99);

        // before the timeout has expired we shouldn't have committed anything
        equal(submitCount, 0);

        equal(propertyChangedCount, 3);

        state = entitySet.getEntityState(prod);
        equal(state, upshot.EntityState.ClientUpdated);

        // Here we queue the test verification and start so that it runs
        // AFTER the queued commit
        setTimeout(function () {
            equal(submitCount, 1);
            start();
        }, 0);

    });

    asyncTest("Implicit commit multiple array operations", 5, function () {
        var submitCount = 0;

        var dc = createTestContext(function (options, editedEntities) {
            submitCount++;
            equal(editedEntities.length, 2);
        }, false);

        var entitySet = dc.getEntitySet("Product");
        var prod = entitySet.getEntities()[0];

        // do an insert
        var newProd = {
            ID: 2,
            Name: "Frish Gnarbles",
            Category: "Snacks",
            Price: 7.99
        };
        $.observable(entitySet.getEntities()).insert(newProd);
        var state = entitySet.getEntityState(newProd);
        equal(state, upshot.EntityState.ClientAdded);

        // do a delete
        entitySet.deleteEntity(prod);
        state = entitySet.getEntityState(prod);
        equal(state, upshot.EntityState.ClientDeleted);

        // before the timeout has expired we shouldn't have committed anything
        equal(submitCount, 0);

        // Here we queue the test verification and start so that it runs
        // AFTER the queued commit
        setTimeout(function () {
            equal(submitCount, 1);
            start();
        }, 0);
    });

/* TODO: We forced managed associations on for Dev11 beta, since unmanaged associations are broken.
    test("Nested entities can be added to an entity, and navigation properties are untracked", 16, function () {
        try {
            upshot.observability.configuration = observability.knockout;

            var manageAssociations = false;
            var dc = createKoTestContext(function () { }, false, manageAssociations);

            var orders = dc.getEntitySet("Order"),
                orderDetails = dc.getEntitySet("OrderDetail"),
                order = orders.getEntities()()[0],
                order2 = orders.getEntities()()[1],
                orderDetail = orderDetails.getEntities()()[0];

            order.OrderDetails.remove(orderDetail);
            equal(1, order.OrderDetails().length, "There should only be a single order detail");
            equal(upshot.EntityState.Unmodified, orders.getEntityState(order), "The order should not be modified");
            equal(upshot.EntityState.Unmodified, orderDetails.getEntityState(orderDetail), "The order detail should not be modified");

            order.OrderDetails.push(orderDetail);
            equal(2, order.OrderDetails().length, "There should be two order details");
            equal(upshot.EntityState.Unmodified, orders.getEntityState(order), "The order should not be modified");
            equal(upshot.EntityState.Unmodified, orderDetails.getEntityState(orderDetail), "The order detail should not be modified");

            orderDetail.Order(order2);
            equal(orderDetail.OrderId(), order2.Id(), "Ids should be equal");
            equal(upshot.EntityState.Unmodified, orders.getEntityState(order2), "The order should not be modified");
            equal(upshot.EntityState.ClientUpdated, orderDetails.getEntityState(orderDetail), "The order detail should not be modified");
            equal(true, orderDetails.isUpdated(orderDetail, "OrderId"), "The OrderId should be modified");
            equal(false, orderDetails.isUpdated(orderDetail, "Order"), "The Order should not be tracked");

            var properties = [];
            $.each(observability.knockout.unmap(orderDetail, "OrderDetail"), function (key, value) {
                properties.push(key);
                equal(ko.utils.unwrapObservable(orderDetail[key]), value, "Properties should be equal");
            });
            equal(properties.length, 4, "The should be 4 serialized properties");

        } finally {
            upshot.observability.configuration = observability.jquery;
        }
    });
*/

    test("Nested entities can be added to an entity, and navigation properties are computed", 39, function () {
        try {
            upshot.observability.configuration = observability.knockout;

            var dc = createKoTestContext(function () { }, false);

            var orders = dc.getEntitySet("Order"),
                orderDetails = dc.getEntitySet("OrderDetail"),
                order = orders.getEntities()()[0],
                order2 = orders.getEntities()()[1],
                orderDetail = orderDetails.getEntities()()[0];

            ok(order.OrderDetails.indexOf(orderDetail) >= 0 && order2.OrderDetails.indexOf(orderDetail) < 0 && orderDetail.Order() === order, "The order detail is a child of order1");
            equal(orders.getEntityState(order), upshot.EntityState.Unmodified, "order1 should not be modified");
            equal(orders.getEntityState(order2), upshot.EntityState.Unmodified, "order2 should not be modified");
            equal(orderDetails.getEntityState(orderDetail), upshot.EntityState.Unmodified, "The order detail should not be modified");
            equal(orderDetails.isUpdated(orderDetail, "OrderId"), false, "The OrderId should not be modified");
            equal(orderDetails.isUpdated(orderDetail, "Order"), false, "The Order should not be tracked");

            orderDetail.OrderId(order2.Id());
            ok(order2.OrderDetails.indexOf(orderDetail) >= 0 && order.OrderDetails.indexOf(orderDetail) < 0 && orderDetail.Order() === order2, "The order detail is a child of order2");
            equal(orders.getEntityState(order), upshot.EntityState.Unmodified, "order1 should not be modified");
            equal(orders.getEntityState(order2), upshot.EntityState.Unmodified, "order2 should not be modified");
            equal(orderDetails.getEntityState(orderDetail), upshot.EntityState.ClientUpdated, "The order detail should be ClientUpdated");
            equal(orderDetails.isUpdated(orderDetail, "OrderId"), true, "The OrderId should be modified");
            equal(orderDetails.isUpdated(orderDetail, "Order"), false, "The Order should not be tracked");

            orderDetails.revertUpdates(orderDetail);
            ok(order.OrderDetails.indexOf(orderDetail) >= 0 && order2.OrderDetails.indexOf(orderDetail) < 0 && orderDetail.Order() === order, "The order detail is a child of order1");
            equal(orders.getEntityState(order), upshot.EntityState.Unmodified, "order1 should not be modified");
            equal(orders.getEntityState(order2), upshot.EntityState.Unmodified, "order2 should not be modified");
            equal(orderDetails.getEntityState(orderDetail), upshot.EntityState.Unmodified, "The order detail should not be modified");
            equal(orderDetails.isUpdated(orderDetail, "OrderId"), false, "The OrderId should not be modified");
            equal(orderDetails.isUpdated(orderDetail, "Order"), false, "The Order should not be tracked");

            orderDetail.Order(order2);
            ok(orderDetail.OrderId() === order2.Id(), "Ids should be equal");
            equal(orders.getEntityState(order2), upshot.EntityState.Unmodified, "The order should not be modified");
            equal(orderDetails.getEntityState(orderDetail), upshot.EntityState.ClientUpdated, "The order detail should be ClientUpdated");
            equal(orderDetails.isUpdated(orderDetail, "OrderId"), true, "The OrderId should be modified");
            equal(orderDetails.isUpdated(orderDetail, "Order"), false, "The Order should not be tracked");

            orderDetails.revertUpdates(orderDetail);
            ok(order.OrderDetails.indexOf(orderDetail) >= 0 && order2.OrderDetails.indexOf(orderDetail) < 0 && orderDetail.Order() === order, "The order detail is a child of order1");
            equal(orders.getEntityState(order), upshot.EntityState.Unmodified, "order1 should not be modified");
            equal(orders.getEntityState(order2), upshot.EntityState.Unmodified, "order2 should not be modified");
            equal(orderDetails.getEntityState(orderDetail), upshot.EntityState.Unmodified, "The order detail should not be modified");
            equal(orderDetails.isUpdated(orderDetail, "OrderId"), false, "The OrderId should not be modified");
            equal(orderDetails.isUpdated(orderDetail, "Order"), false, "The Order should not be tracked");

            orderDetail.Order(null);
            equal(orderDetail.OrderId(), null, "FK should be null");
            equal(orders.getEntityState(order), upshot.EntityState.Unmodified, "The old order should not be modified");
            equal(orderDetails.getEntityState(orderDetail), upshot.EntityState.ClientUpdated, "The order detail should be ClientUpdated");
            equal(orderDetails.isUpdated(orderDetail, "OrderId"), true, "The OrderId should be modified");
            equal(orderDetails.isUpdated(orderDetail, "Order"), false, "The Order should not be tracked");

            var properties = [];
            $.each(observability.knockout.unmap(orderDetail, "OrderDetail"), function (key, value) {
                properties.push(key);
                equal(ko.utils.unwrapObservable(orderDetail[key]), value, "Properties should be equal");
            });
            equal(properties.length, 4, "The should be 4 serialized properties");

        } finally {
            upshot.observability.configuration = observability.jquery;
        }
    });

    test("Nested entities can be added to an entity, and property changes do not bubble to the parent", 2, function () {
        try {
            upshot.observability.configuration = observability.knockout;

            var dc = createKoTestContext(function () { }, false);

            var orders = dc.getEntitySet("Order"),
                orderDetails = dc.getEntitySet("OrderDetail"),
                order = orders.getEntities()()[0],
                orderDetail = orderDetails.getEntities()()[0];

            orderDetail.Name("asdf");
            equal(orders.getEntityState(order), upshot.EntityState.Unmodified, "The order should not be modified");
            equal(orderDetails.getEntityState(orderDetail), upshot.EntityState.ClientUpdated, "The order detail should not be modified");
        } finally {
            upshot.observability.configuration = observability.jquery;
        }
    });

    // Create and return a context using the specified submitChanges mock
    function createTestContext(submitChangesMock, bufferChanges) {
        var dataProvider = new upshot.DataProvider();
        var implicitCommitHandler;
        if (!bufferChanges) {
            implicitCommitHandler = function () {
                dc._commitChanges({ providerParameters: {} });
            }
        }
        var dc = new upshot.DataContext(dataProvider, implicitCommitHandler);
        dc._submitChanges = submitChangesMock;

        // add a single product to the context
        var type = "Product";
        var products = [];
        products.push({
            ID: 1,
            Name: "Crispy Snarfs",
            Category: "Snacks",
            Price: 12.99
        });
        products.push({
            ID: 2,
            Name: "Cheezy Snax",
            Category: "Snacks",
            Price: 1.99
        });

        // mock out enough metadata to do the attach
        upshot.metadata(type, { key: ["ID"] });

        dc.merge(products, type, null);

        return dc;
    }

    function createKoTestContext(submitChangesMock, bufferChanges) {
        var dataProvider = new upshot.DataProvider();
        var implicitCommitHandler;
        if (!bufferChanges) {
            implicitCommitHandler = function () {
                dc._commitChanges({ providerParameters: {} });
            }
        }
        var dc = new upshot.DataContext(dataProvider, implicitCommitHandler);
        dc._submitChanges = submitChangesMock;

        // add a single product to the context
        var manageAssociations = true;  // TODO: Lift this to a createKoTestContext parameter when unmanaged associations is supported.
        var OrderDetail = function (data) {
            this.Id = ko.observable(data.Id);
            this.Name = ko.observable(data.Name);
            this.Order = ko.observable();
            this.OrderId = manageAssociations ? ko.observable(data.OrderId) : ko.computed(function () {
                return this.Order() ? this.Order().Id() : data.OrderId;
            }, this);
            this.Extra = ko.observable("extra");
        };
        var Order = function (data) {
            this.Id = ko.observable(data.Id);
            this.Name = ko.observable(data.Name);
            this.OrderDetails = ko.observableArray(ko.utils.arrayMap(data.OrderDetails, function (od) { return new OrderDetail(od); }));
            this.Extra = ko.observable("extra");
        };

        var orders = [],
            order = {
                Id: 1,
                Name: "Order 1",
                OrderDetails: []
            };
        orders.push(new Order(order));
        orders.push(new Order({
            Id: 2,
            Name: "Order 2",
            OrderDetails: []
        }));
        var orderDetails = [];
        orderDetails.push(new OrderDetail({
            Id: 1,
            Name: "Order Detail 1",
            OrderId: order.Id
        }));
        orderDetails.push(new OrderDetail({
            Id: 2,
            Name: "Order Detail 2",
            OrderId: order.Id
        }));
        orders[0].OrderDetails(orderDetails);

        // mock out enough metadata to do the attach
        upshot.metadata("Order", { key: ["Id"], fields: { Id: { type: "Int32:#System" }, Name: { type: "String:#System" }, OrderDetails: { type: "OrderDetail", association: { Name: "O_OD", isForeignKey: false, thisKey: ["Id"], otherKey: ["OrderId"] }, array: true } } });
        upshot.metadata("OrderDetail", { key: ["Id"], fields: { Id: { type: "Int32:#System" }, Name: { type: "String:#System" }, Order: { type: "Order", association: { Name: "O_OD", isForeignKey: true, thisKey: ["OrderId"], otherKey: ["Id"]} }, OrderId: { type: "Int32:#System"}} });

        dc.merge(orders, "Order", null);

        return dc;
    }
})(upshot, ko);