﻿// Class to handle results of parcel search
//  -- single/multiple results from tool (or queries)

dojo.declare("ParcelResults", null,
{
    id: "parcelResults",
    _parcelExportUrl: "/PropertyService/PropertyReportService.svc/ExportProperties",
    _parcelEmailUrl: "/PropertyService/PropertyReportService.svc/SendPropertyInfo",
    _printServiceUrl: "/MapPrintingService/MapPrintingService.svc",
    _minZoomScale: 5000,
    _params: null,
    _map: null,
    _featureSet: null,
    _countLabel: null,
    _resultsContainer: null,
    _detailsDiv: null,
    _reportDiv: null,

    _idField: "OBJECTID",
    _apnField: "APN",
    _addressField: "SITUSADDRESS", // Needed for report tabs
    _resultsFields: ['APN'],
    _detailsFields: [],
    _relatedLayers: [],
    _idTask: null,
    _idParams: null,
    _detailsTable: null,
    _backToResults: null,
    _resultsPanel: null,
    _graphicsLayer: null,
    _parcelLayerId: null,
    _currentFeature: null,
    _currentAttribs: null,

    _geomTask: null,
    _buffParams: null,
    _reportHelper: null,
    _propertyReportGraphics: null,
    _propertyReportAreaSymbol: null,
    _driveTimeTask: null,

    constructor: function (params) {
        this._params = params;
        this._map = params.map;
        this._idField = params.oidField;
        this._apnField = params.apnField;
        this._addressField = params.situsField;
        if (params.resultsFields && params.resultsFields.length > 0)
            this._resultsFields = params.resultsFields.split(',');
        // ResultsFields aliases are set in the map service
        if (params.detailsFields && params.detailsFields.length > 0)
            this._detailsFields = params.detailsFields.split(',');

        // parse related lyrs/fields
        var idLyrs = [];
        if (params.relatedLayers && params.relatedLayers.length > 0) {
            var relLyrs = params.relatedLayers.split(',');
            var lyrInfo, flds;
            for (var i = 0; i < relLyrs.length; i++) {
                lyrInfo = relLyrs[i].split(':');
                idLyrs.push(lyrInfo[1]);
                flds = lyrInfo[2].split('|');
                for (var f = 0; f < flds.length; f++) {
                    this._relatedLayers.push({ 'id': lyrInfo[1], 'field': flds[f] });
                }
            }
        }
        this._idTask = new esri.tasks.IdentifyTask(params.serviceUrl);
        this._idParams = new esri.tasks.IdentifyParameters();
        this._idParams.layerIds = idLyrs;
        this._idParams.layerOption = esri.tasks.IdentifyParameters.LAYER_OPTION_ALL;
        this._idParams.returnGeometry = false;
        this._idParams.tolerance = 0;

        this._countLabel = $("#parcelResultsCountLabel");
        this._resultsContainer = $("#parcelResultsContainer");
        this._detailsDiv = $("#parcelResultsDetailsDiv");
        this._detailsTable = $("#parcelDetailsTableDiv");
        this._backToResults = $("#parcelResultsBackToSites");
        // HACK: get TitlePane on main page (if present)
        this._resultsPanel = dijit.byId("parcelResultsPane");
        this._reportDiv = $('#' + parcelReportDivId);

        // Set up export dialog
        $(".parcelDetailsExportLink").colorbox({ inline: true, href: "#parcelResultsExportDialog", innerWidth: 450, innerHeight: 240 });
    },

    registerResultsListener: function (handler) {
        // Registers external event listeners to results being displayed - enables sharing results area on page
        $("#parcelResultsEvtHdlr").bind("resultsDisplayed", handler);
    },

    hideResults: function () {
        this._resultsContainer.hide();
        this._detailsDiv.hide();
        $('#parcelResultsClear').hide();
        // clear map graphics
        if (this._graphicsLayer)
            this._graphicsLayer.clear();
    },

    displayResults: function (featureSet, firstOnly, layerId, attribsAreAliased) {
        if (this._propertyReportGraphics)
            this._propertyReportGraphics.clear();
        if (this._reportHelper)
            this._reportHelper.clearReportGraphics();

        if (featureSet && featureSet.features && featureSet.features.length > 0) {
            this._featureSet = featureSet;
            var feats = featureSet.features;

            if (feats.length == 1 || firstOnly) {
                // Display details for the first feature found
                this.getRelatedInfo(feats[0], featureSet.fieldAliases, attribsAreAliased);
                //this.displayDetails(feats[0], featureSet.fieldAliases);
            }
            else {
                // Display a list of the features found
                this.displayAllResults(featureSet);
            }
        }
        else {
            this._countLabel.text("0");
            this._resultsContainer.show();
            this.hideLoading();
        }

        if (this._resultsPanel)
            this._resultsPanel.attr('open', true);
        $('#parcelResultsClear').show();

        // Inform listeners - used to share display area with other controls
        $("#parcelResultsEvtHdlr").trigger("resultsDisplayed", "parcelResults");

        this._parcelLayerId = layerId; // used by notification
    },

    displayAllResults: function (featureSet) {
        // Displays a list of the features, with links to display details
        this._detailsDiv.hide();
        if (this._graphicsLayer)
            this._graphicsLayer.clear();

        // Inserting table with single HTML content faster than manipulating DOM
        var html = [], h = 0;

        html[h] = "<table id='propertyResultsTable' style='width:100%;'><tbody>";
        var feats = featureSet.features;
        var attribs, idVal;
        //var aliases = featureSet.fieldAliases;

        for (var pcl, i = -1; pcl = feats[++i]; ) {
            attribs = pcl.attributes;
            idVal = attribs[this._idField];
            html[++h] = "<tr><td>" + (i + 1).toString() + ".</td>";
            for (var field, f = -1; field = this._resultsFields[++f]; ) {
                html[++h] = "<td><a href='javascript:parcelResults.getDetails(\"" + idVal + "\")'>";
                html[++h] = attribs[field] + "</a></td>";
            }
            html[++h] = "</tr>";
        }
        html[++h] = "</tbody></table>";
        $("#parcelResultsTableContainer").html(html.join(''));

        this._countLabel.text(feats.length);

        // TODO?: zoom to extent of features

        this._resultsContainer.show();
        this.hideLoading();
        // scroll to show top part of results in case it's below map
        $(document.body).animate({ scrollTop: ($('#parcelResultsTableContainer').offset().top - 400) }, 1000);
    },

    backToSites: function () {
        this._graphicsLayer.clear();
        if (this._featureSet && this._featureSet.features.length > 0)
            this.displayAllResults(this._featureSet);
    },

    clear: function () {
        // Clear results, highlights & reports
        this._featureSet = null;
        $('#parcelResultsClear').hide();
        if (this._resultsPanel)
            this._resultsPanel.attr('open', false);
        this._resultsContainer.hide();
        this._detailsDiv.hide();
        if (this._graphicsLayer)
            this._graphicsLayer.clear();
        if (this._propertyReportGraphics)
            this._propertyReportGraphics.clear();
        if (this._reportHelper)
            this._reportHelper.clearReportGraphics();
        // Hide reports (in separate part of page)
        this._reportDiv.hide();
        this._hideReports(); // GISPhandlers.js
    },

    getDetails: function (idVal) {
        // Handles user click in list of results. Finds the feature and displays details.
        if (this._featureSet) {
            var feature = null;
            for (var feat, i = -1; feat = this._featureSet.features[++i]; ) {
                if (feat.attributes[this._idField] == idVal) {
                    feature = feat;
                    break;
                }
            }
            if (feature) {
                this.showLoading();
                $("#parcelResultsBackToSites").show();
                this.getRelatedInfo(feature, this._featureSet.fieldAliases);
            }
            else
                alert("Details not available. Please retry your search, or contact Support.");
        }
        else
            alert("Details not available. Please repeat your search.");
    },

    getRelatedInfo: function (feature, aliases, attribsAreAliased) {
        // get info from other layers
        if (this._relatedLayers.length > 0) {
            // Get label pt for parcel (dropped for now to get all related features intersecting parcel)
            var geoms = [feature.geometry];
            //this._geomTask.labelPoints(geoms, dojo.hitch(this, function (lblPoints) {
            // Send label point for identify

            if (!this._geomTask) {
                // Do negative buffer to skip features only touching boundary
                this._geomTask = new esri.tasks.GeometryService(this._params.geomServer);
                this._buffParams = new esri.tasks.BufferParameters();
                this._buffParams.distances = [-0.5];
                this._buffParams.units = esri.tasks.GeometryService.UNIT_METER;
                this._buffParams.bufferSpatialReference = this._map.spatialReference;
                this._buffParams.outSpatialReference = this._map.spatialReference;
            }

            this._buffParams.geometries = [feature.geometry];

            this._geomTask.buffer(this._buffParams,
                dojo.hitch(this, function (geometries) {
                    if (geometries && geometries.length > 0)
                        this._sendRelatedInfo(geometries[0], feature, aliases, attribsAreAliased);
                    else
                        this._sendRelatedInfo(feature.geometry, feature, aliases, attribsAreAliased); // couldn't get inside buffer: just send parcel poly
                }),
                dojo.hitch(this, function (err) {
                    // skip to display parcel details
                    this.displayDetails(feature, aliases, null);
                })
            );

            return;
            //            this._idParams.geometry = feature.geometry; //lblPoints[0];
            //            this._idParams.mapExtent = this._map.extent;
            //            this._idParams.height = this._map.height;
            //            this._idParams.width = this._map.width;
            //            this._idTask.execute(this._idParams,
            //                dojo.hitch(this, function (results) {
            //                    // Get info on features and add to display
            //                    var relatedAttrs = [];
            //                    var attVal;
            //                    for (var gr, i = -1; gr = results[++i]; ) {
            //                        // find related lyr info for each result
            //                        for (var relLyr, j = -1; relLyr = this._relatedLayers[++j]; ) {
            //                            if (relLyr.id == gr.layerId) {
            //                                attVal = gr.feature.attributes[relLyr.field];
            //                                if (attVal && attVal.length > 0) {
            //                                    // if field is "OBJECTID", then show layer name instead of field
            //                                    if (relLyr.field == this._idField)
            //                                        relatedAttrs.push({ 'name': gr.layerName, 'value': 'Yes' });
            //                                    else {
            //                                        relatedAttrs.push({ 'name': relLyr.field, 'value': attVal });
            //                                    }
            //                                    break;
            //                                }
            //                            }
            //                        }
            //                    }
            //                    this.displayDetails(feature, aliases, relatedAttrs);
            //                }),
            //                dojo.hitch(this, function (err) {
            //                    alert('Error occurred getting related data for the parcel. If error persists, contact Support.');
            //                    this.displayDetails(feature, aliases, null);
            //                })
            //            );
        }
        else {
            this.displayDetails(feature, aliases, null);
        }
    },

    _sendRelatedInfo: function (selectPoly, feature, aliases, attribsAreAliased) {
        this._idParams.geometry = selectPoly;
        this._idParams.mapExtent = this._map.extent;
        this._idParams.height = this._map.height;
        this._idParams.width = this._map.width;
        this._idTask.execute(this._idParams,
                dojo.hitch(this, function (results) {
                    // Get info on features and add to display
                    var relatedAttrs = [];
                    var attVal, hasMultiple;
                    for (var gr, i = -1; gr = results[++i]; ) {
                        // find related lyr info for each result
                        for (var relLyr, j = -1; relLyr = this._relatedLayers[++j]; ) {
                            if (relLyr.id == gr.layerId) {
                                attVal = gr.feature.attributes[relLyr.field];
                                if (attVal && attVal.length > 0) {
                                    // if field is "OBJECTID", then show layer name instead of field
                                    if (relLyr.field == this._idField)
                                        relatedAttrs.push({ 'name': gr.layerName, 'value': 'Yes' });
                                    else {
                                        // if multiple features of same name, append to list
                                        hasMultiple = false;
                                        for (var r = 0, rl = relatedAttrs.length; r < rl; r++) {
                                            if (relatedAttrs[r].name == relLyr.field) {
                                                relatedAttrs[r].value += ", " + attVal;
                                                hasMultiple = true;
                                                break;
                                            }
                                        }
                                        if (!hasMultiple)
                                            relatedAttrs.push({ 'name': relLyr.field, 'value': attVal });
                                    }
                                    break;
                                }
                            }
                        }
                    }
                    this.displayDetails(feature, aliases, relatedAttrs, attribsAreAliased);
                }),
                dojo.hitch(this, function (err) {
                    alert('Error occurred getting related data for the parcel. If error persists, contact Support.');
                    this.displayDetails(feature, aliases, null);
                })
            );
    },

    displayDetails: function (feature, aliases, relatedAttribs, attribsAreAliased) {
        // Displays details about a single feature
        this._resultsContainer.hide();

        var html = [], h = 0;
        html[h] = "<table id='parcelDetailsTable' style='width:100%;'><tbody>";
        var attribs = feature.attributes;
        var name, attrVal;
        var rowIdx = 0;
        var address = "";
        this._currentFeature = feature;
        this._currentAttribs = {};

        // If a details field list available, only show those fields; otherwise display all fields
        if (this._detailsFields && this._detailsFields.length > 0) {
            for (var fldName, f = -1; fldName = this._detailsFields[++f]; ) {

                // Find/Identify tasks return attributes with alias names
                name = (attribsAreAliased) ? this.getFieldAlias(fldName, aliases) : fldName;

                attrVal = attribs[name];
                if (attrVal && $.trim(attrVal) != "" && attrVal.toString().toLowerCase() != "null") {
                    // createDetailsRow also adds to _currentAttribs
                    h = this.createDetailsRow(fldName, attrVal, aliases, html, h, rowIdx);
                    rowIdx++;
                }

                if (fldName == this._addressField)
                    address = attribs[name];
            }
        }
        else {
            for (var attrName in attribs) {
                if ($.trim(attribs[attrName]) != "") {
                    if (attrName == this._addressField)
                        address = attribs[attrName];

                    h = this.createDetailsRow(attrName, attribs[attrName], aliases, html, h, rowIdx);
                    rowIdx++;
                }
            }
        }

        // Add data from related layers, if any (will be null if couldn't retrieve any)
        if (relatedAttribs) {
            for (var relAttr, i = -1; relAttr = relatedAttribs[++i]; ) {
                h = this.createDetailsRow(relAttr.name, relAttr.value, [], html, h, rowIdx);
                rowIdx++;
            }
        }
        html[++h] = "</tbody></table>";

        this._detailsTable.html(html.join(''));
        if (this._featureSet && this._featureSet.features.length > 1)
            this._backToResults.show();
        this._detailsDiv.show();

        // scroll to show top part of results in case it's below map
        $(document.body).animate({ scrollTop: (this._detailsDiv.offset().top - 400) }, 1000);

        // Insert link value if set up (e.g. to permit system)
        var apn = attribs[this._apnField];
        if (this._params.linkUrl != "")
            $("#parcelDetailsLink").attr("href", this._params.linkUrl + apn);

        $(".parcelDemRepAddress").text(address);
        $(".parcelBusRepAddress").text(address);
        $(".parcelNotifRepAddress").text(address);

        // insert data into hidden inputs for reports
        var parcelPt = feature.geometry.getExtent().getCenter();
        // Convert WM to geographic
        var geoPt = esri.geometry.webMercatorToGeographic(parcelPt);
        var pclData = address + "," + geoPt.x + "," + geoPt.y;
        $(".parcelDemReportData").val(pclData);
        $(".parcelNotifReportApn").val(apn);
        $(".parcelNotifRepAddrHidden").val(address);

        this.zoomAndHighlightFeatures([feature]);

        if (parcelOpenStreetView) {
            // TODO: call component streetview
            setupStreetView(geoPt.y, geoPt.x, parcelPt, false);
        }
    },

    createDetailsRow: function (fieldName, fieldValue, aliases, html, htmlLen, rowIdx) {
        // Find the field alias, if any
        var name = this.getFieldAlias(fieldName, aliases);
        if (fieldName != this._apnField)
            name = this.initCaps(name.toLowerCase());
        html[++htmlLen] = "<tr";
        if (rowIdx % 2 == 0)
            html[++htmlLen] = " class='altRow'";
        html[++htmlLen] = "><th style='text-align:left'>" + name + "</th>";
        html[++htmlLen] = "<td>" + fieldValue + "</td></tr>";
        this._currentAttribs[name] = fieldValue;
        return htmlLen;
    },

    /*
    * Report functions
    */
    propertyDistanceReportInit: function () {
        // User clicked demog-distance report button: show circle on map
        var radiusMiles = this.getValidInteger(".parcelDemRepRadius", " distance around the parcel");
        if (!isNaN(radiusMiles)) {
            var radiusMeters = radiusMiles * 1609;
            this.showReportRadius(radiusMeters, 60 * 1609, "parcelDemRepBusy");
        }
    },

    businessReportInit: function () {
        // User clicked business report: show radius on map
        var radiusMiles = this.getValidInteger(".parcelBusRepRadius", "distance around the parcel");
        if (!isNaN(radiusMiles)) {

            // store parameters for report export
            this.initReportHelper();
            var addr = $(".parcelBusRepAddress").text();
            addr = addr || "";
            var params = $(".parcelDemReportData").val().split(",");
            var lng = params[1];
            var lat = params[2];
            this._reportHelper.busReportParams = { distance: radiusMiles, x: Number(lng),
                y: Number(lat), tn: this._params.busT, location: addr
            };

            this.showReportRadius(radiusMiles * 1609, 10 * 1609, "parcelBusRepBusy");
        }
        //        // Hand a reference to the report helper to the business/demog reports
        //        businessReportHelper = this._reportHelper;
        //        demogReportHelper = this._reportHelper;
        this._hideReports(); // switch to map
    },

    //    notificationReportInit: function () {
    //        var radiusFeet = this.getValidInteger(".parcelNotifRepRadius", "distance around the parcel");
    //        if (!isNaN(radiusFeet)) {
    //            var radiusMeters = radiusFeet / 3.28;
    //            this.showReportRadius(radiusMeters, 1000 / 3.28, "parcelNotifRepBusy");
    //        }
    //    },

    // Called by server
    notificationReport: function (resultsDivId) {
        var radiusFeet = this.getValidInteger(".parcelNotifRepRadius", "distance around the parcel");
        if (!isNaN(radiusFeet) && this._currentFeature) {
            $("#parcelNotifRepBusy").show();
            this.initReportHelper();

            if (this._reportHelper.validRadius(radiusFeet, 1000)) {
                var radiusMeters = radiusFeet / 3.28;
                this._reportHelper.createNotificationReport(this._currentFeature, radiusMeters, resultsDivId, this._params.serviceUrl, this._parcelLayerId, this._params.notifFields);
            }
        }
    },

    showReportRadius: function (radius, maxRadius, busyIndicatorId) {
        this.initReportHelper();

        this._reportDiv.show(); // make report panel visible

        // Get inputs
        var params = $(".parcelDemReportData").val().split(",");
        var lng = params[1];
        var lat = params[2];

        // store parameters for report export
        this.initReportHelper();
        this._reportHelper.demogReportBy = "distance";
        this._reportHelper.demogReportParams = { demographicsType: $(".parcelDemRepType").val(), distance: radius,
            x: Number(lng), y: Number(lat), location: $(".parcelDemRepAddress").text()
        };

        this._reportHelper.clearReportGraphics();
        return this._reportHelper.showReportRadiusOnMap(lng, lat, radius, maxRadius, busyIndicatorId);
    },

    createParcelDriveTimeReport: function () {
        this.initReportHelper();

        var params = $(".parcelDemReportData").val().split(",");
        var lng = params[1];
        var lat = params[2];
        var minutes = parseInt($('.parcelDemRepDriveTime').val());

        this._reportHelper.clearReportGraphics();
        this._reportHelper.createPropertyDriveTimeReport(lng, lat, minutes, dojo.hitch(this, this.driveTimeCallback));
    },

    driveTimeCallback: function (polygonData) {
        $(".parcelDemRepDriveTimePolygon").val(polygonData);

        // store parameters for report export
        this._reportHelper.demogReportParams = { demographicsType: $(".parcelDemRepType").val(), points: polygonData,
            minutes: $(".parcelDemRepDriveTime").val(), location: $(".parcelDemRepAddress").text()
        };
        this._reportHelper.demogReportBy = "polygon";

        // Trigger postback for report
        __doPostBack(parcelDemRepPnlUniqueId, "DriveTime");
    },

    initReportHelper: function () {
        if (!this._reportHelper) {
            var dtUrl = (driveTimeSource.toUpperCase() == "ESRI") ? esriDriveTimeService : saasDriveTimeService;
            this._reportHelper = new ReportHelper(map, driveTimeSource, dtUrl, parcelDemRepPnlUniqueId, parcelDemRepPolygonId, // "parcelDemRepDriveTimePolygon",
                "parcelDemRepDriveTimeBusy", this._params.geomServer, this._params.logoUrl);

            // Hand a reference to the report helper to the business/demog reports
            businessReportHelper = this._reportHelper;
            demogReportHelper = this._reportHelper;
        }
    },

    _hideReports: function () {
        // hideReports in map-tab-based apps only (SLO)
        if (typeof showMap != "undefined")
            showMap();
    },

    /*
    * Export function
    */

    _reportMapWidth: 500,
    _reportMapHeight: 500,
    _printClient: null,

    _exportBusy: function (isBusy, message) {
        var busyImg = $('#parcelResultsExportBusyDiv');
        if (isBusy)
            busyImg.css('visibility', 'visible');
        else
            busyImg.css('visibility', 'hidden');

        $('#parcelResultsExportMsg').text(message);
    },

    exportSite: function () {
        $('#parcelResultsExportResult').hide();
        var addMap = $('#parcelResultsExportMap').is(':checked');
        //var addLegend = $('#parcelResultsExportLegend').is(':checked');   // TODO: get legend

        if (addMap) {
            this._exportBusy(true, "Creating map image...");
            if (!this._printClient)
                this._printClient = new PrintUtility(map, this._printServiceUrl + "/json/ExportMap");

            this._printClient.exportMap(this._reportMapHeight, this._reportMapWidth,
                dojo.hitch(this, this._exportMapCallback),
                dojo.hitch(this, this._exportMapError));
        }
        else {
            var addReport = $('#parcelResultsExportReport').is(':checked');
            if (addReport) {
                this._exportBusy(true, "Creating report...");
                this.doExport("");
            }
            else  // No map or report data selected
                alert("Please select map and/or report data for export.");
        }
    },

    _exportMapCallback: function (mapUrl) {
        this._exportBusy(true, "Creating report...");
        this.doExport(mapUrl);
    },

    _exportMapError: function (msg) {
        // just export report data
        this._exportBusy(true, "Creating report...");
        this.doExport("");
    },

    doExport: function (mapUrl) {
        try {
            var format = $('#parcelResultsExportFormat').val();
            var addReport = $('#parcelResultsExportReport').is(':checked');
            var reportData = "";
            if (addReport && this._currentAttribs)
                reportData = dojo.toJson(this._currentAttribs);
            var dataType = "singleParcel";

            if (mapUrl.length > 0 && typeof _lbn !== "undefined") {
                var pr = (mapUrl.indexOf("?") > 0) ? "&" : "?";
                mapUrl += pr + "svr=" + _lbn;
            }

            if (format == "print")
                this._createPrintReport(mapUrl, this._currentAttribs, dataType);
            else if (format == "email")
                this._exportEmail(mapUrl, reportData, dataType);
            else {
                var title = $('#parcelResultsExportTitle').val();
                var data = { "exportType": dataType, "format": format, "propData": reportData,
                    "mapUrl": mapUrl, "mapWidth": this._reportMapWidth, "mapHeight": this._reportMapHeight,
                    "logoUrl": this._params.logoUrl, "title": title
                };

                this._sendExportRequest(data, this._parcelExportUrl, false);
            }
        }
        catch (err) {
            alert("Error occurred creating report. If problem persists, please contact Support.");
            this._exportBusy(false, "");
        }
    },

    _exportEmail: function (mapUrl, reportData, dataType) {

        var from = $('#parcelResultsEmailFrom').val();
        var to = $('#parcelResultsEmailTo').val();
        var subj = $('#parcelResultsEmailRe').val();
        var msg = $('#parcelResultsEmailMsg').val();

        if (from.indexOf(";") != -1 || !this._validateEmails(from, ";") || !this._validateEmails(to, ";")) {
            $('#parcelResultsExportResult').text("From and/or to addresses are not valid e-mail addresses.");
            $('#parcelResultsExportResult').show();
            this._exportBusy(false, "");
            return;
        }

        var data = { from: from, to: to, subject: subj, message: msg, exportType: dataType, data: reportData, mapUrl: mapUrl, logoUrl: this._params.logoUrl };

        this._sendExportRequest(data, this._parcelEmailUrl, true);
    },

    _sendExportRequest: function (data, serviceUrl, isEmail) {
        var jdata = dojo.toJson(data);
        var resCtl = this;

        $.ajax({
            url: serviceUrl,
            type: "POST",
            contentType: "application/json",
            data: jdata,
            success: function (data) {
                // check result for true/false - if false, tell user of error
                var resultOk = false;
                if (isEmail)
                    resultOk = data.d;
                else if (data.d)
                    if (data.d.indexOf("ERROR") < 0 && (data.d.indexOf("http") == 0 || data.d.indexOf("/") == 0))
                        resultOk = true;

                resCtl._exportBusy(false, "");
                $('#parcelResultsExportResult').show();
                if (isEmail) {
                    if (resultOk)
                        $('#parcelResultsExportResult').text("Email successfully sent");
                    else
                        $('#parcelResultsExportResult').text("Email failed. Check that the from/to addresses are valid, and that the subject and message are less than 200 characters.");
                }
                else {
                    if (resultOk) {

                        $('#parcelResultsExportResult').html("<a href='" + data.d + "' target='_blank'>Click here to download your report</a>");
                    }
                    else
                        $('#parcelResultsExportResult').text("Sorry, an error occurred creating the report. Please try again. If problem persists, contact Support.");
                }
                $('#parcelResultsExportResult').show();
            },
            error: function (err) {
                $('#parcelResultsExportResult').text("Error occurred creating the report. If problem persists, please contact Support");
                $('#parcelResultsExportResult').show();
                resCtl._exportBusy(false, "");
            }
        });
    },

    _getExportForm: function () {
        var formName = "parcelExportForm";
        var form = $('#' + formName);

        if (form.length == 0) {
            // For IE < 9.0, open new window to avoid the InfoBar issue
            var ie9 = navigator.userAgent.toLowerCase().indexOf("trident/5.0") > 0; // IE9 has Trident/5.0
            var target = ($.browser.msie && !ie9) ? "_blank" : "parcelResultsIFrame";

            var formStr = "<form id='" + formName + "' action='" + this._parcelExportUrl + "' method='post' target='" + target + "'>"
                + "<input type='hidden' name='title'/>"
                + "<input type='hidden' name='exportType'/>"
                + "<input type='hidden' name='format' />"
                + "<input type='hidden' name='mapUrl' />"
                + "<input type='hidden' name='mapWidth' /><input type='hidden' name='mapHeight'/>"
                + "<input type='hidden' name='logoUrl' />"
                + "<input type='hidden' name='propData' /></form>";
            $('body').append(formStr);
            form = $('#' + formName);
        }
        return form;
    },

    _createPrintReport: function (mapUrl, reportData, dataType) {
        var title = $('#parcelResultsExportTitle').val();
        var html = ["<html><head><title>" + title + "</title></head><body style='font-family:Verdana, sans-serif;'>"];
        if (this._params.logoUrl != "")
            html.push("<img alt='logo' src='" + this._params.logoUrl + "'/><br/>");
        html.push("<h2>" + title + "</h2>");
        html.push("<table style='border-top-color: Turquoise; border-bottom-color: Turquoise'>")
        html.push("<tr><th style='background-color:CornflowerBlue; text-align:center; color:White' colspan='2'>Property</th></tr>");
        var altRowStyle = " style='background-color:AliceBlue'";
        var rowIdx = 0;

        for (var prop in reportData) {
            html.push("<tr");
            if (rowIdx % 2 == 1)
                html.push(altRowStyle);
            html.push("><th style='text-align:left'>" + prop + "</th>");
            html.push("<td>" + reportData[prop] + "</td></tr>");
            rowIdx++;
        }
        html.push("</table>");

        if (mapUrl != "")
            html.push("<br/><img alt='map' src='" + mapUrl + "'/>");

        html.push("</body></html");

        var printWin = window.open("", "printWin", "width=800,height=600,scrollbars=yes,resizable=yes,toolbar=yes,menubar=yes,titlebar=yes");
        printWin.document.write(html.join(""));

        this._exportBusy(false, "");
    },

    /*
    *  Utility functions
    */
    _validateEmails: function (list, separator) {
        /// Simple email list validator - does not match IP addresses.
        var emails = list.split(separator);
        var isValid = true;
        var patt = /^.+@[^\.].*\.[a-z]{2,}$/;
        for (var i = 0; i < emails.length; i++) {
            if (!patt.test(emails[i])) {
                isValid = false;
                break;
            }
        }
        return isValid;
    },

    getValidInteger: function (selector, description) {
        var num = NaN;
        try {
            num = parseInt($(selector).val());
        } catch (e) {
            num = NaN
        }
        return num;
    },

    getFieldAlias: function (name, aliases) {
        for (var al in aliases) {
            if (al = name) {
                return aliases[al];
            }
        }
        return name;
    },

    zoomAndHighlightFeatures: function (graphics) {
        if (!this._graphicsLayer)
            this.addGraphicsLayer();
        else
            this._graphicsLayer.clear();

        var env = null;
        for (var gr, i = -1; gr = graphics[++i]; ) {
            if (env == null)
                env = gr.geometry.getExtent();
            else
                env = env.union(gr.geometry.getExtent());

            this._graphicsLayer.add(gr);
        }
        env = env.expand(3.0);

        // Check new scale vs. minimum
        var scale = env.getWidth() / this._map.width * 96 * 39.36;
        if (scale < this._minZoomScale) {
            // zoom too close - move out a bit
            var newCenter = env.getCenter();
            env = esri.geometry.getExtentForScale(this._map, this._minZoomScale);
            env = env.centerAt(newCenter);
        }
        this._map.setExtent(env);

        this._hideReports();
        this.hideLoading();
    },

    addGraphicsLayer: function () {
        var sfs = new esri.symbol.SimpleFillSymbol(
            esri.symbol.SimpleFillSymbol.STYLE_SOLID,
            new esri.symbol.SimpleLineSymbol(esri.symbol.SimpleLineSymbol.STYLE_SOLID, new dojo.Color([255, 0, 0]), 2),
            new dojo.Color([255, 255, 0, 0.25]));
        var rend = new esri.renderer.SimpleRenderer(sfs);
        this._graphicsLayer = new esri.layers.GraphicsLayer({ displayOnPan: false });
        this._graphicsLayer.setRenderer(rend);
        this._map.addLayer(this._graphicsLayer);
    },

    initCaps: function (str) {
        // Changes initial letter of each word to upper case
        return str.replace(/\b[a-z]/g, function ($0) { return $0.toUpperCase(); })
    },

    showDemogForm: function () {
        $(".parcelDemRepForm").show();
        $(".parcelDemRepResults").hide();
    },

    showNotifForm: function () {
        $('.parcelNotifRepResults').hide();
        $('.parcelNotifRepForm').show();
    },

    showLoading: function () {
        $('#imgMapLoading').show();  // HACK: img on main map div
    },

    hideLoading: function () {
        $('#imgMapLoading').hide();  // HACK: img on main map div
    }
});
