/**
 * @requires javelin-install
 *           javelin-util
 *           javelin-request
 *           javelin-dom
 *           javelin-uri
 *           phabricator-file-upload
 * @provides phabricator-drag-and-drop-file-upload
 * @javelin
 */

JX.install('PhabricatorDragAndDropFileUpload', {

  construct : function(node) {
    this._node = node;
  },

  events : [
    'didBeginDrag',
    'didEndDrag',
    'willUpload',
    'progress',
    'didUpload',
    'didError'],

  statics : {
    isSupported : function() {
      // TODO: Is there a better capability test for this? This seems okay in
      // Safari, Firefox and Chrome.

      return !!window.FileList;
    },
    isPasteSupported : function() {
      // TODO: Needs to check if event.clipboardData is available.
      // Works in Chrome, doesn't work in Firefox 10.
      return !!window.FileList;
    }
  },

  members : {
    _node : null,
    _depth : 0,
    _updateDepth : function(delta) {
      if (this._depth === 0 && delta > 0) {
        this.invoke('didBeginDrag');
      }

      this._depth += delta;

      if (this._depth === 0 && delta < 0) {
        this.invoke('didEndDrag');
      }
    },

    start : function() {

      // TODO: move this to JX.DOM.contains()?
      function contains(container, child) {
        do {
          if (child === container) {
            return true;
          }
          child = child.parentNode;
        } while (child);

        return false;
      }

      // We track depth so that the _node may have children inside of it and
      // not become unselected when they are dragged over.
      JX.DOM.listen(
        this._node,
        'dragenter',
        null,
        JX.bind(this, function(e) {
          if (contains(this._node, e.getTarget())) {
            this._updateDepth(1);
          }
        }));

      JX.DOM.listen(
        this._node,
        'dragleave',
        null,
        JX.bind(this, function(e) {
          if (contains(this._node, e.getTarget())) {
            this._updateDepth(-1);
          }
        }));

      JX.DOM.listen(
        this._node,
        'dragover',
        null,
        function(e) {
          // NOTE: We must set this, or Chrome refuses to drop files from the
          // download shelf.
          e.getRawEvent().dataTransfer.dropEffect = 'copy';
          e.kill();
        });

      JX.DOM.listen(
        this._node,
        'drop',
        null,
        JX.bind(this, function(e) {
          e.kill();

          var files = e.getRawEvent().dataTransfer.files;
          for (var ii = 0; ii < files.length; ii++) {
            this._sendRequest(files[ii]);
          }

          // Force depth to 0.
          this._updateDepth(-this._depth);
        }));

      if (JX.PhabricatorDragAndDropFileUpload.isPasteSupported()) {
        JX.DOM.listen(
          this._node,
          'paste',
          null,
          JX.bind(this, function(e) {
            var clipboardData = e.getRawEvent().clipboardData;
            if (!clipboardData) {
              return;
            }

            for (var ii = 0; ii < clipboardData.items.length; ii++) {
              var item = clipboardData.items[ii];
              if (!/^image\//.test(item.type)) {
                continue;
              }
              this._sendRequest(item.getAsFile());
            }
          }));
      }
    },
    _sendRequest : function(spec) {
      var file = new JX.PhabricatorFileUpload()
        .setName(spec.name)
        .setTotalBytes(spec.size)
        .setStatus('uploading')
        .update();

      this.invoke('willUpload', file);

      var up_uri = JX.$U(this.getURI())
        .setQueryParam('name', file.getName())
        .setQueryParam('__upload__', 1)
        .toString();

      var onupload = JX.bind(this, function(r) {
        if (r.error) {
          file
            .setStatus('error')
            .setError(r.error)
            .update();

          this.invoke('didError', file);
        } else {
          file
            .setID(r.id)
            .setPHID(r.phid)
            .setURI(r.uri)
            .setMarkup(r.html)
            .setStatus('done')
            .update();

          this.invoke('didUpload', file);
        }
      });

      var req = new JX.Request(up_uri, onupload);

      var onerror = JX.bind(this, function(error) {
        file.setStatus('error');

        if (error) {
          file.setError(error.code + ': ' + error.info);
        } else {
          var xhr = req.getTransport();
          if (xhr.responseText) {
            file.setError('Server responded: ' + xhr.responseText);
          }
        }

        file.update();
        this.invoke('didError', file);
      });

      var onprogress = JX.bind(this, function(progress) {
        file
          .setTotalBytes(progress.total)
          .setUploadedBytes(progress.loaded)
          .update();

        this.invoke('progress', file);
      });

      req.listen('error', onerror);
      req.listen('uploadprogress', onprogress);

      req
        .setRawData(spec)
        .send();
    }
  },
  properties: {
    URI : null,
    activatedClass : null
  }
});
