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

        /**
         * @classdesc EMLDateTimeDomain represents the measurement scale of a date/time
         * attribute.
         * @classcategory Models/Metadata/EML211
         * @see https://eml.ecoinformatics.org/schema/eml-attribute_xsd.html#AttributeType_AttributeType_measurementScale_dateTime
         */
        var EMLDateTimeDomain = Backbone.Model.extend(
          /** @lends EMLDateTimeDomain.prototype */{

        	type: "EMLDateTimeDomain",

            /* Attributes of an EMLDateTimeDomain object */
            el: "datetime",

            defaults: function(){
                return {
                  /* Attributes from EML */
                  formatString: null, // Required format string (e.g. YYYY)
                  dateTimePrecision: null, // The precision of the date time value
                  dateTimeDomain: null, // Zero or more bounds, or a references object
                  /* Attributes not from EML */
                  type: "dateTime",
                  parentModel: null, // The parent model this attribute belongs to
                  objectXML: null, // The serialized XML of this EML measurement scale
                  objectDOM: null  // The DOM of this EML measurement scale
                }
            },

            /*
             * 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: {
              "alternativetimescale" : "alternativeTimeScale",
                "datetime": "dateTime",
                "formatstring": "formatString",
                "datetimeprecision": "dateTimePrecision",
                "datetimedomain": "dateTimeDomain",
                "timescalename" : "timeScaleName",
                "timescaleageestimate" : "timeScaleAgeEstimate",
                "timescaleageuncertainty" : "timeScaleAgeUncertainty",
                "timescaleageexplanation" : "timeScaleAgeExplanation",
                "timescalecitation" : "timeScaleCitation"
            },

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

                this.on(
                    "change:formatString " +
                    "change:dateTimePrecision " +
                    "change:dateTimeDomain",
                    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 {};
                }

                // 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 formatString
                attributes.formatString = $objectDOM.find("formatstring").text();

                // Add the dateTimePrecision
                attributes.dateTimePrecision = $objectDOM.find("datetimeprecision").text();

                // Add in the dateTimeDomain
                var dateTimeDomain = $objectDOM.find("datetimedomain");
                if ( dateTimeDomain.length ) {
                    attributes.dateTimeDomain = this.parseDateTimeDomain(dateTimeDomain);

                }
                attributes.objectDOM = $objectDOM[0];

                return attributes;
            },

            /*
             * Parse the attribute/measurementScale/dateTime/dateTimeDomain fragment
             * returning a domain object with a bounds attribute consisting of an array
             * of objects with optional minimum and maximum attributes
             * For example:
             * {
             *     bounds: [
             *         {minimum: 2015, maximum: 2016},
             *         {minimum: 2017, maximum: 2018}
             *     ]
             * }
             * TODO: Support the references element
             */
            parseDateTimeDomain: function(dateTimeDomainXML) {
                var domain = {
                    bounds: []
                }
                var bounds = $(dateTimeDomainXML).find("bounds");

                _.each(bounds, function(bound) {
                    var bnd = {};
                    // Get the minimum if available
                    var min = $(bound).find("minimum").text();
                    if ( min ) {
                        bnd.minimum = min;
                        bnd.exclusive = $(bound).find("minimum").attr("exclusive");
                    }
                    // Get the maximum if available
                    var max = $(bound).find("maximum").text();
                    if ( max ) {
                        bnd.maximum = max;
                        bnd.exclusive = $(bound).find("maximum").attr("exclusive");
                    }
                    domain.bounds.push(bnd);

                }, domain);

                return domain;
            },

            /* 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("type") || "datetime";
                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 formatString
                if ( this.get("formatString") ) {
                    if ( $(objectDOM).find("formatstring").length ) {
                        $(objectDOM).find("formatstring").text(this.get("formatString"));
                    } else {
                        nodeToInsertAfter = this.getEMLPosition(objectDOM, "formatString");

                        if( ! nodeToInsertAfter ) {
                            $(objectDOM).append($(document.createElement("formatstring"))
                                .text(this.get("formatString"))[0]);
                        } else {
                            $(nodeToInsertAfter).after(
                                $(document.createElement("formatstring"))
                                    .text(this.get("formatString"))[0]
                            );
                        }
                    }
                }
                /* TODO: Uncomment out when formatstrings are better supported
                else{
                  $(objectDOM).find("formatstring").remove();
                }
                */

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

                        if( ! nodeToInsertAfter ) {
                            $(objectDOM).append($(document.createElement("datetimeprecision"))
                                .text(this.get("dateTimePrecision"))[0]);
                        } else {
                            $(nodeToInsertAfter).after(
                                $(document.createElement("datetimeprecision"))
                                    .text(this.get("dateTimePrecision"))[0]
                            );
                        }
                    }
                }
                /* TODO: Uncomment out when datetimeprecision if better supported
                else{
                  $(objectDOM).find("datetimeprecision").remove();
                }
                */

                // Update the dateTimeDomain
                var dateTimeDomain = this.get("dateTimeDomain");
                var dateTimeDomainNode = $(objectDOM).find("datetimedomain")[0];
                var minBound;
                var maxBound;
                var boundsNode;
                var minBoundNode;
                var maxBoundNode;
                if ( dateTimeDomain ) {

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

                    // Do we have bounds?
                    if ( typeof dateTimeDomain.bounds !== "undefined" &&
                         dateTimeDomain.bounds.length ) {
                        // Build the new dateTimeDomain node
                        dateTimeDomainNode = document.createElement("datetimedomain");

                        _.each(dateTimeDomain.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);

                                    if(bound.exclusive === true || bound.exclusive == "true")
                                      minBoundNode.attr("exclusive", "true");
                                    else
                                      minBoundNode.attr("exclusive", "false");

                                    $(boundsNode).append(minBoundNode);
                                }

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

                                    if(bound.exclusive === true || bound.exclusive == "true")
                                      maxBoundNode.attr("exclusive", "true");
                                    else
                                      maxBoundNode.attr("exclusive", "false");

                                    $(boundsNode).append(maxBoundNode);
                                }

                                //If the bounds are populated, append it to the date time domain node
                                if( $(boundsNode).children().length > 0 )
                                  $(dateTimeDomainNode).append(boundsNode);

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

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

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

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

            /**/
            getEMLPosition: function(objectDOM, nodeName) {
                var nodeOrder = ["formatString", "dateTimePrecision", "dateTimeDomain"];

                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];
                    }
                }
            },

            /*
            * 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);
            },

            /* Validate the values of this model */
            validate: function(){
            	if( !this.get("formatString") )
            		return { formatString: "Choose a date-time format." }
            	else{

            		this.trigger("valid");
            		return;

            	}
            }

        });

        return EMLDateTimeDomain;
    }
);
