//globals
var map;// esri.Map
var ui;// interfaceManager


//update the zoom extent button status to show if next/prev extent is available
function extentHistoryChangeHandler() {
    dijit.byId("zoomprev").setAttribute('disabled', app.navToolbar.isFirstExtent());
    dijit.byId("zoomnext").setAttribute('disabled', app.navToolbar.isLastExtent());
};


//global reference to application manager
/*
 * application is a global object used to orchestrate all the parts of the app - including startup
 * <remarks>use app as a global reference</remarks>
 */
var app = function() {
    var subscriptions = []; //tracks all the subscriptions so they can be disposed
    var eventHandlers = []; //tracks all the events so they can be disposed
    //"private" variables:
    //var myPrivateVar = "I can be accessed only from within application."

    //"private" method:
    /*
    var myPrivateMethod = function () {
    console.log("I can be accessed only from within application");
    }
    */

    return {
        //Public
        services: {
            base: {},
            live: {}
        },
        //the identify manager
        identify: null,
        //reference to the config data
        configMgr: null,
        //shortcut to the configMgr.config object
        config: null,
        //ref to the navigation tools
        navToolbar: null,
        //ref to the drawing tools
        drawToolbar: null,
        //reference to the measure tool
        measure: null,
        //reference to the buffer parcel tool
        buffer: null,
        //reference to the select address tool
        addressTool: null,
        //the legend content pane
        legend: null,
        //the layer control
        toc: null,
        //ui manager
        ui: null,

        criptions: [],


        /**
        * array of layerDefs for the layers shown on the map by tools (other than layer control)
        * - each tool is responsible for letting the application know about layers it is using with the tpcg/addtoollayer and tpcg/removetoollayer
        * - this list is used by layer control, so that these layers are included in the map definition when the map display is refreshed following
        * a thematic change. Otherwise, these layers will be removed when thematic is changed
        */
        layersFromTools: [],

        //update the text in the loading message
        showLoadingText: function(textToShow) {
            document.getElementById("loadingText").innerHTML = textToShow;
        },
        /*
        * zoom the map to the full extent
        */
        zoomToFullExtent: function() {
            var fullExtent = dojo.clone(this.config.map.fullExtent);
            this.map.setExtent(fullExtent, true); //fit it
        },

        /**
        * change content of legend when the thematic layer changes
        * @param {Object} Layer Entry
        */
        changeLegend: function(/* Layer */lyr) {
            try {
                if (lyr.legendUrl) {
                    console.log("update legend to ", lyr.legendUrl);
                    this.legend.attr("href", lyr.legendUrl); //trigger ajax update of legend content
                }
                else {
                    //no legend defined
                    console.log("no legend");
                    this.legend.attr("content", "<p>&nbsp;</p>");
                };

            } catch (e) {
                console.error("App:: an error occured updating the legend ", e);
            };

        },
        /*
        * has the application been initialized
        */
        isInited: false,
        /*
        * connect to as event handler when application is starts the init process
        */
        onIniting: function() {
            //no code - placeholder for event
        },
        /*
        * connect to as event handler when application is ends the init process
        */
        onInited: function() {
            //no code - placeholder for event
            this.isInited = false;
        },
        //init the application
        /*
        * init the application
        * @param: mapDiv {string}  = element name that map will replace
        */
        init: function(mapDiv) {
            if (!mapDiv) mapDiv = "mapDiv";

            console.log("application:: init started");
            this.onIniting();

            //check if config is loaded yet
            this.configMgr = dijit.byId("configMgr");
            if (!this.configMgr.isLoaded) {
                console.log("config not loaded...try again later");
                dojo.Subscribe(this.configMgr, "configLoadedEvent", dojo.hitch(this, init));
                return;
            };
            console.log("init started");

            //load references to dijits
            this.config = this.configMgr.config;
            this.legend = dijit.byId("legendContent");
            // setup user interface components
            this.ui = new UIManager(mapDiv);
            //setup global reference since other script expect it
            ui = this.ui;

            //reposition the slider
            esriConfig.defaults.map.slider = {
                left: "20px",
                top: "50px",
                width: null,
                height: "200px"
            };

            esri.config.defaults.io.proxyUrl = "proxy.ashx";

            this.map = new esri.Map(mapDiv, this.config.map.options);
            //set the global object since some dijits expect global map reference
            map = this.map;
            //init the map once services are loaded
            dojo.connect(this.map, "onLoad", dojo.hitch(this, this.initMap));

            app.showLoadingText("Initializing Services...");
            var options = this.configMgr.config;

            //setup map services
            var count = 0; //map layer counter
            //setup tiled services
            var that = this; //reference for inside foreach
            dojo.forEach(options.map.baseMaps, function(item, idx) {
                var service = app.getServiceFromConfig(item);
                that.map.addLayer(service);
                that.services.base[item.id] = service;
            });

            //setup dynamic services
            dojo.forEach(options.map.liveMaps, function(item, idx) {
                var service = app.getServiceFromConfig(item);
                that.map.addLayer(service);
                that.services.live[item.id] = service;
            });

            //layers
            this.toc = new tpcg.LayerControl({
                esriMap: this.map,
                mapServices: this.services,
                entriesConfig: this.config.layercontrol
            }, 'layerControl');

            try {
                this.toc.startup();
            } catch (e) {
                console.error("App:: an error occured starting the TOC. Make sure each entry has a unique id. ", e);
            };

            this.identify = new tpcg.IdentifyManager({ "identifierConfigs": this.config.identifiers });
            dojo.connect(this.identify, "onIdentifierSet", dojo.hitch(this, this.onIdentifierChanged));

            this.geometryService = new esri.tasks.GeometryService(this.config.services.geometry);
            this.measure = new tpcg.Measure();
            this.measure.wkid = options.measure.wkid; //set spatial reference from config
            this.measure.convertToFeet = options.measure.convertToFeet;
            //wire up to app so can coordinate the other components
            dojo.connect(this.measure, "startMeasure", this, dojo.hitch(this, this.onDrawStart));
            dojo.connect(this.measure, "stopMeasure", this, dojo.hitch(this, this.onDrawStop));

            this.buffer = new tpcg.BufferParcel();
            this.buffer.startup();
            dojo.connect(this.buffer, "onBufferEnd", this, dojo.hitch(this, this.onBufferResult));
            dojo.connect(this.buffer, "removeParcelResults", this, dojo.hitch(this, this.onBufferResult));

            var addressSvc = app.getServiceById('address');
            var addressUrl = addressSvc.url + "/1";
            this.addressTool = new tpcg.SelectAddress({ urlToQuery: addressUrl });
            this.addressTool.startup();
            dojo.connect(this.addressTool, "onDrawStart", this, dojo.hitch(this, this.onDrawStart));
            dojo.connect(this.addressTool, "onDrawStop", this, dojo.hitch(this, this.onDrawStop));

            //legend
            subscriptions.push(dojo.subscribe("/tpcg/layershown", this, "changeLegend"));

            subscriptions.push(dojo.subscribe("/tpcg/layershown", this.identify, "setIdentifier"));
            subscriptions.push(dojo.subscribe("/tpcg/identifying", this, "onIdentifying"));
            subscriptions.push(dojo.subscribe("/tpcg/identified", this, "onIdentified"));
            subscriptions.push(dojo.subscribe("/tpcg/error", this, "onError"));

            //
            subscriptions.push(dojo.subscribe("/tpcg/layerchanging", this, "mapWaitOnLayerChange"));

            // track layers added by tools
            subscriptions.push(dojo.subscribe("/tpcg/addtoollayer", this, "addToolLayer"));
            subscriptions.push(dojo.subscribe("/tpcg/removetoollayer", this, "removeToolLayer"));


            //wire up event handles for all services
            dojo.forEach(this.services.base, function(svc) {
                eventHandlers.push(dojo.connect(svc, "onError", dojo.hitch(that, "onError")));
            });
            dojo.forEach(this.services.live, function(svc) {
                eventHandlers.push(dojo.connect(svc, "onError", dojo.hitch(that, "onError")));
            });

            eventHandlers.push(dojo.connect(dijit.byId("addressSearchForm"), "onSearchCompleted", dojo.hitch(this, "onAddressSearch")));
            eventHandlers.push(dojo.connect(dijit.byId("townshipSearchForm"), "onSearchCompleted", dojo.hitch(this, "onTownshipRangeSearch")));
            eventHandlers.push(dojo.connect(dijit.byId("parcelSearchForm"), "onSearchCompleted", dojo.hitch(this, "onParcelSearch")));

            eventHandlers.push(dojo.connect(this.map, "onZoom", function() { app.ui.blockMap(true); }));
            eventHandlers.push(dojo.connect(this.map, "onZoomEnd", function() { app.ui.blockMap(false); }));
            //pan is so fast this is not needed
            //eventHandlers.push(dojo.connect(this.map, "onPan", function(){app.ui.blockMap(true);}));
            //eventHandlers.push(dojo.connect(this.map, "onPanEnd", function(){app.ui.blockMap(false);}));

            this.ui.resizeOnce();
            //no official event but try the resize function
            eventHandlers.push(dojo.connect(dijit.byId("legend"), "resize", dojo.hitch(this, this.onLegendResize)));
            this.onLegendResize();
            console.log("application:: init complete");
        },
        /*
        * init the map after the first layer is loaded
        */
        initMap: function() {
            console.log("application :: 1st map loaded.");

            this.showLoadingText("Configuring Services...");

            this.navToolbar = new esri.toolbars.Navigation(this.map);
            eventHandlers.push(dojo.connect(this.navToolbar, "onExtentHistoryChange", extentHistoryChangeHandler));

            this.drawToolbar = new esri.toolbars.Draw(this.map);

            this.ui.displayUI();

            this.ui.makeUIEventConnections();

            this.showLoadingText("Setup Completed...");
            this.onInited(); //let everyone know I'm finishing
            console.log("application:: initMap complete");
        },
        /*
        * removes subscriptions to layershown and redoes it
        * this was done to fix a problem in IE after basemap only option is selected
        * subscriptions to layershown stopped working.
        */
        resubscribeToLayerShown: function() {
            dojo.unsubscribe("/tpcg/layershown")
            dojo.subscribe("/tpcg/layershown", this, "changeLegend");
            dojo.subscribe("/tpcg/layershown", this.identify, "setIdentifier");
        },

        /*
        * called when disposing the page
        */
        onUnload: function() {
            console.warn("application :: unloading");
            //remove all subscriptions
            for (var i = 0; i < subscriptions.length; i++) {
                dojo.unsubscribe(subscriptions[i]);
            };

            //remove all event handlers
            for (var i = 0; i < eventHandlers.length; i++) {
                dojo.disconnect(eventHandlers[i]);
            };
            console.warn("application :: unloaded");
        },
        /**
        * return the live or basemap service definition for the given id
        * only need to use this if don't know if the service is base or live
        * @param {String} serviceId = the id of the service from config
        * @return {Object} returns a service object
        * 
        */
        getServiceById: function(serviceId) {
            var svc = this.services.live[serviceId];
            if (svc)
                return svc;
            //not found so check basemap
            svc = this.services.base[serviceId];
            return svc;
        },
        /**
        * after address search make sure address layer is shown
        */
        onAddressSearch: function() {
            console.log("application :: turning address layer on after address search");
            var addressLE = dijit.byId("address");
            if (!addressLE) {
                console.error("application :: address LE not found");
                return;
            };
            app.toc.showLayer("address");
        },
        /**
        * after township range search make sure township layer is shown
        */
        onTownshipRangeSearch: function() {
            console.log("application :: turning township layer on after township range search");
            var townshipLE = dijit.byId("township");
            if (!townshipLE) {
                console.error("application :: township LE not found");
                return;
            };
            app.toc.showLayer("township");
        },

        /**
        * after parcel search make sure parcel layer is shown
        */
        onParcelSearch: function() {
            console.log("application :: turning parcel layer on after parcel search");

            var search = dijit.byId("parcelSearchForm");
            var parcelSvc = this.getServiceById(search.mapServiceId);
            var lyrIds = parcelSvc.visibleLayers;
            var layerId = Number(search.layerId);
            //if already turned on, honor the current visible layers
            if ((parcelSvc.visible) && (dojo.indexOf(lyrIds, layerId) != -1))
                return; //its already on

            //not on - so turn it on							
            var parcelLE = dijit.byId("parcel");
            if (!parcelLE) {
                console.error("application :: parcel LE not found");
                return;
            };
            app.toc.showLayer("parcel");
        },

        /**
        * what to do when an error happens
        * @param {Object} error
        */
        onError: function(error) {
            app.ui.blockMap(false); //stop map wait
            var errorDialog = new dijit.Dialog({
                title: "Error",
                content: "An error occurred processing your request.<br />Please try again. <br />If the problem continues, please contact the site administrator.",
                style: "text-align:center;"
            });
            errorDialog.show();
            console.error(error);
        },

        /**
        * called when any identify begins
        */
        onIdentifying: function() {
            //show wait message
            app.ui.blockMap(true);
        },
        /**
        * called when any identify begins
        */
        onIdentified: function() {
            //show wait message
            app.ui.blockMap(false);
        },
        /**
        * when legend resizes changed the height of the legend content pane
        */
        onLegendResize: function() {
            var lgdSize = dojo.coords('legend');
            var lgdTitleSize = dojo.coords('legendTitle');

            var contentHeight = lgdSize.h - lgdTitleSize.h - 5; //5 extra for good measure
            dojo.style('legendContent', "height", contentHeight + "px");
            console.log("legendContent set to height of ", contentHeight);
        },
        /**
        * show the map wait for initial display of layer
        * @param {Object} le = layer
        */
        mapWaitOnLayerChange: function(/*Layer*/le) {
            console.log("application:: start map wait for ", le.id);
            app.ui.blockMap(true);

            //map service ids 
            var mapSvcIdsToUpdate = dojo.map(le.layers, function(layer) {
                return layer.mapServiceId;
            });

            //get the services
            var mapSvcsToUpdate = dojo.map(mapSvcIdsToUpdate, function(mapServiceId) {
                var mapSvc = app.getServiceById(mapServiceId);
                return mapSvc;
            });

            //because >1 service per entry, now need to track count of services to update
            var servicesToUpdate = mapSvcsToUpdate.length;
            dojo.forEach(mapSvcsToUpdate, function(mapSvc) {
                var handle = dojo.connect(mapSvc, "onUpdate", function(evt) {
                    servicesToUpdate -= 1;
                    //last one out will turn the lights off
                    if (servicesToUpdate == 0) {
                        app.ui.blockMap(false); //all updates are complete
                        console.log("application:: map wait complete for ", le.id);
                    };
                    dojo.disconnect(handle); //only once - let pan zoom take it from here
                });
            });

        },

        /**
        * what to do when selecting is started
        */
        onDrawStart: function() {
            //disable the current identifier if set
            if (app.identify.currentIdentifier)
                app.identify.currentIdentifier.enabled = false;
        },

        /**
        * what to do when selecting is stopped
        */
        onDrawStop: function() {
            //enable the identifier if set
            if (app.identify.currentIdentifier)
                app.identify.currentIdentifier.enabled = true;
        },

        /**
        * called when the identifier changes
        * @param {Object} the new identifier
        */
        onIdentifierChanged: function(identifier) {
            //if in measure mode, disable the identifier
            identifier.enabled = (app.measure.isOpen) ? false : true;
        },

        /**
        * when the buffer completes update the remove results button
        */
        onBufferResult: function() {
            var showRemoveResultsButton = app.buffer.hasBufferResults ? true : false;

            var removeResultsButton = dijit.byId("removeParcelResults");

            if (showRemoveResultsButton)
                dojo.style(removeResultsButton.domNode, "display", "inline");
            else
                dojo.style(removeResultsButton.domNode, "display", "none");
            removeResultsButton.startup();
        },

        /**
        * add the given layer def to the layersFromTools
        * @param {Object} layerToAdd = layerDef object
        */
        addToolLayer: function(layerToAdd) {
            this.layersFromTools.push(layerToAdd);
            console.log("Application::layer added from tool = ", layerToAdd);
            console.info("Application:: layers from tools = ", this.layersFromTools);
        },
        /**
        * remove the given layer def from the layersFromTools
        * @param {Object} layerToRemove = layerDef object
        */
        removeToolLayer: function(layerToRemove) {
            console.log("Application::layer removed by tool = ", layerToRemove);
            this.layersFromTools = dojo.filter(this.layersFromTools, function(layer) { return layer != layerToRemove; });
            console.info("Application:: layers from tools = ", this.layersFromTools);
        },
        /**
        * return the service from the configuration
        * @param {Object} item = service config object
        * @returns {object} 
        */
        getServiceFromConfig: function(item) {
            switch (item.type) {
                case "tiled":
                    return new esri.layers.ArcGISTiledMapServiceLayer(item.url, {
                        opacity: item.alpha,
                        visible: item.visible,
                        id: item.id
                    });
                    break;
                case "dynamic":
                    return new esri.layers.ArcGISDynamicMapServiceLayer(item.url, {
                        opacity: item.alpha,
                        visible: item.visible,
                        id: item.id
                    });
                    break;
                case "image":
                    // assumed to be all bands
                    var imageSvcParameters = new esri.layers.ImageServiceParameters();
                    imageSvcParameters.bandIds = [0, 1, 2];

                    return new esri.layers.ArcGISImageServiceLayer(item.url, {
                        opacity: item.alpha,
                        visible: item.visible,
                        id: item.id,
                        imageServiceParameters: imageSvcParameters
                    });
                    break;
                default:
                    app.onError("Unknown service type ");

            }
        }

    };

} ();     // the parens here cause the anonymous function to execute and return



