# Source: src/js/views/ImageUploaderView.js

    define(['underscore',
            'jquery',
            'backbone',
            "models/DataONEObject",
            'collections/ObjectFormats',
            "Dropzone",
            "text!templates/imageUploader.html",
            "corejs"],
    function(_, $, Backbone, DataONEObject, ObjectFormats, Dropzone, Template, corejs){

      /**
      * @class ImageUploaderView
      * @classdesc A view that allows a person to upload an image to the repository
      * @classcategory Views
      */
      var ImageUploaderView = Backbone.View.extend(
        /** @lends ImageUploaderView.prototype */{

        /**
        * The type of View this is
        * @type {string}
        */
        type: "ImageUploader",

        /**
        * The HTML tag name to use for this view's element
        * @type {string}
        */
        tagName: "div",

        /**
        * The HTML classes to use for this view's element
        * @type {string}
        */
        className: "image-uploader",

        /**
        * The DataONEObject or PortalImage that is being edited
        * @type {DataONEObject|PortalImage}
        */
        model: undefined,

        /**
        * The URL for the image. If a DataONEObject model is provided to the view
        * instead, the url is automatically set to the output of DataONEObject.url()
        * @type {string}
        */
        url: undefined,

        /**
        * Text to instruct the user how to upload an image
        * @type {string[]}
        */
        uploadInstructions: ["Drag & drop an image or click here to upload"],

        /**
        * The maximum display height of the image preview. This is only used for the
        * css height propery, and doesn't influence the size of the saved image. If
        * set to false, no css height property is set.
        * @type {number}
        */
        height: false,

        /**
        * The display width of the image preview. This is only used for the
        * css width propery, and doesn't influence the size of the saved image. If
        * set to false, no css width property is set.
        * @type {number}
        */
        width: false,

        /**
         * The minimum required height of the image file. If set, the uploader will
         * reject images that are shorter than this. If null, any image height is
         * accepted.
         * @type {number}
         */
        minHeight: null,

        /**
         * The minimum required height of the image file. If set, the uploader will
         * reject images that are shorter than this. If null, any image height is
         * accepted.
         * @type {number}
         */
        minWidth: null,

        /**
         * The maximum height for uploaded files. If a file is taller than this, it
         * will be resized without warning before being uploaded. If set to null,
         * the image won't be resized based on height (but might be depending on
         * maxWidth).
         * @type {number}
         */
        maxHeight: null,

        /**
         * The maximum width for uploaded files. If a file is wider than this, it
         * will be resized without warning before being uploaded. If set to null,
         * the image won't be resized based on width (but might be depending on
         * maxHeight).
         * @type {number}
         */
        maxWidth: null,

        /**
         * The HTML tag name to insert the uploaded image into. Options are "img",
         * in which case the image is inserted as an HTML <img>, or "div", in which
         * case the image is inserted as the background of a div.
         * @type {string}
         */
        imageTagName: "div",

        /**
        * References to templates for this view. HTML files are converted to Underscore.js templates
        */
        template: _.template(Template),

        /**
        * The events this view will listen to and the associated function to call.
        * @type {Object}
        */
        events: {
          "mouseover .icon-remove.remove"  : "previewImageRemove",
          "mouseout  .icon-remove.remove"  : "previewImageRemove"
        },

        /**
        * Creates a new ImageUploaderView
        * @param {Object} options - A literal object with options to pass to the view
        * @property {DataONEObject}  options.model - Gets set as ImageUploaderView.model
        * @property {string[]}  options.uploadInstructions - Gets set as ImageUploaderView.uploadInstructions
        * @property {string}  options.url - Gets set as ImageUploaderView.url
        * @property {string}  options.imageTagName - Gets set as ImageUploaderView.imageTagName
        * @property {number}  options.height - Gets set as ImageUploaderView.height
        * @property {number}  options.width - Gets set as ImageUploaderView.width
        * @property {number}  options.minWidth - Gets set as ImageUploaderView.minWidth
        * @property {number}  options.minHeight - Gets set as ImageUploaderView.minHeight
        * @property {number}  options.maxWidth - Gets set as ImageUploaderView.maxWidth
        * @property {number}  options.maxHeight - Gets set as ImageUploaderView.maxHeight
        */
        initialize: function(options){

          try {
            if( typeof options == "object" ){

              this.model              = options.model;
              this.uploadInstructions = options.uploadInstructions;
              this.url                = options.url;
              this.imageTagName       = options.imageTagName;
              this.height             = options.height;
              this.width              = options.width;
              this.minHeight          = options.minHeight;
              this.minWidth           = options.minWidth;
              this.maxHeight          = options.maxHeight;
              this.maxWidth           = options.maxWidth;

              if( !this.model ){
                this.model = new DataONEObject({
                  synced: true
               });
              }

              if (!this.url && this.model) {
                this.url = this.model.url();
              }

            }

            // Ensure the object formats are cached for uploader's use
            if ( typeof MetacatUI.objectFormats === "undefined" ) {
                MetacatUI.objectFormats = new ObjectFormats();
                MetacatUI.objectFormats.fetch();
            }

            // Bug fix: Overwrite a dropzone function that causes a bug in Edge 16 &
            // 17 browser. If we update our dropzone with a fallback, this function
            // should return the fallback element.
            Dropzone.prototype.getExistingFallback = function(){
              return false
            };

            // Identify which zones should be drag & drop manually
            Dropzone.autoDiscover = false;

          } catch (e) {
            console.log("ImageUploaderView failed to initialize. Error message: " + e);
          }

        },

        /**
        * Renders this view
        */
        render: function(){

          try{
            // Reference to the view
            var view = this,
            // The overall template which holds two sub-templates
            fullTemplate = view.template({
              height: this.height,
              width: this.width,
              uploadInstructions: this.uploadInstructions,
              imageTagName: this.imageTagName
            }),
            // The outer template
            dropzoneTemplate = $(fullTemplate).find(".dropzone")[0].outerHTML,
            // The inner template inserted when an image is added
            previewTemplate = $(fullTemplate)
                                  .find(".dz-preview")[0]
                                  .outerHTML;

            // Insert the main template for this view
            view.$el.html(dropzoneTemplate);

            console.log(view.model.get("imageURL"), view.model.url())

            // Add upload & drag and drop functionality to the dropzone div.
            // For config details, see: https://www.dropzonejs.com/#configuration
            var $dropZone = view.$(".dropzone").dropzone({

              url: view.model.get("imageURL") || view.model.url(),
              acceptedFiles: "image/*",
              addRemoveLinks: false,
              maxFiles: 1,
              parallelUploads: 1,
              uploadMultiple: false,
              resizeHeight: view.maxHeight,
              resizeWidth: view.maxWidth,
              thumbnailHeight: view.maxHeight < view.height ? view.maxHeight : null,
              thumbnailWidth: view.maxWidth < view.width ? view.maxWidth : null,
              dictInvalidFileType: "This file type is not allowed. Please select an image file",
              autoProcessQueue: true,
              previewTemplate: previewTemplate,
              withCredentials: true,
              paramName: "object",
              hiddenInputContainer: this.el,

              headers: {
                  "Cache-Control": null,
                  "X-Requested-With": null,
                  "Authorization": MetacatUI.appUserModel.createAjaxSettings().headers.Authorization
              },

              // Override dropzone's function for showing images in the upload zone
              // so that we have the option to display them as a background images.
              // Check for minimum dimensions at this stage because dropzone has
              // calculated the file's height here.
              thumbnail: function(file, dataURL){
                try {
                  // Don't bother size check for SVG images since they're vector
                  var dimCheck = file.type === "image/svg+xml" ? true : view.checkMinDimensions(file.width, file.height);
                  if(dimCheck != true){
                    if(file.rejectDimensions){
                      // Send reason for rejection rejectDimensions function
                      file.rejectDimensions(dimCheck);
                    }
                  } else {
                    if(file.acceptDimensions){
                      file.acceptDimensions();
                    };
                    view.showImage(file, dataURL);
                  };
                } catch (e) {
                  console.error("Error generating thumbnail image, error message: " + e);
                }

              },

              // Dropzone will check filetype = options.acceptedFiles. Add functions
              // for when the image is too small.
              accept: function accept(file, done) {
                try {
                  file.rejectDimensions = function(message) {  done(message)  };
                  file.acceptDimensions = function(){  done()  };
                } catch (e) {
                  console.error("Error during dropzone's accept function. Error code: " + e);
                }
              },

              // After the file is accepted (correct filetype and min size requirements),
              // resize the image if it's too large in height or width, then
              // provide image data to a dataOne object model and calulate checksum.
              transformFile: function(file, done){
                try {
                  // Only resize images if dimensions are too large.
                  // Once the image is resized (or not), save the data to the model and get a checksum.
                  var resizeWidth = (file.width > this.options.resizeWidth) ? this.options.resizeWidth : null;
                  var resizeHeight = (file.height > this.options.resizeHeight) ? this.options.resizeHeight : null;
                  if (resizeHeight || resizeWidth) {
                    return this.resizeImage(file, resizeWidth, resizeHeight, this.options.resizeMethod, function(blob){
                      view.prepareD1Model(blob, file.name, file.type, done);
                    });
                  } else {
                    return view.prepareD1Model(file, file.name, file.type, done);
                  }
                } catch (e) {
                  console.error("Error during dropzone's transformFile function. Error code: " + e);
                }
              },

              // Add some required formData right before the image is uploaded
              sending: function(file, xhr, formData) {
                try {
                  //Create the system metadata XML & send as blob
                  var sysMetaXML = view.model.serializeSysMeta();
                  var xmlBlob = new Blob([sysMetaXML], {type : 'application/xml'});
                  formData.append("sysmeta", xmlBlob, "sysmeta.xml");
                  formData.append("pid", view.model.get("id"));
                } catch (e) {
                  console.error("Error during dropzone's sending function. Error code: " + e);
                }
              },

              // If there are any errors during the entire process...
              error: function error(file, message, xhr) {
                try {
                  view.trigger("error");
                  // Give a readable error if it's a server error
                  if(xhr){
                    console.error(message);
                    message = "There was an error uploading your file. Please try again later."
                  }
                  // Make sure image isn't showing (src for <img> and style for background images)
                  $(file.previewElement).find(".image-container").attr({
                    src: "",
                    style: ""
                  });
                  // Show error using dropzone's default behaviour
                  this.defaultOptions.error(file, message);
                } catch (e) {
                  console.error("Problem handling error, message: " + e);
                }
              },

              init: function() {
                try {
                  this.on("addedfile", function(file){
                    // Make sure only the most recently added image is shown in the upload zone
                    view.limitFileInput();
                    // Required for parent views to use listenTo() on dropzone events
                    view.trigger("addedfile");
                  });
                  // Hide the remove buttons and text when an image is removed
                  this.on("removedfile", function(file){
                    view.previewImageRemove();
                    // Required for parent views to use listenTo() on dropzone events
                    view.trigger("removedfile");
                  });
                  this.on("success", function(){
                    view.trigger("successSaving", view.model);
                  });
                } catch (e) {
                  console.error("Issue initializing dropzone, error message: " + e);
                }
              }

            });

            // Save the dropzone element for other functions to access later
            view.imageDropzone = $dropZone[0].dropzone;

            // Fetch the image if a URL was provided and show thumbnail
            if(view.url){
              view.showSavedImage();
            }
          }
          catch(error){
            console.error("ImageUploaderView could not be rendered, error message: ", error);
          }
        },

        /**
         * prepareD1Model - Called once an image file is resized or once it's
         * determined the the image does not need to be resized. This function adds
         * data about the image added by the user to a new DataOne model, then
         * calculates the checksum. When the checksum is finished being calculated,
         * calls the callback function (i.e. dropzone's done()).
         *
         * @param  {Blob|File} object Either the Blob or File to be saved to the server
         * @param  {string} filename the name of the file
         * @param  {string} filetype the filetype
         * @param  {function} callback a function to call once the checksum is calculated.
         */
        prepareD1Model: function(object, filename, filetype, callback){

          try{

            var modelAttributes = {
              synced: true,
              type: "image",
              fileName: filename,
              mediaType: filetype,
              size: object.size,
              uploadFile: object
            }

            // Each file upload must be a new DataONE object
            this.model = new DataONEObject(modelAttributes);
            this.model.updateID();
            this.model.set("obsoletes", null);
            this.model.get("accessPolicy").makePublic();

            // Start checksum, and call the callback function when it's complete
            this.model.stopListening(this.model, "checksumCalculated");
            this.model.listenToOnce(this.model, "checksumCalculated", function(){
                callback(object);
            });
            this.model.calculateChecksum();

          } catch (exception) {
            console.log("there was a problem calculating the checksum, exception: " + exception);
          }

        },

        /**
         * limitFileInput - Ensures only the most recently added image is shown in
         * the upload zone, as we limit each zone to 1 image but dropzone is
         * designed to accept multiple files. Called whenever a file is added to a
         * dropzone element.
         */
        limitFileInput: function(){
          if (this.imageDropzone.files[1]!=null){
            this.imageDropzone.removeFile(this.imageDropzone.files[0]);
          }
        },

        /**
         * checkMinDimensions - called from dropzone's thumbnail function before the
         * image is displayed. Checks that the image meets at least the minimum
         * height and width requirements provided to view.minHeight and
         * view.minWidth.
         *
         * @param  {number} width  the image's height.
         * @param  {number} height the image's width.
         * @return {string|boolean}  returns true if the image is at least as wide as and as tall as the given height and width. Otherwise returns an error message to display to the user.
         */
        checkMinDimensions: function(width, height){

          try{
            if(width < this.minWidth && height < this.minHeight){
              return("This image is too small. Please choose an image that's at least " + this.minWidth +"px wide and " + this.minHeight + "px tall.");
            } else if (width < this.minWidth) {
              return("This image is too narrow. Please choose an image that's at least " + this.minWidth +"px wide.")
            } else if (height < this.minHeight){
              return("This image is too short. Please choose an image that's at least " + this.minHeight +"px tall.")
            } else {
              // minimum height and width are met. If too large, then image will be resized.
              return true
            }
          } catch(error){
            console.log("Error checking the min dimensions of added file. Error message:" + error);
            // Better to show an image that's too small in this case.
            return true
          }
        },

        /**
         * showImage - General function for displaying an image file in the upload zone, whether
         * just added or already uploaded. This is the function that we use to override
         * dropzone's thumbnail() function. It displays the image as the background of
         * a div if this view's imageTagName attribute is set to "div", or as an image
         * element if imageTagName is set to "img".
         * @param  {object} file    Information about the image file
         * @param  {string} dataURL A URL for the image to be displayed
         */
        showImage: function(file, dataURL){

          try{
            // Don't show files that are the wrong size or type
            if(!this.url && !file.accepted){
              return
            };

            var previewEl = $(file.previewElement).find(".image-container")[0];

            if(this.imageTagName == "img"){
              previewEl.src = dataURL;
            } else if (this.imageTagName == "div"){
              $(previewEl).css("background-image", "url(" + dataURL + ")");
            }

          } catch(error) {
            console.log(error);
            this.showError($(file.previewElement));
          }

        },

        /**
        * Display an image in the upload zone that's already saved. This gets called
        * when an image url is provided to this view.
        */
        showSavedImage: function(){

          try{

            //If there is no URL or the model hasn't been saved yet, then don't show the image
            if( !this.url || this.model.isNew() ){
              return;
            }

            // A mock image file to identify the image provided to this view
            var imageFile = {
              url: this.url
            };

            // Add it to filelist so excess images can be removed if needed
            this.imageDropzone.files[0] = imageFile;
            // Call the default addedfile event handler
            this.imageDropzone.emit("addedfile", imageFile);
            // Show the thumbnail of the file
            this.imageDropzone.emit("thumbnail", imageFile, imageFile.url);
            // Make sure that there is no progress bar, etc...
            this.imageDropzone.emit("complete", imageFile);

          }
          catch(error){
            console.log("image could not be displayed, error message: " + error);
            // When the preview image fails to render, show some explanatory text
            this.showError($(this.imageDropzone.element));

          }

        },

        /**
         * showError - Indicates to the user that the image uploader may not work
         * due to browser issues.
         * @param {jQuery} dropzoneEl - The dropzone element to show the error for.
         */
        showError: function(dropzoneEl){
          dropzoneEl.addClass("error");
          dropzoneEl.find(".dz-error-message span").text("Error previewing image");
          dropzoneEl.tooltip({
            placement: "bottom",
            trigger: "hover",
            title: "Image previews cannot be shown. Your browser may be out-of-date."
          });
        },

        /**
         * previewImageRemove - When the user hovers over the remove button,
         * indicates to the user that the button will remove the image by 1) changing
         * the upload instruction text to a message about removing the image,
         * and 2) adding a warning class to the message div.
         */
        previewImageRemove: function(e){

          try {

            if(e){
              this.$el.toggleClass("remove-preview");
            } else {
              this.$el.removeClass("remove-preview");
            }

          } catch (error) {
            console.log(error);
          }
        }

      });

      return ImageUploaderView;

    });