define(
  "widget.FileInput",
  ["AbilianWidget", "jquery", "FileAPI", "Hogan"],
  function(Abilian, $, api, Hogan) {
    "use strict";
    /**
     * File input widget. Uses FileAPI (http://mailru.github.io/FileAPI/)
     */
    var defaults = {
      fileItemTemplate: Hogan.compile(
        '<div id="{{ uid }}" class="file-item">\n' +
          '<span class="file-info">{{ name }} ({{ size }})</span> ' +
          '<a class="close" href="#">&times;</a>' +
          "\n</div>"
      ),
      progressTemplate:
        '<div class="progress">' +
        '<div class="progress-bar" role="progressbar" ' +
        '     aria-valuenow="0" aria-valuemin="0" ' +
        '     aria-valuemax="100" style="width: 0;">' +
        "</div>" +
        "</div>",
      inputTemplate: '<input type="hidden" name="{{ name }}" />',
      progressBarHeight: "0.2em",
    };

    function FileInput(node, options) {
      var self = this;
      this.currentlyUploaded = {};
      this.options = $.extend({}, defaults, options);
      this.form = node.parent("form");
      this.$input = node.find(".js-fileapi-wrapper input");
      this.$input.attr("disabled", null);
      this.rootNode = node;
      this.button = node.find(".js-fileapi-wrapper .btn-file");
      this.multiple = Boolean(this.$input.attr("multiple"));
      this.$input.attr("multiple", null);
      this.inputName = this.$input.attr("name");
      this.$input.attr("name", null);
      this.listNode = node.find(".selected-files");

      if (node.data("cloned")) {
        this.listNode.empty();
      } else {
        this.listNode.find(".file-item-existing").each(function() {
          self.setupExistingFileNode($(this));
        });
        this.listNode.find(".file-item-uploaded").each(function() {
          self.setupFileNode($(this));
        });
      }

      this.button.on("change", "input", this.addFiles.bind(this));

      this.form.on("submit", this.onFormSubmit.bind(this));
    }

    FileInput.prototype = {
      addFiles: function(evt) {
        var self = this;
        var files = api.getFiles(evt);

        if (!this.multiple) {
          this.listNode.empty();
          files = files.slice(0, 1);
        }

        $(files).each(function() {
          self.addFileNode(evt.target, this);
        });
      },

      setupExistingFileNode: function(node) {
        var button = node.find("button");
        var unwrappedButton = button.get(0);
        var deleted = button.data("deleted");

        if (!unwrappedButton) {
          return;
        }

        unwrappedButton.markerInputElement = $("<input>").attr({
          type: "hidden",
          name: button.data("name"),
          value: button.data("value"),
        });

        if (deleted) {
          node.append(unwrappedButton.markerInputElement);
        }

        button.on(
          "click",
          { node: node },
          this.onExistingNodeChange.bind(this)
        );
      },

      setupFileNode: function(node) {
        node.find("a.close").on("click", this.removeFileNode.bind(this));
      },

      addFileNode: function(input, file) {
        var el = this.createFileNode(file);
        this.setupFileNode(el);
        this.listNode.append(el);
        this.triggerUpload(el, file);
      },

      triggerUpload: function(element, file) {
        var uid = api.uid(file);
        this.currentlyUploaded[uid] = true;

        api.upload({
          url: Abilian.api.upload.newFileUrl,
          headers: {
            Accept: "application/json",
            "X-CSRF-Token": Abilian.csrf_token,
          },
          files: { file: file },
          progress: this.onFileProgress.bind(this),
          complete: this.onFileComplete.bind(this),
        });
      },

      removeFileNode: function(evt) {
        evt.preventDefault();
        $(evt.target)
          .parent(".file-item")
          .remove();
      },

      createFileNode: function(file) {
        var infos = this.getFileInfos(file);
        var el = $(this.options.fileItemTemplate.render(infos));
        var progress = $(this.options.progressTemplate).css({
          height: this.options.progressBarHeight,
        });

        el.append(progress);
        return el;
      },

      getElementForFile: function(file) {
        var uid = api.uid(file);
        return $(document.getElementById(uid));
      },

      getFileInfos: function(file) {
        return {
          name: this.sanitizeFilename(file.name),
          type: file.type,
          size: this.humanSize(file.size),
          uid: api.uid(file),
        };
      },

      onExistingNodeChange: function(evt) {
        var button = $(evt.target);
        var markerInputElement = evt.target.markerInputElement;
        var isActive = button.hasClass("active");

        button.toggleClass("active");

        if (!isActive) {
          button.removeClass("btn-default");
          button.addClass("btn-danger");
          button.parent(".file-item").append(markerInputElement);
        } else {
          button.removeClass("btn-danger");
          button.addClass("btn-default");
          $(markerInputElement).remove();
        }
      },

      onFileProgress: function(evt, file, xhr, options) {
        var progress = evt.loaded / evt.total * 100;
        this.getElementForFile(file)
          .find(".progress-bar")
          .css({ width: progress + "%" });
      },

      onFileComplete: function(err, xhr, file, options) {
        var $el = this.getElementForFile(xhr.currentFile);
        var uid = api.uid(xhr.currentFile);

        delete this.currentlyUploaded[uid];

        if (err) {
          $el.remove();
          alert("Echec de l'envoi du fichier!"); // FIXME: i18n
          return;
        }

        var $input = $("<input>").attr({
          type: "hidden",
          name: this.inputName,
        });
        var responseText = xhr.responseText;
        var result = JSON.parse(responseText);

        $el.find(".progress").remove();
        $input.val(result.handle);
        $el.append($input);
      },

      onFormSubmit: function(e) {
        if (Object.keys(this.currentlyUploaded).length > 0) {
          e.preventDefault();
          alert("Des fichiers sont en cours d'envoi"); // FIXME: i18n
        }
      },

      sanitizeFilename: function(filename) {
        return filename.replace(/\\/g, "/").replace(/.*\//, "");
      },

      humanSize: function(size) {
        var unit = "b";
        var divider = null;

        if (size > api.TB) {
          unit = "TB";
          divider = api.TB;
        } else if (size > api.GB) {
          unit = "GB";
          divider = api.GB;
        } else if (size > api.MB) {
          unit = "MB";
          divider = api.MB;
        } else if (size > api.KB) {
          unit = "KB";
          divider = api.KB;
        }

        if (divider) {
          size = (size / divider).toFixed(2);
        }

        return size.toString() + unit;
      },
    };

    function createFileInput(options) {
      var element = $(this);
      var widget = new FileInput(element, options);
      element.data("file-input", widget);
      return widget;
    }

    Abilian.registerWidgetCreator("fileInput", createFileInput);

    $.fn.fileInput = function(options) {
      return this.each(function() {
        var node = $(this);
        var widget = node.data("file-input");
        if (widget === undefined) {
          widget = new FileInput(node, options);
          node.data("file-input", widget);
        }
        return widget;
      });
    };

    return FileInput;
  }
);
