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

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

            /**
             * @class EMLNumericDomain
             * @classdesc EMLNumericDomain represents the measurement scale of an interval
             * or ratio measurement scale attribute, and is an extension of
             * EMLMeasurementScale.
             * @see https://github.com/NCEAS/eml/blob/master/eml-attribute.xsd
             */
            var EMLNumericDomain = Backbone.Model.extend(
                /** @lends EMLNumericDomain.prototype */{

            	type: "EMLNumericDomain",

                /* Attributes of an EMLNonNumericDomain object */
                defaults: function(){
                  return {
                    /* Attributes from EML, extends attributes from EMLMeasurementScale */
                    measurementScale: null, // the required name of this measurement scale
                    unit: null, // the required standard or custom unit definition
                    precision: null, // the precision of the observed number
                    numericDomain: {} // a required numeric domain object or its reference
                  }
                },

                /**
                 * The map of lower case to camel case node names
                 * needed to deal with parsing issues with $.parseHTML().
                 * Use this until we can figure out issues with $.parseXML().
                 */
                nodeNameMap: {
                    "standardunit": "standardUnit",
                    "customunit": "customUnit",
                    "numericdomain": "numericDomain",
                    "numbertype": "numberType"
                },

                /* Initialize an EMLNonNumericDomain object */
                initialize: function(attributes, options) {

                    this.on("change:numericDomain", this.trickleUpChange);

                },

                /**
                 * Parse the incoming measurementScale's XML elements
                 */
                parse: function(attributes, options) {

                    var $objectDOM;
                    var measurementScale;
                    var rootNodeName;

                    if ( attributes.objectDOM ) {
                        rootNodeName = $(attributes.objectDOM)[0].localName;
                        $objectDOM = $(attributes.objectDOM);
                    } else if ( attributes.objectXML ) {
                        rootNodeName = $(attributes.objectXML)[0].localName;
                        $objectDOM = $($(attributes.objectXML)[0]);
                    } else {
                        return {};
                    }

                    // do we have an appropriate measurementScale tree?
                    var index = _.indexOf(["measurementscale","interval", "ratio"], rootNodeName);
                    if ( index == -1 ) {
                        throw new Error("The measurement scale XML does not have a root " +
                            "node of 'measurementScale', 'interval', or 'ratio'.");
                    }

                    // If measurementScale is present, add it
                    if ( rootNodeName == "measurementscale" ) {
                        attributes.measurementScale = $objectDOM.children().first()[0].localName;
                        $objectDOM = $objectDOM.children().first();
                    } else {
                        attributes.measurementScale = $objectDOM.localName;
                    }

                    // Add the unit
                    var unitObject = {};
                    var unit = $objectDOM.children("unit");
                    var standardUnitNodes = unit.children("standardunit"),
                        customUnitNodes   = unit.children("customunit"),
                        standardUnit,
                        customUnit;

                    if ( standardUnitNodes.length ) {
                        standardUnit = standardUnitNodes.text();

                        if( standardUnit )
                          unitObject.standardUnit = standardUnit;
                    }
                    else if( customUnitNodes.length ){
                        customUnit = customUnitNodes.text();

                        if( customUnit )
                          unitObject.customUnit = customUnit;
                    }

                    attributes.unit = unitObject;

                    // Add the precision
                    var precision = $objectDOM.children("precision").text();
                    if ( precision ) {
                        attributes.precision = precision;
                    }

                    // Add the numericDomain
                    var numericDomainObject = {};
                    var numericDomain = $objectDOM.children("numericdomain");
                    var numberType;
                    var boundsArray = [];
                    var boundsObject;
                    var bounds;
                    var minimum;
                    var maximum;
                    var references;
                    if ( numericDomain ) {
                        // Add the XML id of the numeric domain
                        if ( $(numericDomain).attr("id") ) {
                            numericDomainObject.xmlID = $(numericDomain).attr("id");
                        }

                        // Add the numberType
                        numberType = $(numericDomain).children("numbertype");

                        if ( numberType ) {
                            numericDomainObject.numberType = numberType.text();

                            // Add optional bounds
                            bounds = $(numericDomain).children("bounds");
                            if ( bounds.length ) {
                                _.each(bounds, function(bound) {
                                    boundsObject = {}; // initialize on each
                                    minimum = $(bound).children("minimum").text();
                                    maximum = $(bound).children("maximum").text();
                                    if ( minimum && maximum ) {
                                        boundsObject.minimum = minimum;
                                        boundsObject.maximum = maximum;
                                    } else if ( minimum ) {
                                        boundsObject.minimum = minimum;
                                    } else if ( maximum ) {
                                        boundsObject.maximum = maximum;
                                    }
                                    // If one of min or max is defined, add to the bounds array
                                    if ( boundsObject.minimum || boundsObject.maximum ) {
                                        boundsArray.push(boundsObject);
                                    }
                                });
                            }
                            numericDomainObject.bounds = boundsArray;

                        } else {
                            // Otherwise look for references
                            references = $(numericDomain).children("references");
                            if ( references ) {
                                numericDomainObject.references = references.text();
                            }
                        }
                        attributes.numericDomain = numericDomainObject;
                    }
                    attributes.objectDOM = $objectDOM[0];

                    return attributes;
                },

                /* Serialize the model to XML */
                serialize: function() {
                    var objectDOM = this.updateDOM();
                    var xmlString = objectDOM.outerHTML;

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

                    return xmlString;
                },

                /* Copy the original XML DOM and update it with new values from the model */
                updateDOM: function(objectDOM) {
                    var nodeToInsertAfter;
                    var type = this.get("measurementScale");
                    if ( typeof type === "undefined") {
                        console.warn("Defaulting to an interval measurementScale.");
                        type = "interval";
                    }
                    if ( ! objectDOM ) {
                        objectDOM = this.get("objectDOM");
                    }
                    var objectXML = this.get("objectXML");

                    // If present, use the cached DOM
                    if ( objectDOM ) {
                        objectDOM = objectDOM.cloneNode(true);

                    // otherwise, use the cached XML
                    } else if ( objectXML ){
                        objectDOM = $(objectXML)[0].cloneNode(true);

                    // This is new, create it
                    } else {
                        objectDOM = document.createElement(type);

                    }

                    // Update the unit
                    var unit = this.get("unit");
                    var unitNode;
                    var unitTypeNode;
                    if ( unit ) {
                        // Remove any existing unit
                        $(objectDOM).find("unit").remove();

                        // Build a unit element, and populate a standard or custom child
                        unitNode = document.createElement("unit");

                        if ( typeof unit.standardUnit !== "undefined") {
                            unitTypeNode = document.createElement("standardUnit");
                            $(unitTypeNode).text(unit.standardUnit);
                        } else if ( typeof unit.customUnit !== "undefined" ) {
                            unitTypeNode = document.createElement("customUnit");
                            $(unitTypeNode).text(unit.customUnit);
                        } else {
                            // Hmm, unit isn't an object?
                            // Default to a standard unit
                            unitTypeNode = document.createElement("standardUnit");
                            if ( typeof unit === "string") {
                                $(unitTypeNode).text(unit);
                                console.warn("EMLNumericDomain.unit should be an object.");
                            } else {
                                // We're really striking out. Default to dimensionless.
                                $(unitTypeNode).text("dimensionless");
                                console.warn("Defaulting EMLNumericDomain.unit to dimensionless.");
                            }
                        }
                        $(unitNode).append(unitTypeNode);

                        // Add the unit to the DOM
                        nodeToInsertAfter = this.getEMLPosition(objectDOM, "unit");

                        if( ! nodeToInsertAfter ) {
                            $(objectDOM).prepend(unitNode);
                        } else {
                            $(nodeToInsertAfter).after(unitNode);
                        }
                    }

                    // Update the precision
                    if ( this.get("precision") ) {
                        if ( $(objectDOM).find("precision").length ) {
                            $(objectDOM).find("precision").text(this.get("precision"));
                        } else {
                            nodeToInsertAfter = this.getEMLPosition(objectDOM, "precision");

                            if( ! nodeToInsertAfter ) {
                                $(objectDOM).append($(document.createElement("precision"))
                                    .text(this.get("precision"))[0]);
                            } else {
                                $(nodeToInsertAfter).after(
                                    $(document.createElement("precision"))
                                        .text(this.get("precision"))[0]
                                );
                            }
                        }
                    }

                    // Update the numericDomain
                    var numericDomain = this.get("numericDomain");
                    var numericDomainNode = $(objectDOM).find("numericdomain")[0];
                    var numberType;
                    var numberTypeNode;
                    var minBound;
                    var maxBound;
                    var boundsNode;
                    var minBoundNode;
                    var maxBoundNode;
                    if ( numericDomain ) {

                    	var oldNumericDomainNode = $(numericDomainNode).clone();

                        // Remove the existing numericDomainNode node
                        if ( typeof numericDomainNode !== "undefined" ) {
                            numericDomainNode.remove();
                        }

                        // Build the new numericDomain node
                        numericDomainNode = document.createElement("numericdomain");

                        // Do we have numberType?
                        if ( typeof numericDomain.numberType !== "undefined" ) {
                            numberTypeNode = document.createElement("numbertype");
                            $(numberTypeNode).text(numericDomain.numberType);
                            $(numericDomainNode).append(numberTypeNode);
                        }

                        // Do we have bounds?
                        if ( typeof numericDomain.bounds !== "undefined" &&
                             numericDomain.bounds.length ) {

                            _.each(numericDomain.bounds, function(bound) {
                                minBound = bound.minimum;
                                maxBound = bound.maximum;
                                boundsNode = document.createElement("bounds");

                                var hasBounds = typeof minBound !== "undefined" || typeof maxBound !== "undefined";

                                if ( hasBounds ) {
                                    // Populate the minimum element
                                    if ( typeof minBound !== "undefined" ) {
                                        minBoundNode = $(document.createElement("minimum"));
                                        minBoundNode.text(minBound);

                                        var existingExclusive = oldNumericDomainNode.find("minimum").attr("exclusive");

                                        if( !existingExclusive || existingExclusive === "false" )
                                        	minBoundNode.attr("exclusive", "false");
                                        else
                                        	minBoundNode.attr("exclusive", "true");
                                    }

                                    // Populate the maximum element
                                    if ( typeof maxBound !== "undefined" ) {
                                        maxBoundNode = $(document.createElement("maximum"));
                                        maxBoundNode.text(maxBound);

                                        var existingExclusive = oldNumericDomainNode.find("maximum").attr("exclusive");

                                        if( !existingExclusive || existingExclusive === "false" )
                                        	maxBoundNode.attr("exclusive", "false");
                                        else
                                        	maxBoundNode.attr("exclusive", "true");
                                    }

                                    $(boundsNode).append(minBoundNode, maxBoundNode);
                                    $(numericDomainNode).append(boundsNode);

                                } else {
                                    // Do nothing. Content is missing, don't append the node
                                }
                            });
                        } else {
                            // Basically do nothing. Don't append the numericDomain element
                            // TODO: handle numericDomain.references

                        }
                        nodeToInsertAfter = this.getEMLPosition(objectDOM, "numericDomain");

                        if( ! nodeToInsertAfter ) {
                            $(objectDOM).append(numericDomainNode);
                        } else {
                            $(nodeToInsertAfter).after(numericDomainNode);
                        }
                    }
                    return objectDOM;
                },

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

                /**/
                getEMLPosition: function(objectDOM, nodeName) {
                    // TODO: set the node order
                    var nodeOrder = ["unit", "precision", "numericDomain"];

                    var position = _.indexOf(nodeOrder, nodeName);

                    // Append to the bottom if not found
                    if ( position == -1 ) {
                        return $(objectDOM).children().last()[0];
                    }

                    // Otherwise, go through each node in the node list and find the
                    // position where this node will be inserted after
                    for ( var i = position - 1; i >= 0; i-- ) {
                        if ( $(objectDOM).find(nodeOrder[i]).length ) {
                            return $(objectDOM).find(nodeOrder[i]).last()[0];
                        }
                    }
                },

                validate: function(){
                	var errors = {};

                	if(!this.get("unit"))
                		errors.unit = "Choose a unit.";

                	if( Object.keys(errors).length )
                		return errors;
                	else{

                		this.trigger("valid");
                		return;

                	}

                },

                /*
                * 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;

                },

                /* Let the top level package know of attribute changes from this object */
                trickleUpChange: function(){
                    MetacatUI.rootDataPackage.packageModel.set("changed", true);
                }

            });

            return EMLNumericDomain;
        }
    );