# Source: models/metadata/eml211/EMLGeoCoverage.js

    /* global define */
    define(['jquery', 'underscore', 'backbone', 'models/DataONEObject'],
        function ($, _, Backbone, DataONEObject) {

            /**
            * @class EMLGeoCoverage
            * @classdesc A description of geographic coverage of a dataset, per the EML 2.1.1 metadata standard
            * @extends Backbone.Model
            * @constructor
            */
            var EMLGeoCoverage = Backbone.Model.extend(
              /** @lends EMLGeoCoverage.prototype */{

                defaults: {
                    objectXML: null,
                    objectDOM: null,
                    parentModel: null,
                    description: null,
                    east: null,
                    north: null,
                    south: null,
                    west: null
                },

                initialize: function (attributes) {
                    if (attributes && attributes.objectDOM) this.set(this.parse(attributes.objectDOM));

                    //specific attributes to listen to
                    this.on("change:description " +
                        "change:east " +
                        "change:west " +
                        "change:south " +
                        "change:north",
                        this.trickleUpChange);
                },

                /*
                 * Maps the lower-case EML node names (valid in HTML DOM) to the camel-cased EML node names (valid in EML).
                 * Used during parse() and serialize()
                 */
                nodeNameMap: function () {
                    return {
                        "altitudemaximum": "altitudeMaximum",
                        "altitudeminimum": "altitudeMinimum",
                        "altitudeunits": "altitudeUnits",
                        "boundingaltitudes": "boundingAltitudes",
                        "boundingcoordinates": "boundingCoordinates",
                        "eastboundingcoordinate": "eastBoundingCoordinate",
                        "geographiccoverage": "geographicCoverage",
                        "geographicdescription": "geographicDescription",
                        "northboundingcoordinate": "northBoundingCoordinate",
                        "southboundingcoordinate": "southBoundingCoordinate",
                        "westboundingcoordinate": "westBoundingCoordinate"
                    }
                },

                /** Based on this example serialization
                <geographicCoverage scope="document">
                    <geographicDescription>Rhine-Main-Observatory</geographicDescription>
                    <boundingCoordinates>
                        <westBoundingCoordinate>9.0005</westBoundingCoordinate>
                        <eastBoundingCoordinate>9.0005</eastBoundingCoordinate>
                        <northBoundingCoordinate>50.1600</northBoundingCoordinate>
                        <southBoundingCoordinate>50.1600</southBoundingCoordinate>
                    </boundingCoordinates>
                </geographicCoverage>
                 **/
                parse: function (objectDOM) {

                    var modelJSON = {};

                    if (!objectDOM) {
                        if (this.get("objectDOM"))
                            var objectDOM = this.get("objectDOM");
                        else
                            return {};
                    }

                    //Create a jQuery object of the DOM
                    var $objectDOM = $(objectDOM);

                    //Get the geographic description
                    modelJSON.description = $objectDOM.children('geographicdescription').text();

                    //Get the bounding coordinates
                    var boundingCoordinates = $objectDOM.children('boundingcoordinates');
                    if (boundingCoordinates) {
                        modelJSON.east = boundingCoordinates.children('eastboundingcoordinate').text().replace("+", "");
                        modelJSON.north = boundingCoordinates.children('northboundingcoordinate').text().replace("+", "");
                        modelJSON.south = boundingCoordinates.children('southboundingcoordinate').text().replace("+", "");
                        modelJSON.west = boundingCoordinates.children('westboundingcoordinate').text().replace("+", "");
                    }

                    return modelJSON;
                },

                serialize: function () {
                    var objectDOM = this.updateDOM(),
                        xmlString = objectDOM.outerHTML;

                    //Camel-case the XML
                    xmlString = this.formatXML(xmlString);

                    return xmlString;
                },

                /*
                 * Makes a copy of the original XML DOM and updates it with the new values from the model.
                 */
                updateDOM: function () {
                    var objectDOM;

                    if (!this.isValid()) {
                        return "";
                    }

                    if (this.get("objectDOM")) {
                        objectDOM = $(this.get("objectDOM").cloneNode(true));
                    } else {
                        objectDOM = $(document.createElement("geographiccoverage"));
                    }

                    //If only one point is given, make sure both points are the same
                    if ((this.get("north") && this.get("west")) && (!this.get("south") && !this.get("east"))) {
                        this.set("south", this.get("north"));
                        this.set("east", this.get("west"));
                    }
                    else if ((this.get("south") && this.get("east")) && (!this.get("north") && !this.get("west"))) {
                        this.set("north", this.get("south"));
                        this.set("west", this.get("east"));
                    }

                    // Description
                    if (!objectDOM.children("geographicdescription").length)
                        objectDOM.append($(document.createElement("geographicdescription")).text(this.get("description")));
                    else
                        objectDOM.children("geographicdescription").text(this.get("description"));

                    // Create the bounding coordinates element
                    var boundingCoordinates = objectDOM.find("boundingcoordinates");
                    if (!boundingCoordinates.length) {
                        boundingCoordinates = document.createElement("boundingcoordinates");
                        objectDOM.append(boundingCoordinates);
                    }

                    //Empty out the coordinates first
                    $(boundingCoordinates).empty();

                    //Add the four coordinate values
                    $(boundingCoordinates).append($(document.createElement("westboundingcoordinate")).text(this.get("west")),
                        $(document.createElement("eastboundingcoordinate")).text(this.get("east")),
                        $(document.createElement("northboundingcoordinate")).text(this.get("north")),
                        $(document.createElement("southboundingcoordinate")).text(this.get("south")));

                    return objectDOM;
                },

                /**
                * Sometimes we'll need to add a space between error messages, but only if an
                * error has already been triggered. Use addSpace to accomplish this.
                *
                * @param {string} msg The string that will be appended
                * @param {bool} front A flag that when set will append the whitespace to the front of 'msg'
                * @return {string} The string that was passed in, 'msg', with whitespace appended
                */
                addSpace: function (msg, front) {
                    if (typeof front === "undefined") {
                        front = false;
                    }
                    if (msg) {
                        if (front) {
                            return (" " + msg);
                        }
                        return msg += " ";
                    }
                    return msg;
                },

                /**
                * Because the same error messages are used in a couple of different places, we centralize the strings
                * and access here.
                *
                * @param {string} area Specifies the area that the error message belongs to.
                * Browse through the switch statement to find the one you need.
                * @return {string} The error message
                */
                getErrorMessage: function (area) {
                    switch (area) {
                        case "north":
                            return "The Northwest latitude must be between -90 and 90.";
                            break;
                        case "east":
                            return "The Southeast longitude must be between -180 and 180.";
                            break;
                        case "south":
                            return "The Southeast latitude must be between -90 and 90.";
                            break;
                        case "west":
                            return "The Northwest longitude must be between -180 and 180.";
                            break;
                        case "missing":
                            return "Each coordinate must include a latitude AND longitude.";
                            break;
                        case "description":
                            return "Each location must have a description.";
                            break;
                        case "needPair":
                            return "Each location description must have at least one coordinate pair.";
                            break;
                        default:
                            return "";
                            break;
                    }
                },

                /**
                * Generates an object that describes the current state of each latitude
                * and longitude box. The status includes whether there is a value and
                * if the value is valid.
                *
                * @return {array} An array containing the current state of each coordinate box
                */
                getCoordinateStatus: function () {
                    var north = this.get("north"),
                        east = this.get("east"),
                        south = this.get("south"),
                        west = this.get("west");

                    return {
                        'north': {
                            isSet: typeof north !== "undefined" && north != null && north !== "",
                            isValid: this.validateCoordinate(north, -90, 90)
                        },
                        'east': {
                            isSet: typeof east !== "undefined" && east != null && east !== "",
                            isValid: this.validateCoordinate(east, -180, 180)
                        },
                        'south': {
                            isSet: typeof south !== "undefined" && south != null && south !== "",
                            isValid: this.validateCoordinate(south, -90, 90)
                        },
                        'west': {
                            isSet: typeof west !== "undefined" && west != null && west !== "",
                            isValid: this.validateCoordinate(west, -180, 180)
                        },
                    }
                },

                /**
                * Checks the status object for conditions that warrant an error message to the user. This is called
                * during the validation processes (validate() and updateModel()) after the status object has been
                * created by getCoordinateStatus().
                *
                * @param status The status object, holding the state of the coordinates
                * @return {string} Any errors that need to be displayed to the user
                    */
                generateStatusErrors: function (status) {
                    var errorMsg = "";

                    // Northwest Latitude
                    if (status.north.isSet && !status.north.isValid) {
                        errorMsg = this.addSpace(errorMsg);
                        errorMsg += this.getErrorMessage("north");
                    }
                    // Northwest Longitude
                    if (status.west.isSet && !status.west.isValid) {
                        errorMsg = this.addSpace(errorMsg);
                        errorMsg += this.getErrorMessage("west");
                    }
                    // Southeast Latitude
                    if (status.south.isSet && !status.south.isValid) {
                        errorMsg = this.addSpace(errorMsg);
                        errorMsg += this.getErrorMessage("south");
                    }
                    // Southeast Longitude
                    if (status.east.isSet && !status.east.isValid) {
                        errorMsg = this.addSpace(errorMsg);
                        errorMsg += this.getErrorMessage("east");
                    }
                    return errorMsg;

                },

                /**
                * This grabs the various location elements and validates the user input. In the case of an error,
                * we append an error string (errMsg) so that we display all of the messages at the same time. This
                * validates the entire location row by adding extra checks for a description and for coordinate pairs
                *
                * @return {string} The error messages that the user will see
                */
                validate: function () {
                    var errors = {};

                    if (!this.get("description")) {
                        errors.description = this.getErrorMessage("description");
                    }

                    var pointStatuses = this.getCoordinateStatus();
    /*
                    if (!this.checkForPairs(pointStatuses)) {
                        errorMsg = this.addSpace(errorMsg);
                        errorMsg += this.getErrorMessage("needPair");
                    }

                    if( this.hasMissingPoint(pointStatuses) ) {
                        //errorMsg = this.addSpace(errorMsg);
                        errors += this.getErrorMessage("missing");
                    }
    */
     //               errorMsg += this.addSpace(this.generateStatusErrors(pointStatuses), true);

                    if( !pointStatuses.north.isSet && !pointStatuses.south.isSet &&
                    		!pointStatuses.east.isSet && !pointStatuses.west.isSet){
                    	errors.north = this.getErrorMessage("needPair");
                    	errors.west  = "";
                    }

                    //Check that all the values are correct
                    if( pointStatuses.north.isSet && !pointStatuses.north.isValid )
                    	errors.north = this.getErrorMessage("north");
                    if( pointStatuses.south.isSet && !pointStatuses.south.isValid )
                    	errors.south = this.getErrorMessage("south");
                    if( pointStatuses.east.isSet && !pointStatuses.east.isValid )
                    	errors.east = this.getErrorMessage("east");
                    if( pointStatuses.west.isSet && !pointStatuses.west.isValid )
                    	errors.west = this.getErrorMessage("west");

                    if( pointStatuses.north.isSet && !pointStatuses.west.isSet )
                    	errors.west = this.getErrorMessage("missing");
                    else if( !pointStatuses.north.isSet && pointStatuses.west.isSet )
                    	errors.north = this.getErrorMessage("missing");
                    else if( pointStatuses.south.isSet && !pointStatuses.east.isSet )
                    	errors.east = this.getErrorMessage("missing");
                    else if( !pointStatuses.south.isSet && pointStatuses.east.isSet )
                    	errors.south = this.getErrorMessage("missing");

                    if( Object.keys(errors).length )
                    	return errors;
                    else
                    	return false;
                },

                /**
                 * Checks for any coordinates with missing counterparts.
                 *
                 * @param status The status of the coordinates
                 * @return {bool} True if there are missing coordinates, false otherwise
                 */
                hasMissingPoint: function (status) {
                    if ((status.north.isSet && !status.west.isSet) ||
                        (!status.north.isSet && status.west.isSet)) {
                        return true
                    } else if ((status.south.isSet && !status.east.isSet) ||
                        (!status.south.isSet && status.east.isSet)) {
                        return true;
                    }

                    return false;

                },

                /**
                 * Checks that there are either two or four coordinate values. If there aren't,
                 * it means that the user still needs to enter coordinates.
                 *
                 * @param status The current state of the coordinates
                 * @return {bool} True if there are pairs, false otherwise
                 */
                checkForPairs: function (status) {
                    var isSet = _.filter(status, function (coord) { return coord.isSet == true; });

                    if (isSet.length == 0) {
                        return false;
                    }
                    return true;
                },

                /**
                 * Validate a coordinate String by making sure it can be coerced into a number and
                 * is within the given bounds.
                 * Note: Min and max are inclusive
                 *
                 * @param value {string} The value of the edit area that will be validated
                 * @param min The minimum value that 'value' can be
                 * @param max The maximum value that 'value' can be
                 * @return {bool} True if the validation passed, otherwise false
                 */
                validateCoordinate: function (value, min, max) {

                    if (typeof value === "undefined" || value === null || value === "" && isNaN(value)) {
                        return false;
                    }

                    var parsed = Number(value);

                    if (isNaN(parsed)) {
                        return false;
                    }

                    if (parsed < min || parsed > max) {
                        return false;
                    }

                    return true;
                },

                /*
                * Climbs up the model heirarchy until it finds the EML model
                *
                * @return {EML211 or false} - Returns the EML 211 Model or false if not found
                */
                getParentEML: function(){
                  var emlModel = this.get("parentModel"),
                      tries = 0;

                  while (emlModel.type !== "EML" && tries < 6){
                    emlModel = emlModel.get("parentModel");
                    tries++;
                  }

                  if( emlModel && emlModel.type == "EML")
                    return emlModel;
                  else
                    return false;

                },

                trickleUpChange: function () {
                    this.get("parentModel").trigger("change");
                    this.get("parentModel").trigger("change:geoCoverage");
                },

                formatXML: function (xmlString) {
                    return DataONEObject.prototype.formatXML.call(this, xmlString);
                }
            });

            return EMLGeoCoverage;
        });