define([
                                'dojo/_base/declare',
                                'dojo/dom',
                                'dojo/dom-construct',
                                'dojo/dom-class',
                                'dojo/dom-style',
                                'dojo/json',
                                'dojo/Deferred',
                                'dojo/query',
                                'dojo/on',
                                'dojo/request/xhr',
                                'dojo/Evented',
                                'esri/layers/FeatureLayer',
                                "esri/layers/GraphicsLayer",
                                'esri/geometry/webMercatorUtils',
                                "Hud.Common/Viewport/EsriMapController",
                                'dojo/text!./MapBuilderView.html',
                                "xstyle/css!./MapBuilder.css"
                            ],
                            function (
                                declare,
                                dom,
                                domConstruct,
                                domClass,
                                domStyle,
                                JSON,
                                Deferred,
                                query,
                                on,
                                xhr,
                                Evented,
                                FeatureLayer,
                                GraphicsLayer,
                                webMercatorUtils,
                                MapController,
                                MapBuilderView) {
                            
                                return declare([Evented], {
                                    //properties
                                    options: null,
                                    config: null,
                                    map: null,
                                    defaultExtent: null,
                                    defaultGraphic: null,
                                    defaultBasemap: null,
                                    selectedThematicLayer: null,
                                    selectedPropertyLayers: [],
                                    useLocalStorage: false,
                                    webMapCollection: [],
                                    localStorageKey: '_x_CustomMap_x_',
                            
                                    /**
                                    * Constructs a new MapBuilder class.
                                    * A component for building and saving customizable maps.
                                    *
                                    * @class Common.MapBuilder
                                    * @constructor
                                    */
                                    constructor: function (options) {
                                        this.options = options;
                                        this.map = options.map;
                            
                                        var d = this.getConfig();
                                        var self = this;
                                        d.then(function (config) {
                                            self.config = config;
                                            self.initialize();
                                        });
                                    },
                            
                                    /**
                                      * Initializes the UI and event handlers.
                                      * @method initialize
                                      */
                                    initialize: function () {
                                        var self = this;
                            
                                        //determine storage type
                                        self.useLocalStorage = self.localStorageAvailable();
                                        var item = window.localStorage.getItem(self.localStorageKey);
                                        if (item && item !== null) {
                                            var _item = JSON.parse(item);
                                            if (Array.isArray(_item)) {
                                                self.webMapCollection = _item;
                                            }
                                        }
                            
                                        //inject UI template
                                        domConstruct.place(MapBuilderView, self.options.containerId, 'only');
                            
                                        //build UI form from config
                                        self.buildForm();
                            
                                        /* BIND EVENTS */
                                        //basemap layer change
                                        var bl = query('input[name="basemap-options"]');
                                        bl.on('click', function (e) {
                                            var type = e.target.value;
                                            self.map.setBasemap(type);
                                        });
                                        //thematic layer change
                                        var tl = query('input[name="thematic-options"]');
                                        tl.on('click', function (e) {
                                            self.addThematicLayer(e.target.value);
                                            self.emit("layer-changed", { map: e.map });
                                        });
                                        //property layer change
                                        var pl = query('input[name="property-options"]');
                                        pl.on('click', function (e) {
                                            if (e.target.checked === true) {
                                                self.addPropertyLayer(e.target.value);
                                            }
                                            else {
                                                self.removePropertyLayer(e.target.value);
                                            }
                                            self.emit("layer-changed", { map: e.map });
                                        });
                                        //create map check change
                                        on(dom.byId('chkNewMap'), 'change', function (e) {
                                            self.toggleForm(e.target.checked);
                                        });
                                        //save click
                                        on(dom.byId('btnSaveCustomMap'), 'click', function (e) {
                                            self.save();
                                        });
                                        //cancel click
                                        on(dom.byId('btnCancelCustomMap'), 'click', function (e) {
                                            document.getElementById('chkNewMap').checked = false;
                                            self.toggleForm(false);
                                        });
                                    },
                            
                                    buildForm: function () {
                                        //basemaps
                                        var basemapHtml = '';
                                        for (var i = 0, _l = this.config.basemapLayers.length; i < _l; i++) {
                                            var basemap = this.config.basemapLayers[i];
                                            basemapHtml +=
                                                '<div class="col-md-6">' +
                                                    '<div class="radio map-options-item">' +
                                                        '<label>' +
                                                            '<input type="radio" name="basemap-options" id="basemap-radio-' + basemap.id + '" value="' + basemap.id + '">' +
                                                            basemap.name +
                                                        '</label>' +
                                                    '</div>' +
                                                '</div>';
                            
                                            if (i === 0) this.defaultBasemap = basemap.id;
                                        }
                                        domConstruct.place(basemapHtml, "basemap-layer-container", "only");
                            
                                        //thematic layers
                                        var thematicHtml = '';
                                        for (var i = 0, _l = this.config.thematicLayers.length; i < _l; i++) {
                                            var thematicLayer = this.config.thematicLayers[i];
                                            thematicHtml +=
                                                '<div class="col-md-6">' +
                                                    '<div class="radio map-options-item">' +
                                                        '<label>' +
                                                            '<input type="radio" name="thematic-options" id="thematic-radio-' + thematicLayer.id + '" value="' + thematicLayer.id + '">' +
                                                            thematicLayer.name +
                                                        '</label>' +
                                                    '</div>' +
                                                '</div>';
                                        }
                                        domConstruct.place(thematicHtml, "thematic-layer-container", "only");
                            
                                        //operational layers
                                        var layerHtml = '';
                                        for (var i = 0, _l = this.config.propertyLayers.length; i < _l; i++) {
                                            var layer = this.config.propertyLayers[i];
                                            layerHtml +=
                                                '<div class="col-md-6">' +
                                                    '<div class="checkbox map-options-item">' +
                                                        '<label>' +
                                                            '<input name="property-options" type="checkbox" value="' + layer.id + '">' +
                                                            layer.name +
                                                        '</label>' +
                                                    '</div>' +
                                                '</div>';
                                        }
                                        domConstruct.place(layerHtml, "property-layer-container", "only");
                            
                                        //web maps table
                                        var tableHtml =
                                            '<table class="table table-striped table-condensed">' +
                                                '<caption>Saved Maps</caption>' +
                                                '<thead>' +
                                                    '<tr><th>#</th><th>Map Name</th><th></th><th></th></tr>' +
                                                '</thead>' +
                                                '<tbody id="custom-map-table"></tbody>' +
                                            '</table>';
                                        domConstruct.place(tableHtml, "div-custommaptable", "only");
                            
                                        this.updateMapTable();
                                        //disable table buttons on initial load
                                        query('.custom-map-load').attr('disabled', true);
                                        query('.custom-map-delete').attr('disabled', true);
                                    },
                            
                                    toggleForm: function (enabled) {
                                        if (enabled === true) {
                                            this.resetWebMap();
                                            document.getElementById('txtMapName').disabled = false;
                                            document.getElementById('btnSaveCustomMap').disabled = false;
                                            document.getElementById('btnCancelCustomMap').disabled = false;
                                            query('input[name="basemap-options"]').attr('disabled', false);
                                            query('input[name="thematic-options"]').attr('disabled', false);
                                            query('input[name="property-options"]').attr('disabled', false);
                                            query('.custom-map-load').attr('disabled', true);
                                            query('.custom-map-delete').attr('disabled', true);
                                        }
                                        else {
                                            this.resetForm();
                                            document.getElementById('txtMapName').disabled = true;
                                            document.getElementById('btnSaveCustomMap').disabled = true;
                                            document.getElementById('btnCancelCustomMap').disabled = true;
                                            query('input[name="basemap-options"]').attr('disabled', true);
                                            query('input[name="thematic-options"]').attr('disabled', true);
                                            query('input[name="property-options"]').attr('disabled', true);
                                            query('.custom-map-load').attr('disabled', false);
                                            query('.custom-map-delete').attr('disabled', false);
                                        }
                                    },
                            
                                    resetForm: function () {
                                        //clear the map name field
                                        document.getElementById('txtMapName').value = '';
                            
                                        //set the default basemap
                                        document.getElementById('basemap-radio-' + this.defaultBasemap).checked = true;
                            
                                        //clear the thematic layers
                                        var thematicInputs = document.getElementsByName('thematic-options');
                                        for (var i = 0, _l = thematicInputs.length; i < _l; i++) {
                                            thematicInputs[i].checked = false;
                                        }
                            
                                        //clear the operational layers
                                        var propertyInputs = document.getElementsByName('property-options');
                                        for (var i = 0, _l = propertyInputs.length; i < _l; i++) {
                                            propertyInputs[i].checked = false;
                                        }
                                    },
                            
                                    save: function () {
                                        //get and validate map name
                                        var mapName = document.getElementById('txtMapName').value;
                                        if (!mapName || mapName === null || mapName === '') {
                                            domClass.remove('txt-map-name-warning', 'hidden');
                                            domClass.add('div-map-name', 'bg-danger');
                                            domClass.add('div-map-name', 'warning-container');
                                            return;
                                        }
                                        else {
                                            //reset warning styles
                                            domClass.add('txt-map-name-warning', 'hidden');
                                            domClass.remove('div-map-name', 'bg-danger');
                                            domClass.remove('div-map-name', 'warning-container');
                                        }
                            
                                        //build web map json from inputs
                                        var webMapJson = this.getWebMapJsonTemplate();
                                        webMapJson.item.title = mapName;
                            
                                        //get the selected basemap layer
                                        var selectedBasemap = query('input[name="basemap-options"]:checked')[0];
                                        for (var i = 0, _l = this.config.basemapLayers.length; i < _l; i++) {
                                            var basemapJson = this.config.basemapLayers[i];
                                            if (selectedBasemap.value === basemapJson.id) {
                                                webMapJson.itemData.baseMap.baseMapLayers.push(basemapJson);
                                                break;
                                            }
                                        }
                            
                                        //get the selected thematic layer
                                        var selectedThematic = query('input[name="thematic-options"]:checked')[0];
                                        if (selectedThematic && selectedThematic !== null) {
                                            for (var i = 0, _l = this.config.thematicLayers.length; i < _l; i++) {
                                                var thematicJson = this.config.thematicLayers[i];
                                                if (selectedThematic.value === thematicJson.id) {
                                                    webMapJson.itemData.operationalLayers.push(thematicJson);
                                                    break;
                                                }
                                            }
                                        }
                            
                                        //add the selected geography
                                        var selectedGraphicsLayer = this.map.getLayer("selected-geography");
                                        for (var i = 0, sgl = selectedGraphicsLayer.graphics.length; i < sgl; i++) {
                                            //build the json object
                                            var graphic = selectedGraphicsLayer.graphics[i];
                                            var graphicObj = {
                                                "id": "selected-geography",
                                                "title": "Community",
                                                "featureCollection": {
                                                    "layers": [
                                                        {
                                                            "layerDefinition": {
                                                                "geometryType": "esriGeometryPolygon",
                                                                "objectIdField": "OBJECTID",
                                                                "type": "Feature Layer",
                                                                "typeIdField": "",
                                                                "drawingInfo": {
                                                                    "renderer": {
                                                                        "type": "simple",
                                                                        "symbol": graphic.symbol.toJson()
                                                                    },
                                                                    "fixedSymbols": true
                                                                },
                                                                "fields": [
                                                                    {
                                                                        "name": "OBJECTID",
                                                                        "alias": "OBJECTID",
                                                                        "type": "esriFieldTypeOID",
                                                                        "editable": false,
                                                                        "nullable": false,
                                                                        "domain": null
                                                                    }
                                                                ],
                                                                "name": "Community"
                                                            },
                                                            "featureSet": {
                                                                "features": [
                                                                    {
                                                                        "geometry": graphic.geometry.toJson(),
                                                                        "attributes": graphic.attributes
                                                                    }
                                                                ],
                                                                "geometryType": "esriGeometryPolygon"
                                                            }
                                                        }],
                                                    "showLegend": true
                                                },
                                                "visibility": true,
                                                "opacity": selectedGraphicsLayer.opacity
                                            };
                            
                                            webMapJson.itemData.operationalLayers.push(graphicObj);
                                        }
                            
                                        //get the property layers
                                        var selectedProperties = query('input[name="property-options"]:checked');
                                        if (selectedProperties && selectedProperties !== null) {
                                            for (var i = 0, _l = this.config.propertyLayers.length; i < _l; i++) {
                                                var propertyJson = this.config.propertyLayers[i];
                            
                                                for (var x = 0, xl = selectedProperties.length; x < xl; x++) {
                                                    if (selectedProperties[x].value === propertyJson.id) {
                                                        webMapJson.itemData.operationalLayers.push(propertyJson);
                                                        break;
                                                    }
                                                }
                                            }
                                        }
                            
                                        //get the map extent
                                        var wgsExtent = webMercatorUtils.webMercatorToGeographic(this.map.extent);
                                        var extent = [
                                            [wgsExtent.xmin, wgsExtent.ymin],
                                            [wgsExtent.xmax, wgsExtent.ymax]
                                        ]
                                        webMapJson.item.extent = extent;
                            
                                        //store the webmapjson in local storage
                                        this.webMapCollection.push({
                                            name: mapName,
                                            map: JSON.stringify(webMapJson)
                                        });
                            
                                        if (this.useLocalStorage === true) {
                                            window.localStorage.setItem(this.localStorageKey, JSON.stringify(this.webMapCollection))
                                        }
                            
                                        //refresh the maps table
                                        this.updateMapTable();
                            
                                        //reset the form and disable until the user re-enables
                                        this.resetForm();
                                        this.toggleForm(false);
                                        document.getElementById('chkNewMap').checked = false;
                                    },
                            
                                    load: function (mapDef) {
                                        var self = this;
                                        var returnD = new Deferred();
                            
                                        //hacky; fixing issue where height is reset on new map load
                                        var mHeight = document.getElementById("mapDiv").style.height;
                                        self.map.destroy();
                            
                                        //using HudWebApi MapController - gives the map some built in functionality.
                                        var d = new Deferred();
                                        var options = {
                                            config: mapDef,
                                            mapElementId: "mapDiv",
                                            deferred: d
                                        };
                                        var mapController = new MapController(options);
                                        d.then(function (map) {
                                            self.map = map;
                            
                                            document.getElementById("mapDiv").style.height = mHeight;
                                            self.map.resize();
                            
                                            //HACK - Fix for a bug in the JS API when loading
                                            //a map from the WebMapJson specification that
                                            //contains a featureCollection layer
                                            //Esri seems to append an '_0' to the layer name, but
                                            //does not update the layer name in the array of layer names:
                                            //map.layerIds
                                            var index = self.map.layerIds.indexOf("selected-geography");
                                            if (index > -1) {
                                                self.map.layerIds[index] = "selected-geography_0";
                                            }
                            
                                            returnD.resolve(map);
                            
                                            self.emit("map-loaded", { map: map });
                                        });
                            
                                        return returnD;
                                    },
                            
                                    delete: function (mapIndex) {
                                        this.webMapCollection.splice(parseInt(mapIndex), 1);
                            
                                        this.updateMapTable();
                            
                                        if (this.useLocalStorage === true) {
                                            window.localStorage.setItem(this.localStorageKey, JSON.stringify(this.webMapCollection));
                                        }
                                    },
                            
                                    updateMapTable: function () {
                                        var self = this;
                            
                                        var rowHtml = '';
                                        for (var i = 0, _l = this.webMapCollection.length; i < _l; i++) {
                                            rowHtml +=
                                                '<tr>' +
                                                    '<td>' + (i + 1).toString() + '</td>' +
                                                    '<td style="width:75%;">' + this.webMapCollection[i].name + '</td>' +
                                                    '<td><button class="btn btn-default btn-xs custom-map-delete" value="' + i.toString() + '">Delete</button></td>' +
                                                    '<td><button class="btn btn-default btn-xs custom-map-load" value="' + i.toString() + '">Load</button>' +
                                                '</tr>';
                                        }
                                        document.getElementById('custom-map-table').innerHTML = rowHtml;
                            
                                        var links = query('.custom-map-load');
                                        links.on('click', function (e) {
                                            //find the matching map
                                            var mapDef = JSON.parse(self.webMapCollection[parseInt(e.target.value)].map);
                                            self.load(mapDef);
                                        });
                            
                                        var deleteBtns = query('.custom-map-delete');
                                        deleteBtns.on('click', function (e) {
                                            self.delete(e.target.value);
                                        });
                                    },
                            
                                    addThematicLayer: function (layerId) {
                                        //remove existing thematic layer if it exists
                                        if (this.selectedThematicLayer !== null) {
                                            var id = this.selectedThematicLayer.id;
                                            this.map.removeLayer(this.selectedThematicLayer);
                                            if (this.map.layerIds.indexOf(id) > -1) { this.map.layerIds.splice(this.map.layerIds.indexOf(id), 1); }
                                        }
                            
                                        //add new selected layer
                                        for (var i = 0, _l = this.config.thematicLayers.length; i < _l; i++) {
                                            var l = this.config.thematicLayers[i];
                                            if (l.id === layerId) {
                                                var thematicLayer = new FeatureLayer(l.url, { id: l.id });
                                                thematicLayer.title = l.title;
                                                this.map.addLayer(thematicLayer, 0);
                                                this.selectedThematicLayer = thematicLayer;
                            
                                                if (this.map.layerIds.indexOf(thematicLayer.id) === -1) { this.map.layerIds.push(thematicLayer.id); }
                                                break;
                                            }
                                        }
                                    },
                            
                                    addPropertyLayer: function (layerId) {
                                        //add new selected layer
                                        for (var i = 0, _l = this.config.propertyLayers.length; i < _l; i++) {
                                            var l = this.config.propertyLayers[i];
                                            if (l.id === layerId) {
                                                var layer = new FeatureLayer(l.url, { id: l.id });
                                                layer.title = l.title;
                                                this.map.addLayer(layer);
                            
                                                if (this.map.layerIds.indexOf(layer.id) === -1) { this.map.layerIds.push(layer.id); }
                                                break;
                                            }
                                        }
                                    },
                            
                                    removePropertyLayer: function (layerId) {
                                        var layer = this.map.getLayer(layerId);
                                        if (layer) {
                                            var id = layer.id;
                                            this.map.removeLayer(layer);
                                            if (this.map.layerIds.indexOf(id) > -1) { this.map.layerIds.splice(this.map.layerIds.indexOf(id), 1); }
                                        }
                                    },
                            
                                    resetWebMap: function () {
                                        var mapDef = this.getDefaultWebMap();
                                        var d = this.load(mapDef);
                            
                                        var self = this;
                                        d.then(function (map) {
                                            if (self.defaultGraphic && self.defaultGraphic !== null) {
                            
                                                var gLayer = new GraphicsLayer({
                                                    id: "selected-geography",
                                                    opacity: 0.8
                                                });
                                                gLayer.name = "Community";
                                                map.addLayer(gLayer);
                            
                                                gLayer.add(self.defaultGraphic);
                            
                                                self.map.setExtent(self.defaultGraphic.geometry.getExtent(), true);
                                            }
                                            self.emit("map-loaded", { map: map });
                                        });
                                    },
                            
                                    getWebMapJsonTemplate: function () {
                                        var webMapJson = {
                                            item: {
                                                extent: [],
                                                title: "HUD EGIS CART - CUSTOM MAP"
                                            },
                                            itemData: {
                                                operationalLayers: [],
                                                baseMap: {
                                                    baseMapLayers: [],
                                                    title: "Basemap"
                                                },
                                                version: "1.7"
                                            }
                                        };
                            
                                        return webMapJson;
                                    },
                            
                                    getDefaultWebMap: function () {
                                        var defaultWebMap = {
                                            item: {
                                                title: "HUD CART",
                                                snippet: "HUD CART",
                                                extent: [
                                                    [
                                                        -139.4916,
                                                        10.7191
                                                    ],
                                                    [
                                                        -52.392,
                                                        59.5199
                                                    ]
                                                ]
                                            },
                                            itemData: {
                                                baseMap: {
                                                    baseMapLayers: [
                                                        {
                                                            id: "World_Dark_Gray_Base",
                                                            opacity: 1,
                                                            visibility: true,
                                                            url: "http://services.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer"
                                                        }
                                                    ],
                                                    title: "gray"
                                                },
                                                version: "1.7"
                                            }
                                        }
                            
                                        return defaultWebMap;
                                    },
                            
                                    localStorageAvailable: function () {
                                        try {
                                            var storage = window['localStorage'],
                                                x = '__storage_test__';
                                            storage.setItem(x, x);
                                            storage.removeItem(x);
                                            return true;
                                        }
                                        catch (e) {
                                            return false;
                                        }
                                    },
                            
                                    getConfig: function () {
                                        var d = new Deferred();
                                        var url = 'config/mapBuilder.json';
                            
                                        var r = xhr(url, {
                                            handleAs: 'json'
                                        });
                                        r.then(function (response) {
                                            d.resolve(response);
                                        },
                                        function (err) {
                                            //TODO
                                        });
                            
                                        return d;
                                    }
                                });
                            });