# Source: src/js/views/metadata/EMLEntityView.js

    /* global define */
    define(['underscore', 'jquery', 'backbone', 'localforage',
            'models/DataONEObject', 'models/metadata/eml211/EMLAttribute', 'models/metadata/eml211/EMLEntity',
            'views/DataPreviewView',
            'views/metadata/EMLAttributeView',
            'text!templates/metadata/eml-entity.html',
            'text!templates/metadata/eml-attribute-menu-item.html',
            'common/Utilities'],
        function(_, $, Backbone, LocalForage, DataONEObject, EMLAttribute, EMLEntity,
            DataPreviewView,
            EMLAttributeView,
            EMLEntityTemplate,
            EMLAttributeMenuItemTemplate,
            Utilities){

            /**
            * @class EMLEntityView
            * @classdesc An EMLEntityView shows the basic attributes of a DataONEObject, as described by EML
            * @classcategory Views/Metadata
            * @screenshot views/metadata/EMLEntityView.png
            * @extends Backbone.View
            */
            var EMLEntityView = Backbone.View.extend(
              /** @lends EMLEntityView.prototype */{

                tagName: "div",

                className: "eml-entity modal hide fade",

                id: null,

                /* The HTML template for an entity */
                template: _.template(EMLEntityTemplate),
                attributeMenuItemTemplate: _.template(EMLAttributeMenuItemTemplate),
                fillButtonTemplateString: '<button class="btn btn-primary fill-button"><i class="icon-magic"></i> Fill from file</button>',

                /**
                 * A list of file formats that can be auto-filled with attribute information
                 * @type {string[]}
                 * @since 2.15.0
                 */
                fillableFormats: [
                  "text/csv"
                ],

                /* Events this view listens to */
                events: {
                  "change" : "saveDraft",
                  "change input" : "updateModel",
                  "change textarea" : "updateModel",
                  "click .entity-container > .nav-tabs a" : "showTab",
                  "click .attribute-menu-item" : "showAttribute",
                  "mouseover .attribute-menu-item .remove" : "previewAttrRemove",
                  "mouseout .attribute-menu-item .remove"  : "previewAttrRemove",
                  "click .attribute-menu-item .remove" : "removeAttribute",
                  "click .fill-button": "handleFill"
                },

                initialize: function(options){
                  if(!options)
                    var options = {};

                  this.model = options.model || new EMLEntity();
                  this.DataONEObject = options.DataONEObject;
                },

                render: function(){

                  this.renderEntityTemplate();

                  this.renderPreview();

                  this.renderAttributes();

                  this.renderFillButton();

                  this.listenTo(this.model, "invalid", this.showValidation);
                  this.listenTo(this.model, "valid", this.showValidation);

                },

                renderEntityTemplate: function(){
                  var modelAttr = this.model.toJSON();

                  if(!modelAttr.entityName)
                    modelAttr.title = "this data";
                  else
                    modelAttr.title = modelAttr.entityName;

                  modelAttr.uniqueId = this.model.cid;

                  this.$el.html(this.template( modelAttr ));

                  //Initialize the modal window
                  this.$el.modal();

                   //Set the menu height
                  var view = this;
                   this.$el.on("shown", function(){
                     view.adjustHeight();
                     view.setMenuWidth();

                     window.addEventListener('resize', function(event){
                       view.adjustHeight();
                       view.setMenuWidth();
                     });
                   });

                  this.$el.on("hidden", function(){
                    view.showValidation();
                  });

                },

                renderPreview: function(){
                  //Get the DataONEObject model
                  if(this.DataONEObject){
                    var dataPreview = new DataPreviewView({
                      model: this.DataONEObject
                    });
                    dataPreview.render();
                    this.$(".preview-container").html(dataPreview.el);

                    if(dataPreview.$el.children().length){
                      this.$(".description").css("width", "calc(100% - 310px)");
                    }
                    else
                      dataPreview.$el.remove();
                  }
                },

                renderAttributes: function(){
                  //Render the attributes
                  var attributes      = this.model.get("attributeList"),
                    attributeListEl = this.$(".attribute-list"),
                    attributeMenuEl = this.$(".attribute-menu");

                  _.each(attributes, function(attr){

                    //Create an EMLAttributeView
                    var view = new EMLAttributeView({
                      model: attr
                    });

                    //Create a link in the attribute menu
                    var menuItem = $(this.attributeMenuItemTemplate({
                        attrId: attr.cid,
                        attributeName: attr.get("attributeName"),
                        classes: ""
                      })).data({
                  model: attr,
                  attributeView: view
                  });
                    attributeMenuEl.append(menuItem);
                    menuItem.find(".tooltip-this").tooltip();

                    this.listenTo(attr, "change:attributeName", function(attr){
                      menuItem.find(".name").text(attr.get("attributeName"));
                    });

                    view.render();

                    attributeListEl.append(view.el);

                    view.$el.hide();

                    this.listenTo(attr, "change",  this.addAttribute);
                    this.listenTo(attr, "invalid", this.showAttributeValidation);
                    this.listenTo(attr, "valid",   this.hideAttributeValidation);

                  }, this);

                  //Add a new blank attribute view at the end
                  this.addNewAttribute();

                  //If there are no attributes in this EML model yet,
                  //then make sure we show a new add attribute button when the user starts typing
                  if(attributes.length == 0){
                    var onlyAttrView = this.$(".attribute-menu-item").first().data("attributeView"),
                      view = this,
                      keyUpCallback = function(){
                        //This attribute is no longer new
                        view.$(".attribute-menu-item.new").first().removeClass("new");
                        view.$(".attribute-list .eml-attribute.new").first().removeClass("new");

                        //Add a new attribute link and view
                        view.addNewAttribute();

                        //Don't listen to keyup anymore
                        onlyAttrView.$el.off("keyup", keyUpCallback);
                      };

                    onlyAttrView.$el.on("keyup", keyUpCallback);
                  }

                //Activate the first navigation item
                var firstAttr = this.$(".side-nav-item").first();
                firstAttr.addClass("active");

                //Show the first attribute view
                firstAttr.data("attributeView").$el.show();

                firstAttr.data("attributeView").postRender();

                },

                renderFillButton: function() {
                  var formatGuess = this.model.get("dataONEObject")
                    ? this.model.get("dataONEObject").get("formatId")
                    : this.model.get("entityType");

                  if (!_.contains(this.fillableFormats, formatGuess)) {
                    return;
                  }

                  var target = this.$(".fill-button-container");

                  if (!target.length === 1) {
                    return;
                  }

                  var btn = $(this.fillButtonTemplateString);
                  $(target).html(btn);
                },

                updateModel: function(e){
                  var changedAttr = $(e.target).attr("data-category");

                  if(!changedAttr) return;

                  var emlModel = this.model.getParentEML(),
                      newValue = emlModel? emlModel.cleanXMLText($(e.target).val()) : $(e.target).val();

                  this.model.set(changedAttr, newValue);

                  this.model.trickleUpChange();

                },

                addNewAttribute: function(){

                  //Check if there is already a new attribute view
                  if( this.$(".attribute-list .eml-attribute.new").length ){
                    return;
                  }

                  var newAttrModel = new EMLAttribute({
                      parentModel: this.model,
                            xmlID: DataONEObject.generateId()
                      }),
                      newAttrView  = new EMLAttributeView({
                        isNew: true,
                        model: newAttrModel
                      });

                  newAttrView.render();
                  this.$(".attribute-list").append(newAttrView.el);
                  newAttrView.$el.hide();

                  //Change the last menu item if it still says "Add attribute"
                  if(this.$(".attribute-menu-item").length == 1){
                    var firstAttrMenuItem = this.$(".attribute-menu-item").first();

                    if( firstAttrMenuItem.find(".name").text() == "Add attribute" ){
                      firstAttrMenuItem.find(".name").text("New attribute");
                      firstAttrMenuItem.find(".add").hide();
                    }
                  }

                  //Create the new menu item
                  var menuItem = $(this.attributeMenuItemTemplate({
                      attrId: newAttrModel.cid,
                      attributeName: "Add attribute",
                      classes: "new"
                    })).data({
                      model: newAttrModel,
                      attributeView: newAttrView
                    });
                  menuItem.find(".add").show();
                  this.$(".attribute-menu").append(menuItem);
                  menuItem.find(".tooltip-this").tooltip();

                  //When the attribute name is changed, update the navigation
                  this.listenTo(newAttrModel, "change:attributeName", function(attr){
                    menuItem.find(".name").text(attr.get("attributeName"));
                    menuItem.find(".add").hide();
                  });

                  this.listenTo(newAttrModel, "change",  this.addAttribute);
                  this.listenTo(newAttrModel, "invalid", this.showAttributeValidation);
                  this.listenTo(newAttrModel, "valid",   this.hideAttributeValidation);
                },

                addAttribute: function(emlAttribute){
                  //Add the attribute to the attribute list in the EMLEntity model
                  if( !_.contains(this.model.get("attributeList"), emlAttribute) )
                    this.model.addAttribute(emlAttribute);
                },

                removeAttribute: function(e){
                  var removeBtn = $(e.target);

                  var menuItem  = removeBtn.parents(".attribute-menu-item"),
                    attrModel = menuItem.data("model");

                  if(attrModel){
                    //Remove the attribute from the model
                    this.model.removeAttribute(attrModel);

                    //If this menu item is active, then make the next attribute active instead
                    if(menuItem.is(".active")){
                      var nextMenuItem = menuItem.next();

                      if(!nextMenuItem.length || nextMenuItem.is(".new")){
                        nextMenuItem = menuItem.prev();
                      }

                      if(nextMenuItem.length){
                        nextMenuItem.addClass("active");

                        this.showAttribute(nextMenuItem.data("model"));
                      }
                    }

                    //Remove the elements for this attribute from the page
                    menuItem.remove();
                    this.$(".eml-attribute[data-attribute-id='" + attrModel.cid + "']").remove();
                    $(".tooltip").remove();

                    this.model.trickleUpChange();
                  }
                },

                adjustHeight: function(e){
                  var contentAreaHeight = this.$(".modal-body").height() - this.$(".entity-container .nav-tabs").height();

                  this.$(".attribute-menu, .attribute-list").css("height", contentAreaHeight + "px");
                },

                setMenuWidth: function(){

                  this.$(".entity-container .nav").width( this.$el.width() );

                },

                /**
                 * Shows the attribute in the attribute editor
                 * @param {Event} e - JS event or attribute model
                 */
                showAttribute: function(e){

                  if(e.target){
                         var clickedEl = $(e.target),
                             menuItem = clickedEl.is(".attribute-menu-item") || clickedEl.parents(".attribute-menu-item");

                      if(clickedEl.is(".remove"))
                        return;
                  }
                  else{
                    var menuItem = this.$(".attribute-menu-item[data-attribute-id='" + e.cid + "']");
                  }

                  if(!menuItem)
                    return;

                  //Validate the previously edited attribute
                  //Get the current active attribute
                  var activeAttrTab = this.$(".attribute-menu-item.active");

                  //If there is a currently-active attribute tab,
                  if( activeAttrTab.length ){
                    //Get the attribute list from this view's model
                    var emlAttributes = this.model.get("attributeList");

                    //If there is an EMLAttribute list,
                    if( emlAttributes && emlAttributes.length ){

                      //Get the active EMLAttribute
                      var activeEMLAttribute = _.findWhere(emlAttributes, { cid: activeAttrTab.attr("data-attribute-id") });

                      //If there is an active EMLAttribute model, validate it
                      if( activeEMLAttribute ){
                        activeEMLAttribute.isValid();
                      }

                    }

                  }

                  //If the user clicked on the add attribute link
                  if( menuItem.is(".new") && this.$(".new.attribute-menu-item").length < 2 ){

                    //Change the attribute menu item
                    menuItem.removeClass("new").find(".name").text("New attribute");
                    this.$(".eml-attribute.new").removeClass("new");
                    menuItem.find(".add").hide();

                    //Add a new attribute view and menu item
                    this.addNewAttribute();

                    //Scroll the attribute menu to the bottom so that the "Add New" button is always visible
                    var attrMenuHeight = this.$(".attribute-menu").scrollTop() + this.$(".attribute-menu").height();
                    this.$(".attribute-menu").scrollTop( attrMenuHeight );
                  }

                  //Get the attribute view
                  var attrView = menuItem.data("attributeView");

                  //Change the active attribute in the menu
                  this.$(".attribute-menu-item.active").removeClass("active");
                  menuItem.addClass("active");

                  //Hide the old attribute view
                  this.$(".eml-attribute").hide();
                  //Show the new attribute view
                  attrView.$el.show();

                  //Scroll to the top of the attribute view
                  this.$(".attribute-list").scrollTop(0);

                  attrView.postRender();
                },

                /**
                 * Show the attribute validation errors in the attribute navigation menu
                 * @param {EMLAttribute} attr
                 */
                showAttributeValidation: function(attr){

                  var attrLink = this.$(".attribute-menu-item[data-attribute-id='" + attr.cid + "']").find("a");

                  //If the validation is already displayed, then exit
                  if(attrLink.is(".error")) return;

                  var errorIcon = $(document.createElement("i")).addClass("icon icon-exclamation-sign error icon-on-left");

                  attrLink.addClass("error").prepend(errorIcon);
                },

                /**
                 * Hide the attribute validation errors from the attribute navigation menu
                 */
                hideAttributeValidation: function(attr){
                  this.$(".attribute-menu-item[data-attribute-id='" + attr.cid + "']")
                    .find("a").removeClass("error").find(".icon.error").remove();
                },

                /**
                 * Show the user what will be removed when this remove button is clicked
                 */
                previewAttrRemove: function(e){
                  var removeBtn = $(e.target);

                  removeBtn.parents(".attribute-menu-item").toggleClass("remove-preview");
                },

                /**
                *
                * Will display validation styling and messaging. Should be called after
                * this view's model has been validated and there are error messages to display
                */
                showValidation: function(){

                  //Reset the error messages and styling
                  //Only change elements inside the overview-container which contains only the
                  // EMLEntity metadata. The Attributes will be changed by the EMLAttributeView.
                  this.$(".overview-container .notification").text("");
                  this.$(".overview-tab .icon.error, .attributes-tab .icon.error").remove();
                  this.$(".overview-container, .overview-tab a, .attributes-tab a, .overview-container .error").removeClass("error");

                  var overviewTabErrorIcon  = false,
                      attributeTabErrorIcon = false;

                  _.each( this.model.validationError, function(errorMsg, category){

                    if( category == "attributeList" ){

                      //Create an error icon for the Attributes tab
                      if( !attributeTabErrorIcon ){
                        var errorIcon = $(document.createElement("i"))
                                          .addClass("icon icon-on-left icon-exclamation-sign error")
                                          .attr("title", "There is missing information in this tab");

                        //Add the icon to the Overview tab
                        this.$(".attributes-tab a").prepend(errorIcon).addClass("error");
                      }

                      return;
                    }

                    //Get all the elements for this category and add the error class
                    this.$(".overview-container [data-category='" + category + "']").addClass("error");
                    //Get the notification element for this category and add the error message
                    this.$(".overview-container .notification[data-category='" + category + "']").text(errorMsg);

                    //Create an error icon for the Overview tab
                    if( !overviewTabErrorIcon ){
                      var errorIcon = $(document.createElement("i"))
                                        .addClass("icon icon-on-left icon-exclamation-sign error")
                                        .attr("title", "There is missing information in this tab");

                      //Add the icon to the Overview tab
                      this.$(".overview-tab a").prepend(errorIcon).addClass("error");

                      overviewTabErrorIcon = true;
                    }

                  }, this);

                },

                /**
                 * Show the entity overview or attributes tab
                 * depending on the click target
                 * @param {Event} e
                 */
                showTab: function(e){
                  e.preventDefault();

                  //Get the clicked link
                   var link = $(e.target);

                   //Remove the active class from all links and add it to the new active link
                   this.$(".entity-container > .nav-tabs li").removeClass("active");
                   link.parent("li").addClass("active");

                   //Hide all the panes and show the correct one
                   this.$(".entity-container > .tab-content > .tab-pane").hide();
                   this.$(link.attr("href")).show();

                },

                /**
                 * Show the entity in a modal dialog
                 */
                show: function(){

                  this.$el.modal('show');

                },

                /**
                 * Hide the entity modal dialog
                 */
                hide: function(){
                  this.$el.modal('hide');
                },

                /**
                 * Save a draft of the parent EML model
                 */
                saveDraft: function() {
                  var view = this;

                  try {
                    var model = this.model.getParentEML();
                    var draftModel = model.clone();
                    var title = model.get("title") || "No title";

                    LocalForage.setItem(model.get("id"),
                    {
                      id: model.get("id"),
                      datetime: (new Date()).toISOString(),
                      title: Array.isArray(title) ? title[0] : title,
                      draft: draftModel.serialize()
                    }).then(function() {
                      view.clearOldDrafts();
                    });
                  } catch (ex) {
                    console.log("Error saving draft:", ex);
                  }
                },

                /**
                 * Clear older drafts by iterating over the sorted list of drafts
                 * stored by LocalForage and removing any beyond a hardcoded limit.
                 */
                 clearOldDrafts: function() {
                   var drafts = [];

                  try {
                    LocalForage.iterate(function(value, key, iterationNumber) {
                    // Extract each draft
                    drafts.push({
                        key: key,
                        value: value
                      });
                    }).then(function(){
                      // Sort by datetime
                      drafts = _.sortBy(drafts, function(draft) {
                        return draft.value.datetime.toString();
                      }).reverse();
                    }).then(function() {
                      _.each(drafts, function(draft, i) {
                        var age = (new Date()) - new Date(draft.value.datetime);
                        var isOld = (age / 2678400000) > 1; // ~31days
                        // Delete this draft is not in the most recent 100 or
                        // if older than 31 days
                        var shouldDelete = i > 100 || isOld;
                          if (!shouldDelete) {
                            return;
                          }

                          LocalForage.removeItem(draft.key).then(function() {
                            // Item should be removed
                          });
                        })
                      });
                  }
                  catch (ex) {
                    console.log("Failed to clear old drafts: ", ex);
                  }
                },

                /**
                 * Handle the click event on the fill button
                 *
                 * @param {Event} e - The click event
                 * @since 2.15.0
                 */
                handleFill: function(e) {
                  var d1Object = this.model.get("dataONEObject");

                  if (!d1Object) {
                    return;
                  }

                  var file = d1Object.get("uploadFile");

                  try {
                    if (!file) {
                      this.handleFillViaFetch();
                    } else {
                      this.handleFillViaFile(file);
                    }
                  } catch (error) {
                    console.log("Error while attempting to fill", error);
                    view.updateFillButton(
                      '<i class="icon-warning-sign"></i> Couldn\'t fill'
                    );
                  }
                },

                /**
                 * Handle the fill event using a File object
                 *
                 * @param {File} file - A File object to fill from
                 * @since 2.15.0
                 */
                handleFillViaFile: function(file) {
                  var view = this;

                  Utilities.readSlice(file, this, function (event) {
                    if (event.target.readyState !== FileReader.DONE) {
                      return;
                    }

                    view.tryParseAndFillAttributeNames.bind(view)(event.target.result);
                  });
                },

                /**
                 * Handle the fill event by fetching the object
                 * @since 2.15.0
                 */
                handleFillViaFetch: function() {
                  var view = this;

                  var requestSettings = {
                    url:  MetacatUI.appModel.get("objectServiceUrl") + encodeURIComponent(this.model.get("dataONEObject").get("id")),
                    method: "get",
                    success: view.tryParseAndFillAttributeNames.bind(this),
                    error: function(error) {
                      view.updateFillButton('<i class="icon-warning-sign"></i> Couldn\'t fill');
                      console.error("Error fetching DataObject to parse out headers", error);
                    }
                  }

                  this.updateFillButton('<i class="icon-time"></i> Please wait...', true);
                  this.disableFillButton();

                  requestSettings = _.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings());
                  $.ajax(requestSettings);
                },

                /**
                 * Attempt to parse header and fill attributes names
                 *
                 * @param {string} content - Part of a file to attempt to parse
                 * @since 2.15.0
                 */
                tryParseAndFillAttributeNames: function(content) {
                  var names = Utilities.tryParseCSVHeader(content);

                  if (names.length === 0) {
                    this.updateFillButton('<i class="icon-warning-sign"></i> Couldn\'t fill');
                  } else {
                    this.updateFillButton('<i class="icon-ok"></i> Filled!');
                  }

                  //Make sure the button is enabled
                  this.enableFillButton();

                  this.updateAttributeNames(names);
                },

                /**
                 * Update attribute names from an array
                 *
                 * This will update existing attributes' names or create new
                 * attributes as needed. This also performs a full re-render.
                 *
                 * @param {string[]} names - A list of names to apply
                 * @since 2.15.0
                 */
                updateAttributeNames: function(names) {
                  if (!names) {
                    return;
                  }

                  var attributes = this.model.get("attributeList");

                  //Update the name of each attribute or create a new Attribute if one doesn't exist
                  for (var i = 0; i < names.length; i++) {
                    if (attributes.length - 1 >= i) {
                      attributes[i].set("attributeName", names[i]);
                    } else {
                      attributes.push(
                        new EMLAttribute({
                          parentModel: this.model,
                          xmlID: DataONEObject.generateId(),
                          attributeName: names[i],
                        })
                      );
                    }
                  }

                  //Update the attribute list
                  this.model.set("attributeList", attributes);

                  // Reset first
                  this.$(".attribute-menu.side-nav-items").empty();
                  this.$(".eml-attribute").remove();

                  // Then re-render
                  this.renderAttributes();
                },

                /**
                 * Update the Fill button temporarily and set it back to the default
                 *
                 * Used to show success or failure of the filling operation
                 *
                 * @param {string} messageHTML - HTML template string to set
                 *   temporarily
                 * @param {boolean} disableTimeout - If true, the timeout will not be set
                 * @since 2.15.0
                 */
                updateFillButton: function(messageHTML, disableTimeout) {
                  var view = this;

                  this.$(".fill-button").html(messageHTML);

                  if( !disableTimeout ){
                    window.setTimeout(function () {
                      view.$(".fill-button-container").html(view.fillButtonTemplateString);
                    }, 3000);
                  }
                },

                /**
                * Disable the Fill Attributes button
                * @since 2.15.0
                */
                disableFillButton: function(){
                  this.$(".fill-button").prop("disabled", true);
                },

                /**
                * Enable the Fill Attributes button
                * @since 2.15.0
                */
                enableFillButton: function(){
                  this.$(".fill-button").prop("disabled", false);
                }
              });

            return EMLEntityView;
    });