(function (Flow, window, document, undefined) {
  'use strict';

  var extend = Flow.extend;
  var each = Flow.each;

  function addEvent(element, type, handler) {
    if (element.addEventListener) {
      element.addEventListener(type, handler, false);
    } else if (element.attachEvent) {
      element.attachEvent("on" + type, handler);
    } else {
      element["on" + type] = handler;
    }
  }

  function removeEvent(element, type, handler) {
    if (element.removeEventListener) {
      element.removeEventListener(type, handler, false);
    } else if (element.detachEvent) {
      element.detachEvent("on" + type, handler);
    } else {
      element["on" + type] = null;
    }
  }

  function removeElement(element) {
    element.parentNode.removeChild(element);
  }

  function isFunction(functionToCheck) {
    var getType = {};
    return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
  }

  /**
   * Not resumable file upload library, for IE7-IE9 browsers
   * @name FustyFlow
   * @param [opts]
   * @param {bool} [opts.singleFile]
   * @param {string} [opts.fileParameterName]
   * @param {Object|Function} [opts.query]
   * @param {Object} [opts.headers]
   * @param {string} [opts.target]
   * @param {Function} [opts.generateUniqueIdentifier]
   * @param {bool} [opts.matchJSON]
   * @constructor
   */
  function FustyFlow(opts) {
    // Shortcut of "r instanceof Flow"
    this.support = false;

    this.files = [];
    this.events = [];
    this.defaults = {
      simultaneousUploads: 3,
      fileParameterName: 'file',
      query: {},
      target: '/',
      generateUniqueIdentifier: null,
      matchJSON: false
    };

    var $ = this;

    this.inputChangeEvent = function (event) {
      var input = event.target || event.srcElement;
      removeEvent(input, 'change', $.inputChangeEvent);
      var newClone = input.cloneNode(false);
      // change current input with new one
      input.parentNode.replaceChild(newClone, input);
      // old input will be attached to hidden form
      $.addFile(input, event);
      // reset new input
      newClone.value = '';
      addEvent(newClone, 'change', $.inputChangeEvent);
    };

    this.opts = Flow.extend({}, this.defaults, opts || {});
  }

  FustyFlow.prototype = {
    on: Flow.prototype.on,
    off: Flow.prototype.off,
    fire: Flow.prototype.fire,
    cancel: Flow.prototype.cancel,
    assignBrowse: function (domNodes) {
      if (typeof domNodes.length == 'undefined') {
        domNodes = [domNodes];
      }
      each(domNodes, function (domNode) {
        var input;
        if (domNode.tagName === 'INPUT' && domNode.type === 'file') {
          input = domNode;
        } else {
          input = document.createElement('input');
          input.setAttribute('type', 'file');

          extend(domNode.style, {
            display: 'inline-block',
            position: 'relative',
            overflow: 'hidden',
            verticalAlign: 'top'
          });

          extend(input.style, {
            position: 'absolute',
            top: 0,
            right: 0,
            fontFamily: 'Arial',
            // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
            fontSize: '118px',
            margin: 0,
            padding: 0,
            opacity: 0,
            filter: 'alpha(opacity=0)',
            cursor: 'pointer'
          });

          domNode.appendChild(input);
        }
        // When new files are added, simply append them to the overall list
        addEvent(input, 'change', this.inputChangeEvent);
      }, this);
    },
    assignDrop: function () {
      // not supported
    },
    unAssignDrop: function () {
      // not supported
    },
    isUploading: function () {
      var uploading = false;
      each(this.files, function (file) {
        if (file.isUploading()) {
          uploading = true;
          return false;
        }
      });
      return uploading;
    },
    upload: function () {
      // Kick off the queue
      var files = 0;
      each(this.files, function (file) {
        if (file.progress() == 1 || file.isPaused()) {
          return;
        }
        if (file.isUploading()) {
          files++;
          return;
        }
        if (files++ >= this.opts.simultaneousUploads) {
          return false;
        }
        if (files == 1) {
          this.fire('uploadStart');
        }
        file.send();
      }, this);
      if (!files) {
        this.fire('complete');
      }
    },
    pause: function () {
      each(this.files, function (file) {
        file.pause();
      });
    },
    resume: function () {
      each(this.files, function (file) {
        file.resume();
      });
    },
    progress: function () {
      var totalDone = 0;
      var totalFiles = 0;
      each(this.files, function (file) {
        totalDone += file.progress();
        totalFiles++;
      });
      return totalFiles > 0 ? totalDone / totalFiles : 0;
    },
    addFiles: function (elementsList, event) {
      var files = [];
      each(elementsList, function (element) {
        // is domElement ?
        if (element.nodeType === 1 && element.value) {
          var f = new FustyFlowFile(this, element);
          if (this.fire('fileAdded', f, event)) {
            files.push(f);
          }
        }
      }, this);
      if (this.fire('filesAdded', files, event)) {
        each(files, function (file) {
          if (this.opts.singleFile && this.files.length > 0) {
            this.removeFile(this.files[0]);
          }
          this.files.push(file);
        }, this);
      }
      this.fire('filesSubmitted', files, event);
    },
    addFile: function (file, event) {
      this.addFiles([file], event);
    },
    generateUniqueIdentifier: function (element) {
      var custom = this.opts.generateUniqueIdentifier;
      if (typeof custom === 'function') {
        return custom(element);
      }
      return 'xxxxxxxx-xxxx-yxxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
        var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
        return v.toString(16);
      });
    },
    getFromUniqueIdentifier: function (uniqueIdentifier) {
      var ret = false;
      each(this.files, function (f) {
        if (f.uniqueIdentifier == uniqueIdentifier) ret = f;
      });
      return ret;
    },
    removeFile: function (file) {
      for (var i = this.files.length - 1; i >= 0; i--) {
        if (this.files[i] === file) {
          this.files.splice(i, 1);
        }
      }
    },
    getSize: function () {
      // undefined
    },
    timeRemaining: function () {
      // undefined
    },
    sizeUploaded: function () {
      // undefined
    }
  };

  function FustyFlowFile(flowObj, element) {
    this.flowObj = flowObj;
    this.element = element;
    this.name = element.value && element.value.replace(/.*(\/|\\)/, "");
    this.relativePath = this.name;
    this.uniqueIdentifier = flowObj.generateUniqueIdentifier(element);
    this.iFrame = null;

    this.finished = false;
    this.error = false;
    this.paused = false;

    var $ = this;
    this.iFrameLoaded = function (event) {
      // when we remove iframe from dom
      // the request stops, but in IE load
      // event fires
      if (!$.iFrame || !$.iFrame.parentNode) {
        return;
      }
      $.finished = true;
      try {
        // fixing Opera 10.53
        if ($.iFrame.contentDocument &&
          $.iFrame.contentDocument.body &&
          $.iFrame.contentDocument.body.innerHTML == "false") {
          // In Opera event is fired second time
          // when body.innerHTML changed from false
          // to server response approx. after 1 sec
          // when we upload file with iframe
          return;
        }
      } catch (error) {
        //IE may throw an "access is denied" error when attempting to access contentDocument
        $.error = true;
        $.abort();
        $.flowObj.fire('fileError', $, error);
        return;
      }
      // iframe.contentWindow.document - for IE<7
      var doc = $.iFrame.contentDocument || $.iFrame.contentWindow.document;
      var innerHtml = doc.body.innerHTML;
      if ($.flowObj.opts.matchJSON) {
        innerHtml = /(\{.*\})/.exec(innerHtml)[0];
      }

      $.abort();
      $.flowObj.fire('fileSuccess', $, innerHtml);
      $.flowObj.upload();
    };
    this.bootstrap();
  }

  FustyFlowFile.prototype = {
    getExtension: Flow.FlowFile.prototype.getExtension,
    getType: function () {
      // undefined
    },
    send: function () {
      if (this.finished) {
        return;
      }
      var o = this.flowObj.opts;
      var form = this.createForm();
      var params = o.query;
      if (isFunction(params)) {
        params = params(this);
      }
      params[o.fileParameterName] = this.element;
      params['flowFilename'] = this.name;
      params['flowRelativePath'] = this.relativePath;
      params['flowIdentifier'] = this.uniqueIdentifier;

      this.addFormParams(form, params);
      addEvent(this.iFrame, 'load', this.iFrameLoaded);
      form.submit();
      removeElement(form);
    },
    abort: function (noupload) {
      if (this.iFrame) {
        this.iFrame.setAttribute('src', 'java' + String.fromCharCode(115) + 'cript:false;');
        removeElement(this.iFrame);
        this.iFrame = null;
        !noupload && this.flowObj.upload();
      }
    },
    cancel: function () {
      this.flowObj.removeFile(this);
      this.abort();
    },
    retry: function () {
      this.bootstrap();
      this.flowObj.upload();
    },
    bootstrap: function () {
      this.abort(true);
      this.finished = false;
      this.error = false;
    },
    timeRemaining: function () {
      // undefined
    },
    sizeUploaded: function () {
      // undefined
    },
    resume: function () {
      this.paused = false;
      this.flowObj.upload();
    },
    pause: function () {
      this.paused = true;
      this.abort();
    },
    isUploading: function () {
      return this.iFrame !== null;
    },
    isPaused: function () {
      return this.paused;
    },
    isComplete: function () {
      return this.progress() === 1;
    },
    progress: function () {
      if (this.error) {
        return 1;
      }
      return this.finished ? 1 : 0;
    },

    createIframe: function () {
      var iFrame = (/MSIE (6|7|8)/).test(navigator.userAgent) ?
        document.createElement('<iframe name="' + this.uniqueIdentifier + '_iframe' + '">') :
        document.createElement('iframe');

      iFrame.setAttribute('id', this.uniqueIdentifier + '_iframe_id');
      iFrame.setAttribute('name', this.uniqueIdentifier + '_iframe');
      iFrame.style.display = 'none';
      document.body.appendChild(iFrame);
      return iFrame;
    },
    createForm: function() {
      var target = this.flowObj.opts.target;
      if (typeof target === "function") {
        target = target.apply(null);
      }

      var form = document.createElement('form');
      form.encoding = "multipart/form-data";
      form.method = "POST";
      form.setAttribute('action', target);
      if (!this.iFrame) {
        this.iFrame = this.createIframe();
      }
      form.setAttribute('target', this.iFrame.name);
      form.style.display = 'none';
      document.body.appendChild(form);
      return form;
    },
    addFormParams: function(form, params) {
      var input;
      each(params, function (value, key) {
        if (value && value.nodeType === 1) {
          input = value;
        } else {
          input = document.createElement('input');
          input.setAttribute('value', value);
        }
        input.setAttribute('name', key);
        form.appendChild(input);
      });
    }
  };

  FustyFlow.FustyFlowFile = FustyFlowFile;

  if (typeof module !== 'undefined') {
    module.exports = FustyFlow;
  } else if (typeof define === "function" && define.amd) {
    // AMD/requirejs: Define the module
    define(function(){
      return FustyFlow;
    });
  } else {
    window.FustyFlow = FustyFlow;
  }
})(window.Flow, window, document);

