"use strict";

/*
 * helper functions
 */

function clearFeatureLayer(featureLayer, cb)
{
	g_modules.esriRequest({
		url: featureLayer.url + "/deleteFeatures",
		content: { f: 'json', where: '1=1'},
		handleAs: 'json'
	},{usePost:true}).then( function(response)
	{
		cb && cb(true,response);
	},
	function(error)
	{
		cb && cb(false,error);
	});
}

function countFeatures(featureLayer, cb)
{
	g_modules.esriRequest({
		url: featureLayer.url + "/query",
		content: { f: 'json', where: '1=1', returnCountOnly:true},
		handleAs: 'json'
	},{usePost:true}).then( function(response)
	{
		cb && cb(true,response);
	},
	function(error)
	{
		cb && cb(false,error);
	});
}

function getObjectIds(graphics)
{
	return graphics.map( function(g) { return g.attributes[g_offlineFeaturesManager.DB_UID]; });
}

/*
 * tests begin here
 */
var async = new AsyncSpec(this);

describe("Normal online editing - Exercise the feature services", function()
{
	var g1,g2,g3;

	describe("Original applyEdits method", function()
	{
		async.it("clears the feature layers", function(done)
		{
			var count = 0;
			function completedOne()
			{
				count += 1;
				if(count==g_layersIds.length)
					done();
			}

            // Run clear twice because of current bug in ArcGIS Online related to clearing feature services with attachments.
            clearFeatureLayer(g_featureLayers[0], function(success){
                clearFeatureLayer( g_featureLayers[0], function(success,response)
                {
                    expect(success).toBeTruthy();
                    var listener = g_featureLayers[0].on('update-end', function(){ listener.remove(); completedOne();})
                    g_featureLayers[0].refresh();

                });
            });
		});

		async.it("add test features", function(done)
		{
			expect(g_featureLayers[0].graphics.length).toBe(0);

            g1 = new g_modules.Graphic({"geometry":{"x":-105400,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"OBJECTID":1,"lat":0.0,"lng":0.0,"description":"g1"}});
            g2 = new g_modules.Graphic({"geometry":{"x":-105600,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"OBJECTID":2,"lat":0.0,"lng":0.0,"description":"g2"}});
            g3 = new g_modules.Graphic({"geometry":{"x":-105800,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"OBJECTID":3,"lat":0.0,"lng":0.0,"description":"g3"}});

            var adds = [g1,g2,g3];
			g_featureLayers[0]._applyEdits(adds,null,null,function(addResults,updateResults,deleteResults)
			{
				expect(addResults.length).toBe(3);
				expect(addResults[0].success).toBeTruthy();
				expect(addResults[1].success).toBeTruthy();
				expect(addResults[2].success).toBeTruthy();
				g1.attributes.objectid = addResults[0].objectId;
				g2.attributes.objectid = addResults[1].objectId;
				g3.attributes.objectid = addResults[2].objectId;
				expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([g1,g2,g3]));
				expect(g_featureLayers[0].graphics.length).toBe(3);
				countFeatures(g_featureLayers[0], function(success,result)
				{
					expect(success).toBeTruthy();
					expect(result.count).toBe(3);
					done();
				});
			},
			function(error)
			{
				expect(true).toBeFalsy();
				done();
			});
		});

		async.it("update test features", function(done)
		{
			expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([g1,g2,g3]));
			expect(g_featureLayers[0].graphics.length).toBe(3);

			g1.geometry.y += 300;
			g2.geometry.y += 100;
			g3.geometry.y -= 200;
			var updates = [g1,g2,g3];
			g_featureLayers[0]._applyEdits(null,updates,null,function(addResults,updateResults,deleteResults)
			{
				expect(updateResults.length).toBe(3);
				expect(updateResults[0].success).toBeTruthy();
				expect(updateResults[1].success).toBeTruthy();
				expect(updateResults[2].success).toBeTruthy();
				expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([g1,g2,g3]));
				expect(g_featureLayers[0].graphics.length).toBe(3);
				done();
			},
			function(error)
			{
				expect(true).toBeFalsy();
				done();
			});
		});

		async.it("delete test features", function(done) {
            expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([g1, g2, g3]));
            expect(g_featureLayers[0].graphics.length).toBe(3);

            var deletes = [g3];
            g_featureLayers[0]._applyEdits(null, null, deletes, function (addResults, updateResults, deleteResults) {
                    expect(deleteResults.length).toBe(1);
                    expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([g1, g2]));
                    expect(g_featureLayers[0].graphics.length).toBe(2);
                    done();
                },
                function (error) {
                    expect(true).toBeFalsy();
                    done();
                });
        });
		});
	});

//describe("Online attachments", function(){
//    async.it("Add image attachment", function(done){
//
//
//        var xhr = new XMLHttpRequest();
//        xhr.open("GET","../samples/images/blue-pin.png",true);
//        xhr.responseType = "blob";
//
//        xhr.onload = function()
//        {
//            if( xhr.status === 200)
//            {
//                var blob = new Blob([this.response],{type: 'image/png'});
//
//                // Verify our image is a PNG file!
//                var reader = new FileReader();
//                reader.onload = function (evt) {
//                    file = evt.target.result;
//                    var test = file.slice(0,4);
//                    expect(test).toContain("PNG");
//                };
//                reader.readAsBinaryString(blob);
//
//                var parts = [blob,"test", new ArrayBuffer(blob.size)];
//
//                var file = new File(parts,"blue-pin.png",{
//                    lastModified: new Date(0),
//                    type: "image/png"
//                });
//
//                var formNode = {
//                    elements:[
//                        {type:"file",
//                            files:[file]}
//                    ]
//                };
//
//                g_offlineFeaturesManager.initAttachments(function(success){
//                    expect(success).toBe(true);
//                    if(success){
//
//                        done();
//                        //g_featureLayers[0].addAttachment(/* objectid */1,/* form node */ formNode,function(event){
//                        //    expect(event.attachmentId).toBe(-4);
//                        //    expect(event.objectId).toBe(1);
//                        //    expect(event.success).toBe(true);
//                        //    //console.log("ATTACHMENTS: " + JSON.stringify(event));
//                        //    done();
//                        //},function(error){
//                        //    expect(error).toBe(true); // we want to fail if there is an error!
//                        //    done();
//                        //});
//                    }
//                });
//            }
//            else
//            {
//                console.log("Test attachments failed");
//            }
//        };
//        xhr.onerror = function(e)
//        {
//            console.log("Test attachments failed: " + JSON.stringify(e));
//        };
//
//        xhr.send(null);
//    });
//});

describe("Offline Editing", function()
{

    var g1,g2,g3;
    var g4,g5,g6;
    var l1,l2,l3;

    describe("Prep db and feature service", function()
    {
        async.it("detect IndexedDB support", function (done) {
            expect(g_editsStore.isSupported()).toBeTruthy();
            done();
        });

        async.it("initialize database", function (done) {
            g_editsStore.init(function (success) {
                expect(success).toEqual(true);
                done();
            })
        });

        async.it("Get feature layer options object", function(done){
            g_featureLayers[0].getFeatureLayerJSONDataStore(function(success,message){
                expect(success).toBe(true);
                expect(message.zoom).toEqual(10);
                done();
            })
        });

        async.it("Modify feature layer options object", function(done){
            var object = {
                "zoom":9
            };
            g_featureLayers[0].setFeatureLayerJSONDataStore(object,function(success,message){
                expect(success).toBe(true);

                g_featureLayers[0].getFeatureLayerJSONDataStore(function(success,message){
                    expect(success).toBe(true);
                    expect(message.zoom).toEqual(9);

                    var graphics = JSON.parse(message.graphics);

                    //Verify that we did not overwrite the options object.
                    //If this tests passes then it's still there!
                    expect(graphics.layerDefinition.id).toEqual(0);

                    done();
                });
            })
        });

        async.it("Prepare feature service. Clear database",function(done)
        {
            g_featureLayers[0].resetDatabase(function (result) {
                expect(result).toEqual(true);

                g_featureLayers[0].pendingEditsCount(function (count) {
                    expect(count).toBe(0);
                    done();
                });

            });
        });

        async.it("Prepare feature service. Clear feature Layers - points - lines", function(done)
        {
            var count = 0;
            function completedOne()
            {
                count += 1;
                if(count== g_layersIds.length)
                    done();
            }
            clearFeatureLayer( g_featureLayers[0], function(success,response)
            {
                expect(success).toBeTruthy();
                var listener = g_featureLayers[0].on('update-end', function(){ listener.remove(); completedOne();})
                g_featureLayers[0].refresh();

            });
        });

        async.it("Prepare feature service. Add some features online - points", function(done)
        {
            expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.ONLINE);

            g1 = new g_modules.Graphic({"geometry":{"x":-105400,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"OBJECTID":1,"lat":0.0,"lng":0.0,"description":"g1"}});
            g2 = new g_modules.Graphic({"geometry":{"x":-105600,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"OBJECTID":2,"lat":0.0,"lng":0.0,"description":"g2"}});
            g3 = new g_modules.Graphic({"geometry":{"x":-105800,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"OBJECTID":3,"lat":0.0,"lng":0.0,"description":"g3"}});

            var adds = [g1,g2,g3];
            g_featureLayers[0].applyEdits(adds,null,null,function(addResults,updateResults,deleteResults)
                {
                    expect(addResults.length).toBe(3);
                    expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([g1,g2,g3]));
                    expect(g_featureLayers[0].graphics.length).toBe(3);
                    countFeatures(g_featureLayers[0], function(success,result)
                    {
                        expect(success).toBeTruthy();
                        expect(result.count).toBe(3);
                        done();
                    });
                },
                function(error)
                {
                    expect(true).toBeFalsy();
                });
        });

        // Temporarily comment out. We have switched to a Point-based service only that accepts attachments
        //async.it("Prepare feature service. Add some features online - lines", function(done)
        //{
        //    expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.ONLINE);
        //
        //    l1 = new g_modules.Graphic({"geometry":{"paths":[[[-101300,5136900],[-108400,5136900]]],"spatialReference":{"wkid":102100}},"attributes":{"ruleid":40,"zmax":null,"additionalinformation":null,"eny":null,"uniquedesignation":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"echelon":null,"x":null,"y":null,"z":null,"zmin":null}});
        //    l2 = new g_modules.Graphic({"geometry":{"paths":[[[-101300,5136800],[-108400,5136800]]],"spatialReference":{"wkid":102100}},"attributes":{"ruleid":40,"zmax":null,"additionalinformation":null,"eny":null,"uniquedesignation":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"echelon":null,"x":null,"y":null,"z":null,"zmin":null}});
        //    l3 = new g_modules.Graphic({"geometry":{"paths":[[[-101300,5136700],[-108400,5136700]]],"spatialReference":{"wkid":102100}},"attributes":{"ruleid":40,"zmax":null,"additionalinformation":null,"eny":null,"uniquedesignation":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"echelon":null,"x":null,"y":null,"z":null,"zmin":null}});
        //
        //    var adds = [l1,l2,l3];
        //    g_featureLayers[1].applyEdits(adds,null,null,function(addResults,updateResults,deleteResults)
        //        {
        //            expect(addResults.length).toBe(3);
        //            expect(getObjectIds(g_featureLayers[1].graphics)).toEqual(getObjectIds([l1,l2,l3]));
        //            expect(g_featureLayers[1].graphics.length).toBe(3);
        //            countFeatures(g_featureLayers[1], function(success,result)
        //            {
        //                expect(success).toBeTruthy();
        //                expect(result.count).toBe(3);
        //                done();
        //            });
        //        },
        //        function(error)
        //        {
        //            expect(true).toBeFalsy();
        //        });
        //});
    });

    describe("Go offline", function()
    {
        async.it("go Offline", function(done)
        {
            expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.ONLINE);
            g_offlineFeaturesManager.goOffline();
            expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.OFFLINE);
            done();
        });

        async.it("Convert feature array into JSON", function(done){

            var adds = [g1,g2,g3];
            g_offlineFeaturesManager.serializeFeatureGraphicsArray(adds,function(JSONString){
                expect(typeof JSONString).toBe("string");
                var object = JSON.parse(JSONString);
                expect(typeof object).toBe("object");
                expect(object[0].geometry.x).toEqual(-105400);
                expect(object[1].geometry.x).toEqual(-105600);
                expect(object[2].geometry.x).toEqual(-105800);
                done();
            });

        });

        async.it("Get empty featureCollections Object", function(done) {
            g_offlineFeaturesManager.getFeatureCollections(function(success, result) {
                expect(success).toBe(false);
                expect(result).toBeNull();
                done();
            });
        });

        async.it("update existing features - points", function(done)
        {
            expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([g1,g2,g3]));
            expect(g_featureLayers[0].graphics.length).toBe(3);
            expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.OFFLINE);

            g1.geometry.y += 300;
            g2.geometry.y += 100;
            g3.geometry.y -= 200;
            var updates = [g1,g2,g3];
            g_featureLayers[0].applyEdits(null,updates,null,function(addResults,updateResults,deleteResults)
                {   console.log("update existing features - points: " + JSON.stringify(updateResults))
                    expect(updateResults.length).toBe(3);
                    expect(updateResults[0].success).toBeTruthy();
                    expect(updateResults[1].success).toBeTruthy();
                    expect(updateResults[2].success).toBeTruthy();
                    expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([g1,g2,g3]));
                    expect(g_featureLayers[0].graphics.length).toBe(3);
                    g_editsStore.pendingEditsCount(function(result){
                        expect(result).toBe(3);
                        done();
                    });
                },
                function(error)
                {
                    expect(true).toBeFalsy();
                    done();
                });
        });

        async.it("Get featureCollections Object", function(done) {
            g_offlineFeaturesManager.getFeatureCollections(function(success, result) {
                expect(success).toBe(true);
                expect(result.featureCollections.length).toBe(1);
                expect(result.featureCollections[0].featureLayerCollection).toEqual(g_featureLayers[0].toJson());
                expect(result.featureCollections[0].featureLayerUrl).toEqual("http://services1.arcgis.com/M8KJPUwAXP8jhtnM/arcgis/rest/services/Simple_Point_Service/FeatureServer/0");
                done();
            });
        });

        // NOTE: We are only dealing with points!
        //async.it("update existing features - lines", function(done)
        //{
        //    expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([l1,l2,l3]));
        //    expect(g_featureLayers[1].graphics.length).toBe(3);
        //    expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.OFFLINE);
        //
        //
        //    l1.geometry.y += 300; // jabadia: change
        //    l2.geometry.y += 100;
        //    l3.geometry.y -= 200;
        //
        //    var updates = [l1,l2,l3];
        //    g_featureLayers[1].applyEdits(null,updates,null,function(addResults,updateResults,deleteResults)
        //        {
        //            expect(updateResults.length).toBe(3);
        //            expect(updateResults[0].success).toBeTruthy();
        //            expect(updateResults[1].success).toBeTruthy();
        //            expect(updateResults[2].success).toBeTruthy();
        //            expect(getObjectIds(g_featureLayers[1].graphics)).toEqual(getObjectIds([l1,l2,l3]));
        //            expect(g_featureLayers[1].graphics.length).toBe(3);
        //            g_editsStore.pendingEditsCount(function(result){
        //                expect(result).toBe(6);
        //                done();
        //            });
        //        },
        //        function(error)
        //        {
        //            expect(true).toBeFalsy();
        //            done();
        //        });
        //});

        async.it("delete existing features - points", function(done)
        {
            expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([g1,g2,g3]));
            expect(g_featureLayers[0].graphics.length).toBe(3);

            var deletes = [g3]; console.log("Graphic " + JSON.stringify(g3.toJson()));
            g_featureLayers[0].applyEdits(null,null,deletes,function(addResults,updateResults,deleteResults)
                {
                    expect(deleteResults.length).toBe(1);
                    expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([g1,g2]));
                    expect(g_featureLayers[0].graphics.length).toBe(2);
                    g_editsStore.pendingEditsCount(function(result){

                        // Expected count will stil be 6! This record is the database gets overwritten
                        // with the latest edit request. Last edit wins.
                        expect(result).toBe(3);
                        done();
                    });
                },
                function(error)
                {
                    expect(true).toBeFalsy();
                    done();
                });
        });

        async.it("add new features offline - points", function(done)
        {
            expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([g1,g2]));
            expect(g_featureLayers[0].graphics.length).toBe(2);
            expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.OFFLINE);

            //g4 = new g_modules.Graphic({"geometry":{"x":-109100,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"symbolname":"Reference Point DLRP","z":null,"additionalinformation":null,"eny":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"uniquedesignation":null,"x":null,"y":null}} );
            //g5 = new g_modules.Graphic({"geometry":{"x":-109500,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"symbolname":"Reference Point DLRP","z":null,"additionalinformation":null,"eny":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"uniquedesignation":null,"x":null,"y":null}} );
            //g6 = new g_modules.Graphic({"geometry":{"x":-109900,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"symbolname":"Reference Point DLRP","z":null,"additionalinformation":null,"eny":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"uniquedesignation":null,"x":null,"y":null}} );

            g4 = new g_modules.Graphic({"geometry":{"x":-109100,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"OBJECTID":4,"lat":0.0,"lng":0.0,"description":"g4"}});
            g5 = new g_modules.Graphic({"geometry":{"x":-109500,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"OBJECTID":5,"lat":0.0,"lng":0.0,"description":"g5"}});
            g6 = new g_modules.Graphic({"geometry":{"x":-109900,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"OBJECTID":6,"lat":0.0,"lng":0.0,"description":"g6"}});

            var adds = [g4,g5,g6];
            g_featureLayers[0].applyEdits(adds,null,null,function(addResults,updateResults,deleteResults)
                {
                    expect(addResults.length).toBe(3);

                    g_editsStore.pendingEditsCount(function(result){

                        // Should be 9 since we added 3 three edits and we had 6 edits in the previous test
                        expect(result).toBe(6);
                        done();
                    });

                    expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([g1,g2,g4,g5,g6]));
                    expect(g_featureLayers[0].graphics.length).toBe(5);
                    g4.attributes.objectid = addResults[0].objectId;
                    g5.attributes.objectid = addResults[1].objectId;
                    g6.attributes.objectid = addResults[2].objectId;
                    expect(g4.attributes.objectid).toBeLessThan(0);
                    expect(g5.attributes.objectid).toBeLessThan(g4.attributes.objectid);
                    expect(g6.attributes.objectid).toBeLessThan(g5.attributes.objectid);
                },
                function(error)
                {
                    expect(true).toBeFalsy();
                });
        });

        async.it("Update new feature offline - point", function(done){

            // Let's make a change to g6 attributes
            g6.attributes.additionalinformation = "TEST123";
            var updates = [g6];
            g_featureLayers[0].applyEdits(null,updates,null,function(addResults,updateResults,deleteResults)
                {
                    expect(updateResults.length).toBe(1);

                    g_editsStore.pendingEditsCount(function(result){

                        // Should be the exact same as previous test
                        // An update to a new feature should be a single entry in the database.
                        // We simply update the existing entry with the new information.
                        expect(result).toBe(6);
                        done();
                    });
                },
                function(error)
                {
                    expect(true).toBeFalsy();
                });
        });

        async.it("validate non-existent feature - ADD", function(done){

            var testGraphic = new g_modules.Graphic({"geometry":{"x":-109100,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"symbolname":"Reference Point DLRP","z":null,"additionalinformation":null,"eny":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"uniquedesignation":null,"x":null,"y":null}} );

            g_featureLayers[0]._validateFeature(testGraphic,g_featureLayers[0].url,"add")
                .then(function(result){
                    expect(result.success).toBe(true);
                    expect(testGraphic).toEqual(result.graphic);
                    expect(result.operation).toEqual("add");
                    done();
                },function(error){
                    console.log("Validate feature error: " + error);
                });
        });

        async.it("validate feature that exists in db - ADD", function(done){
            var id = getObjectIds([g6]).toString();
            expect(id).toEqual("-3");
            g_featureLayers[0]._validateFeature(g6,g_featureLayers[0].url,"add")
                .then(function(result){
                    expect(result.success).toBe(true);
                    expect(g6).toEqual(result.graphic);
                    expect(JSON.stringify(g6.toJson()) === JSON.stringify(result.graphic.toJson())).toBeTruthy();
                    expect(result.operation).toEqual("add");
                    done();
                },function(error){
                    console.log("Validate feature error: " + error);
                });
        });

        // This UPDATE should be converted in an ADD
        async.it("validate feature that exists in db - UPDATE", function(done){
            var id = getObjectIds([g6]).toString();
            expect(id).toEqual("-3");
            g_featureLayers[0]._validateFeature(g6,g_featureLayers[0].url,"update")
                .then(function(result){
                    expect(result.success).toBe(true);
                    expect(g6).toEqual(result.graphic);

                    // we swap the operation type when updating an edit that hasn't
                    // been submitted to the server yet.
                    expect(result.operation).toBe("add");
                    expect(JSON.stringify(g6.toJson()) === JSON.stringify(result.graphic.toJson())).toBeTruthy();
                    expect(result.operation).toEqual("add");
                    done();
                },function(error){
                    console.log("Validate feature error: " + error);
                });
        });

        // Checking for errors and error handling
        async.it("delete a non-existing feature directly from db", function(done){

            var fakeGraphic = new g_modules.Graphic({"geometry":{"x":-10910,"y":513700,"spatialReference":{"wkid":102100}},"attributes":{"symbolname":"Reference Point 1234","z":null,"additionalinformation":null,"eny":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"uniquedesignation":null,"x":null,"y":null}} );
            g_editsStore.delete(g_featureLayers[0].url,fakeGraphic,function(success,error){
                expect(success).toBe(false);
                done();
            });
        });

        // Checking for errors and error handling
        async.it("delete a non-existing feature using extended feature layer", function(done){

            var fakeGraphic = new g_modules.Graphic({"geometry":{"x":-10910,"y":513700,"spatialReference":{"wkid":102100}},"attributes":{"symbolname":"Reference Point 1234","z":null,"additionalinformation":null,"eny":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"uniquedesignation":null,"x":null,"y":null}} );
            g_featureLayers[0]._deleteTemporaryFeature(fakeGraphic,function(results){
                expect(results[0]).toBe(false);
                expect(results[1]).toBe(false);
                done();
            });
        });

        // This private function deletes a temporary graphic and it's associated phantom graphic
        async.it("delete an existing feature using extended feature layer", function(done){
            var id = getObjectIds([g5]).toString();
            expect(id).toEqual("-2");
            g_featureLayers[0]._deleteTemporaryFeature(g5,function(results){
                expect(results[0]).toBe(true);
                expect(results[1]).toBe(true);
                done();
            });
        });

        async.it("check db size", function(done){
            g_featureLayers[0].getUsage(function(usage,error){
                expect(usage.sizeBytes).toBe(9203);
                expect(usage.editCount).toBe(5);
                expect(error).toBe(null);
                done();
            })
        });

        async.it("Validate featureCollections Object", function(done) {
            g_offlineFeaturesManager.getFeatureCollections(function(success, result) {
                expect(success).toBe(true);
                expect(result.featureCollections.length).toBe(1);
                expect(result.featureCollections[0].featureLayerCollection).toEqual(g_featureLayers[0].toJson());
                expect(result.featureCollections[0].featureLayerUrl).toEqual("http://services1.arcgis.com/M8KJPUwAXP8jhtnM/arcgis/rest/services/Simple_Point_Service/FeatureServer/0");
                done();
            });
        });
    });

    // TO-DO!!
    describe("Test attachments", function(){
        async.it("Add image attachment", function(done){


            var xhr = new XMLHttpRequest();
            xhr.open("GET","../samples/images/blue-pin.png",true);
            xhr.responseType = "blob";

            xhr.onload = function()
            {
                if( xhr.status === 200)
                {
                    var blob = new Blob([this.response],{type: 'image/png'});

                    // Verify our image is a PNG file!
                    var reader = new FileReader();
                    reader.onload = function (evt) {
                        file = evt.target.result;
                        var test = file.slice(0,4);
                        expect(test).toContain("PNG");
                    };
                    reader.readAsBinaryString(blob);

                    var parts = [blob,"test", new ArrayBuffer(blob.size)];

                    var file = new File(parts,"blue-pin.png",{
                        lastModified: new Date(0),
                        type: "image/png"
                    });

                    var formNode = {
                        elements:[
                            {type:"file",
                                files:[file]}
                        ]
                    };

                    g_offlineFeaturesManager.initAttachments(function(success){
                        expect(success).toBe(true);
                        if(success){

                            g_featureLayers[0].addAttachment(/* objectid */1,/* form node */ formNode,function(event){
                                expect(event.attachmentId).toBe(-4);
                                expect(event.objectId).toBe(1);
                                expect(event.success).toBe(true);
                                //console.log("ATTACHMENTS: " + JSON.stringify(event));
                                done();
                            },function(error){
                                expect(error).toBe(true); // we want to fail if there is an error!
                                done();
                            });
                        }
                    });
                }
                else
                {
                    console.log("Test attachments failed");
                }
            };
            xhr.onerror = function(e)
            {
                console.log("Test attachments failed: " + JSON.stringify(e));
            };

            xhr.send(null);
        });

        // Change the image to a GIF
        async.it("Update image attachment", function(done){

            var xhr = new XMLHttpRequest();
            xhr.open("GET","../samples/images/loading.gif",true);
            xhr.responseType = "blob";

            xhr.onload = function()
            {
                if( xhr.status === 200)
                {
                    var blob = new Blob([this.response],{type: 'image/png'});

                    // Verify our image is a GIF file!
                    var reader = new FileReader();
                    reader.onload = function (evt) {
                        file = evt.target.result;
                        var test = file.slice(0,6);
                        expect(test).toContain("GIF89a");
                    };
                    reader.readAsBinaryString(blob);

                    var parts = [blob,"test", new ArrayBuffer(blob.size)];

                    var file = new File(parts,"loading.gif",{
                        lastModified: new Date(0),
                        type: "image/png"
                    });

                    var formNode = {
                        elements:[
                            {type:"file",
                                files:[file]}
                        ]
                    };

                    g_featureLayers[0].updateAttachment(/* objectid */1,/* attachmentId */-4,/* form node */ formNode,function(event){
                        expect(event.attachmentId).toBe(-4);
                        expect(event.objectId).toBe(1);
                        expect(event.success).toBe(true);
                        //console.log("ATTACHMENTS: " + JSON.stringify(event));
                        done();
                    },function(error){
                        expect(error).toBe(true); // we want to fail if there is an error!
                        done();
                    });

                }
                else
                {
                    console.log("Test attachments failed");
                }
            };
            xhr.onerror = function(e)
            {
                console.log("Test attachments failed: " + JSON.stringify(e));
            };

            xhr.send(null);
        });

        async.it("Delete image attachment", function(done){
            g_featureLayers[0].deleteAttachments(1,[-4],function(event) {
                expect(event[0].attachmentId).toBe(-4);
                expect(event[0].objectId).toBe(1);
                expect(event[0].success).toBe(true);
                console.log("DELETE DELETE " + JSON.stringify(event));
                done();
            }, function(error) {
                expect(error).toBe(true);
                done();
            });
        });
    });

    describe("Test PhantomGraphicsLayer using editsStore directly", function()
    {
        async.it("Get PhantomLayerGraphics array via editsStore", function(done){
            g_editsStore.getPhantomGraphicsArray(function(results,errors){

                // Should be the same size as the number of edits!!
                expect(results.length).toBe(5);
                expect(results[0].id).toBe("phantom-layer|@|-1");
                expect(results[1].id).toBe("phantom-layer|@|-3");
                expect((results[2].id).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
                expect((results[3].id).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
                expect((results[4].id).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
                expect(errors).toBe("end");
                done();
            })
        });

        // offlineFeaturesManager results should be the same as getting results directly from database
        async.it("Get PhantomLayerGraphics via the layer", function(done){
            g_featureLayers[0].getPhantomGraphicsArray(function(result,array){
                expect(result).toBe(true);
                expect(typeof array).toBe("object");
                expect(array.length).toBe(5);
                expect(array[0].id).toBe("phantom-layer|@|-1");
                expect(array[1].id).toBe("phantom-layer|@|-3");
                expect((array[2].id).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
                expect((array[3].id).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
                expect((array[4].id).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);

                done();
            });
        });

        async.it("Set PhantomLayerGraphic", function(done){
            var graphic = new g_modules.Graphic({"geometry":{"x":-109900,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"objectid":"test001","symbolname":"Reference Point DLRP","z":null,"additionalinformation":null,"eny":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"uniquedesignation":null,"x":null,"y":null}} );
            g_editsStore.pushPhantomGraphic(graphic,function(result,error){
                expect(result).toBe(true);
                expect(error).toBe(null);
                done();
            });
        });

        async.it("Get simple PhantomLayerGraphics array (internal)", function(done){
           g_editsStore._getPhantomGraphicsArraySimple(function(results,errors){

               // Should be the previous size + 1 additional phantom graphic.
               expect(results.length).toBe(6);
               expect(results[0]).toBe("phantom-layer|@|-1");
               expect(results[1]).toBe("phantom-layer|@|-3");
               expect((results[2]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
               expect((results[3]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
               expect((results[4]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
               expect(results[5]).toBe("phantom-layer|@|test001");
               expect(errors).toBe("end");
               done();
           })
        });

        async.it("Add two more PhantomLayer graphics to database", function(done){
            var graphic = new g_modules.Graphic({"geometry":{"x":-109901,"y":5137000,"spatialReference":{"wkid":102100}},"attributes":{"objectid":"test002","symbolname":"Reference Point DLRP","z":null,"additionalinformation":null,"eny":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"uniquedesignation":null,"x":null,"y":null}} );

            g_editsStore.pushPhantomGraphic(graphic,function(result,error){
                expect(result).toBe(true);
                expect(error).toBe(null);

                var graphic2 = new g_modules.Graphic({"geometry":{"x":-109901,"y":5137001,"spatialReference":{"wkid":102100}},"attributes":{"objectid":"test003","symbolname":"Reference Point DLRP","z":null,"additionalinformation":null,"eny":null,"datetimevalid":null,"datetimeexpired":null,"distance":null,"azimuth":null,"uniquedesignation":null,"x":null,"y":null}} );

                g_editsStore.pushPhantomGraphic(graphic2,function(result,error){
                    expect(result).toBe(true);
                    expect(error).toBe(null);
                    done();
                });
            });
        });

        async.it("Get simple PhantomLayerGraphics array (internal)", function(done){
            g_editsStore._getPhantomGraphicsArraySimple(function(results,errors){

                // We added two phantom graphics to previous result
                expect(results.length).toBe(8);
                expect(results[0]).toBe("phantom-layer|@|-1");
                expect(results[1]).toBe("phantom-layer|@|-3");
                expect((results[2]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
                expect((results[3]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
                expect((results[4]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
                expect(results[5]).toBe("phantom-layer|@|test001");
                expect(results[6]).toBe("phantom-layer|@|test002");
                expect(results[7]).toBe("phantom-layer|@|test003");
                expect(errors).toBe("end");
                done();
            })
        });

        async.it("Delete a single graphic in the phantom graphics layer", function(done){
           g_editsStore.deletePhantomGraphic("phantom-layer|@|test001",function(success){
               expect(success).toBe(true);

               g_editsStore._getPhantomGraphicsArraySimple(function(results,errors){

                   // We remove one graphic from the previous result of 12
                   expect(results.length).toBe(7);
                   expect(results[0]).toBe("phantom-layer|@|-1");
                   expect(results[1]).toBe("phantom-layer|@|-3");
                   expect((results[2]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
                   expect((results[3]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
                   expect((results[4]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
                   expect(results[5]).toBe("phantom-layer|@|test002");
                   expect(results[6]).toBe("phantom-layer|@|test003");
                   expect(errors).toBe("end");
                   done();
               })
           })
        });

        //async.it("delete temporary feature", function(done){
        //    g_featureLayers[0]._deleteTemporaryFeature(g6,function(results){
        //        expect(results[0]).toBe(true);
        //        expect(results[1]).toBe(true);
        //        done();
        //    });
        //});

        async.it("Delete one of the phantom graphics using resetLimitedPhantomGraphicsQueue()", function(done){
            var responseObject = {
                0:{
                    "id":-1,
                    "updateResults":[
                        {
                        "success":false
                        }
                    ],
                    "addResults":[
                        {
                            "success":true
                        }
                    ],
                    "deleteResults":[
                        {
                            "success":false
                        }
                    ]
                }
            };

            g_editsStore.resetLimitedPhantomGraphicsQueue(responseObject,function(success){
                expect(success).toBe(true);
                done();
            });
        });

        async.it("Reverify total number of phantom graphics", function(done){
            g_editsStore._getPhantomGraphicsArraySimple(function(results,errors){

                // We remove one graphic from the previous result and should now be @ 9
                expect(results.length).toBe(6);
                expect(results[0]).toBe("phantom-layer|@|-3");
                expect((results[1]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
                expect((results[2]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
                expect((results[3]).indexOf(g_editsStore.PHANTOM_GRAPHIC_PREFIX)).toBe(0);
                expect(results[4]).toBe("phantom-layer|@|test002");
                expect(results[5]).toBe("phantom-layer|@|test003");
                expect(errors).toBe("end");
                done();
            })
        });

        async.it("Delete all PhantomLayerGraphics", function(done){

           g_editsStore.resetPhantomGraphicsQueue(function(result){
               expect(result).toBe(true);

               g_editsStore._getPhantomGraphicsArraySimple(function(results,errors){
                   expect(results.length).toBe(0);
                   expect(errors).toBe("end");
                   done();
               })
           })
        });
    });

    describe("Test FeatureLayerJSON store", function()
    {

        async.it("delete non-existent FeatureLayer data", function(done){
            g_editsStore.deleteFeatureLayerJSON(function(success, msg){
                expect(success).toBe(false);
                expect(msg.message).toBe("id does not exist");

                g_editsStore.getFeatureLayerJSON(function(success,msg){
                    expect(success).toBe(false);
                    expect(msg).toBe("nothing found");
                    done();
                });
            })
        });

        async.it("store FeatureLayerJSON data using editsStore directly", function(done){
            var dataObject = {
                graphics: {test:1},
                renderer: {test:2}
            };
            g_editsStore.pushFeatureLayerJSON(dataObject,function(result){
                expect(result).toBe(true);
                done();
            });
        });

        async.it("retrieve FeatureLayerJSON data", function(done){
            g_editsStore.getFeatureLayerJSON(function(success,data){
                expect(success).toBe(true);
                expect(data.id).toBe(g_editsStore.FEATURE_LAYER_JSON_ID);
                expect(typeof data.graphics).toBe("object");
                expect(typeof data.renderer).toBe("object");
                done();
            });
        });

        async.it("update FeatureLayerJSON data", function(done){
            var dataObject = {
                graphics: {test: 2}
            };
            g_editsStore.pushFeatureLayerJSON(dataObject,function(success,result){
                expect(success).toBe(true);
                expect(result).toBe(null);

                g_editsStore.getFeatureLayerJSON(function(success,data){
                    expect(success).toBe(true);
                    expect(data.id).toBe(g_editsStore.FEATURE_LAYER_JSON_ID);
                    expect(data.graphics.test).toBe(2);
                    done();
                });
            })
        });

        async.it("delete FeatureLayerJSON data", function(done){
            g_editsStore.deleteFeatureLayerJSON(function(success, msg){
                expect(success).toBe(true);
                expect(msg.message).toBe("id does not exist");

                g_editsStore.getFeatureLayerJSON(function(success,msg){
                    expect(success).toBe(false);
                    expect(msg).toBe("nothing found");
                    done();
                });
            })
        });
    });

    describe("Before going online", function(){
        async.it("Before going online validate graphic layer properties", function(done){
            // Remember we deleted g3! So our total count is 8 not 9. HOWEVER, there should be 9 records in the database!
            expect(getObjectIds(g_featureLayers[0].graphics)).toEqual(getObjectIds([g1,g2,g4,g5,g6]));
            //expect(getObjectIds(g_featureLayers[1].graphics)).toEqual(getObjectIds([l1,l2,l3]));
            expect(g_featureLayers[0].graphics.length).toBe(5);
            //expect(g_featureLayers[1].graphics.length).toBe(3);
            //expect(g_featureLayers[2].graphics.length).toBe(0);
            done();
        });

        // Here's a list of what we should have for pending edits:
        // -1 = add
        // -3 = add
        // update
        // update
        // delete
        async.it("Retrieve edits array from the layer", function(done){
            g_featureLayers[0].getAllEditsArray(function(success,array){
                expect(success).toBe(true); console.log("Pending edits prior to going back online: " + JSON.stringify(array))
                expect(array.length).toBe(5);
                done();
            });
        });

        async.it("Validate featureCollections Object", function(done) {
            g_offlineFeaturesManager.getFeatureCollections(function(success, result) {
                expect(success).toBe(true);
                expect(result.featureCollections.length).toBe(1);
                expect(result.featureCollections[0].featureLayerCollection).toEqual(g_featureLayers[0].toJson());
                expect(result.featureCollections[0].featureLayerUrl).toEqual("http://services1.arcgis.com/M8KJPUwAXP8jhtnM/arcgis/rest/services/Simple_Point_Service/FeatureServer/0");
                done();
            });
        });

        async.it("Verify feature layer graphic counts",function(done){
            // all of them are positive
            console.log("OBJECT IDs Before Online: " + JSON.stringify(getObjectIds(g_featureLayers[0].graphics)));
            expect(getObjectIds(g_featureLayers[0].graphics).filter(function(id){ return id<0; })).toEqual([-1,-2,-3]);
            expect(g_featureLayers[0].graphics.length).toBe(5);
            done();
        });

        async.it("Verify feature count from the feature layer's REST endpoint",function(done){
            countFeatures(g_featureLayers[0], function(success,result)
            {
                expect(success).toBeTruthy();
                expect(result.count).toBe(3);
                done();
            });
        });
    });

    describe("go Online", function()
    {
        async.it("go Online", function(done)
        {

            var listener = jasmine.createSpy('event listener all edits sent');
            var listener_editsSent = jasmine.createSpy('event listener edits sent');

            g_offlineFeaturesManager.on(g_offlineFeaturesManager.events.ALL_EDITS_SENT,listener);

            g_offlineFeaturesManager.goOnline(function(results) {
                console.log("Library is now back online");
                expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.ONLINE);
                expect(listener).toHaveBeenCalled();
                expect(results.success).toBeTruthy();

                //console.log("RESPONSES " + JSON.stringify(responses) + ", " + JSON.stringify(results))

                expect(Object.keys(results.responses).length).toBe(5);
                for (var key in results.responses) {

                    var response = results.responses[key];

                    console.log("RESPONSE " + JSON.stringify(response))

                    var layerId = response.layer.substring(response.layer.lastIndexOf('/') + 1);

                    expect(typeof response.tempId).toBe("object");
                    expect(typeof response.updateResults).toBe("object");
                    expect(typeof response.deleteResults).toBe("object");
                    expect(typeof response.addResults).toBe("object");
                    expect(typeof response.id).toBe("string");
                    expect(typeof response.layer).toBe("string");

                    if(response.updateResults.length > 0){
                        expect(response.updateResults[0].success).toBe(true);
                        expect(response.updateResults[0].objectId).toBeGreaterThan(0);
                    }
                    if(response.deleteResults.length > 0){
                        expect(response.deleteResults[0].success).toBe(true);
                        expect(response.deleteResults[0].objectId).toBeGreaterThan(0);
                    }
                    if(response.addResults.length > 0){
                        expect(response.addResults[0].success).toBe(true);
                        expect(response.addResults[0].objectId).toBeGreaterThan(0);
                    }
                }
                done();
            });
        });
    });

    describe("After online", function(){
        async.it("After online - verify feature layer graphic counts",function(done){
            // all of them are positive
            console.log("OBJECT IDs After Online: " + JSON.stringify(getObjectIds(g_featureLayers[0].graphics)));
            expect(getObjectIds(g_featureLayers[0].graphics).filter(function(id){ return id<0; })).toEqual([-2]);
            expect(g_featureLayers[0].graphics.length).toBe(5);
            done();
        });

        async.it("Verify feature count from the feature layer's REST endpoint",function(done) {
            countFeatures(g_featureLayers[0], function(success,result)
            {
                expect(success).toBeTruthy();
                expect(result.count).toBe(4);
                done();
            });
        });

        async.it("Retrieve edits array from the layer", function(done){
            g_featureLayers[0].getAllEditsArray(function(success,array){
                expect(success).toBe(true); console.log("ARRAY should be empty " + JSON.stringify(array))
                expect(array.length).toBe(0);
                done();
            });
        });

        async.it("After online - verify online status",function(done){
            expect(g_offlineFeaturesManager.getOnlineStatus()).toBe(g_offlineFeaturesManager.ONLINE);
            done();
        });

        async.it("After online - check pending edits count 0",function(done){
            g_editsStore.pendingEditsCount(function(result){
                expect(result).toBe(0);
                done();
            });
        });
    });
});