/**
 * AJAX Upload 3.6 
 * Project page - http://valums.com/ajax-upload/
 * Copyright (c) 2008 Andris Valums, http://valums.com
 * Licensed under the MIT license (http://valums.com/mit-license/)
 */
(function(){
  
var d = document, w = window;

/**
 * Get element by id
 */ 
function get(element){
  if (typeof element == "string")
    element = d.getElementById(element);
  return element;
}

/**
 * Attaches event to a dom element
 */
function addEvent(el, type, fn){
  if (w.addEventListener){
    el.addEventListener(type, fn, false);
  } else if (w.attachEvent){
    var f = function(){
      fn.call(el, w.event);
    };      
    el.attachEvent('on' + type, f)
  }
}


/**
 * Creates and returns element from html chunk
 */
var toElement = function(){
  var div = d.createElement('div');
  return function(html){
    div.innerHTML = html;
    var el = div.childNodes[0];
    div.removeChild(el);
    return el;
  }
}();

function hasClass(ele,cls){
  return ele.className.match(new RegExp('(\\s|^)'+cls+'(\\s|$)'));
}
function addClass(ele,cls) {
  if (!hasClass(ele,cls)) ele.className += " "+cls;
}
function removeClass(ele,cls) {
  var reg = new RegExp('(\\s|^)'+cls+'(\\s|$)');
  ele.className=ele.className.replace(reg,' ');
}

// getOffset function copied from jQuery lib (http://jquery.com/)
if (document.documentElement["getBoundingClientRect"]){
  // Get Offset using getBoundingClientRect
  // http://ejohn.org/blog/getboundingclientrect-is-awesome/
  var getOffset = function(el){
    var box = el.getBoundingClientRect(),
    doc = el.ownerDocument,
    body = doc.body,
    docElem = doc.documentElement,
    
    // for ie 
    clientTop = docElem.clientTop || body.clientTop || 0,
    clientLeft = docElem.clientLeft || body.clientLeft || 0,
    
    // In Internet Explorer 7 getBoundingClientRect property is treated as physical,
    // while others are logical. Make all logical, like in IE8.   
    
    zoom = 1;
    
    if (body.getBoundingClientRect) {
      var bound = body.getBoundingClientRect();
      zoom = (bound.right - bound.left)/body.clientWidth;
    }
    
    if (zoom > 1){
      clientTop = 0;
      clientLeft = 0;
    }
    
    var top = box.top/zoom + (window.pageYOffset || docElem && docElem.scrollTop/zoom || body.scrollTop/zoom) - clientTop,
    left = box.left/zoom + (window.pageXOffset|| docElem && docElem.scrollLeft/zoom || body.scrollLeft/zoom) - clientLeft;
        
    return {
      top: top,
      left: left
    };
  }
  
} else {
  // Get offset adding all offsets 
  var getOffset = function(el){
    if (w.jQuery){
      return jQuery(el).offset();
    }   
      
    var top = 0, left = 0;
    do {
      top += el.offsetTop || 0;
      left += el.offsetLeft || 0;
    }
    while (el = el.offsetParent);
    
    return {
      left: left,
      top: top
    };
  }
}

function getBox(el){
  var left, right, top, bottom; 
  var offset = getOffset(el);
  left = offset.left;
  top = offset.top;
            
  right = left + el.offsetWidth;
  bottom = top + el.offsetHeight;   
    
  return {
    left: left,
    right: right,
    top: top,
    bottom: bottom
  };
}

/**
 * Crossbrowser mouse coordinates
 */
function getMouseCoords(e){   
  // pageX/Y is not supported in IE
  // http://www.quirksmode.org/dom/w3c_cssom.html     
  if (!e.pageX && e.clientX){
    // In Internet Explorer 7 some properties (mouse coordinates) are treated as physical,
    // while others are logical (offset).
    var zoom = 1; 
    var body = document.body;
    
    if (body.getBoundingClientRect) {
      var bound = body.getBoundingClientRect();
      zoom = (bound.right - bound.left)/body.clientWidth;
    }

    return {
      x: e.clientX / zoom + d.body.scrollLeft + d.documentElement.scrollLeft,
      y: e.clientY / zoom + d.body.scrollTop + d.documentElement.scrollTop
    };
  }
  
  return {
    x: e.pageX,
    y: e.pageY
  };    

}
/**
 * Function generates unique id
 */   
var getUID = function(){
  var id = 0;
  return function(){
    return 'ValumsAjaxUpload' + id++;
  }
}();

function fileFromPath(file){
  return file.replace(/.*(\/|\\)/, "");     
}

function getExt(file){
  return (/[.]/.exec(file)) ? /[^.]+$/.exec(file.toLowerCase()) : '';
}     

/**
 * Cross-browser way to get xhr object  
 */
var getXhr = function(){
  var xhr;
  
  return function(){
    if (xhr) return xhr;
        
    if (typeof XMLHttpRequest !== 'undefined') {
      xhr = new XMLHttpRequest();
    } else {
      var v = [
        "Microsoft.XmlHttp",
        "MSXML2.XmlHttp.5.0",
        "MSXML2.XmlHttp.4.0",
        "MSXML2.XmlHttp.3.0",
        "MSXML2.XmlHttp.2.0"          
      ];
      
      for (var i=0; i < v.length; i++){
        try {
          xhr = new ActiveXObject(v[i]);
          break;
        } catch (e){}
      }
    }       

    return xhr;
  }
}();

// Please use AjaxUpload , Ajax_upload will be removed in the next version
Ajax_upload = AjaxUpload = function(button, options){
  if (button.jquery){
    // jquery object was passed
    button = button[0];
  } else if (typeof button == "string" && /^#.*/.test(button)){         
    button = button.slice(1);       
  }
  button = get(button); 
  
  this._input = null;
  this._button = button;
  this._disabled = false;
  this._submitting = false;
  // Variable changes to true if the button was clicked
  // 3 seconds ago (requred to fix Safari on Mac error)
  this._justClicked = false;
  this._parentDialog = d.body;
    
  if (window.jQuery && jQuery.ui && jQuery.ui.dialog){
    var parentDialog = jQuery(this._button).parents('.ui-dialog');
    if (parentDialog.length){
      this._parentDialog = parentDialog[0];
    }
  }     
          
  this._settings = {
    // Location of the server-side upload script
    action: 'upload.php',     
    // File upload name
    name: 'userfile',
    // Additional data to send
    data: {},
    // Submit file as soon as it's selected
    autoSubmit: true,
    // The type of data that you're expecting back from the server.
    // Html and xml are detected automatically.
    // Only useful when you are using json data as a response.
    // Set to "json" in that case. 
    responseType: false,
    // Location of the server-side script that fixes Safari 
    // hanging problem returning "Connection: close" header
    closeConnection: '',
    // Class applied to button when mouse is hovered
    hoverClass: 'hover',    
    // When user selects a file, useful with autoSubmit disabled      
    onChange: function(file, extension){},          
    // Callback to fire before file is uploaded
    // You can return false to cancel upload
    onSubmit: function(file, extension){},
    // Fired when file upload is completed
    // WARNING! DO NOT USE "FALSE" STRING AS A RESPONSE!
    onComplete: function(file, response) {}
  };

  // Merge the users options with our defaults
  for (var i in options) {
    this._settings[i] = options[i];
  }
  
  this._createInput();
  this._rerouteClicks();
}
      
// assigning methods to our class
AjaxUpload.prototype = {
  setData : function(data){
    this._settings.data = data;
  },
  disable : function(){
    this._disabled = true;
  },
  enable : function(){
    this._disabled = false;
  },
  // removes instance
  destroy : function(){
    if(this._input){
      if(this._input.parentNode){
        this._input.parentNode.removeChild(this._input);
      }
      this._input = null;
    }
  },        
  /**
   * Creates invisible file input above the button 
   */
  _createInput : function(){
    var self = this;
    var input = d.createElement("input");
    input.setAttribute('type', 'file');
    input.setAttribute('name', this._settings.name);
    var styles = {
      'position' : 'absolute'
      ,'margin': '-5px 0 0 -175px'
      ,'padding': 0
      ,'width': '220px'
      ,'height': '30px'
      ,'fontSize': '14px'               
      ,'opacity': 0
      ,'cursor': 'pointer'
      ,'display' : 'none'
      ,'zIndex' :  2147483583 //Max zIndex supported by Opera 9.0-9.2x 
      // Strange, I expected 2147483647
      // Doesn't work in IE :(
      //,'direction' : 'ltr'      
    };
    for (var i in styles){
      input.style[i] = styles[i];
    }
    
    // Make sure that element opacity exists
    // (IE uses filter instead)
    if ( ! (input.style.opacity === "0")){
      input.style.filter = "alpha(opacity=0)";
    }
              
    this._parentDialog.appendChild(input);

    addEvent(input, 'change', function(){
      // get filename from input
      var file = fileFromPath(this.value);  
      if(self._settings.onChange.call(self, file, getExt(file)) == false ){
        return;       
      }                           
      // Submit form when value is changed
      if (self._settings.autoSubmit){
        self.submit();            
      }           
    });
    
    // Fixing problem with Safari
    // The problem is that if you leave input before the file select dialog opens
    // it does not upload the file.
    // As dialog opens slowly (it is a sheet dialog which takes some time to open)
    // there is some time while you can leave the button.
    // So we should not change display to none immediately
    addEvent(input, 'click', function(){
      self._justClicked = true;
      setTimeout(function(){
        // we will wait 3 seconds for dialog to open
        self._justClicked = false;
      }, 2500);     
    });   
    
    this._input = input;
  },
  _rerouteClicks : function (){
    var self = this;
  
    // IE displays 'access denied' error when using this method
    // other browsers just ignore click()
    // addEvent(this._button, 'click', function(e){
    //   self._input.click();
    // });
        
    var box, dialogOffset = {top:0, left:0}, over = false;
                  
    addEvent(self._button, 'mouseover', function(e){
      if (!self._input || over) return;
      
      over = true;
      box = getBox(self._button);
          
      if (self._parentDialog != d.body){
        dialogOffset = getOffset(self._parentDialog);
      } 
    });
    
  
    // We can't use mouseout on the button,
    // because invisible input is over it
    addEvent(document, 'mousemove', function(e){
      var input = self._input;      
      if (!input || !over) return;
      
      if (self._disabled){
        removeClass(self._button, self._settings.hoverClass);
        input.style.display = 'none';
        return;
      } 
                    
      var c = getMouseCoords(e);

      if ((c.x >= box.left) && (c.x <= box.right) && 
      (c.y >= box.top) && (c.y <= box.bottom)){
              
        input.style.top = c.y - dialogOffset.top + 'px';
        input.style.left = c.x - dialogOffset.left + 'px';
        input.style.display = 'block';
        addClass(self._button, self._settings.hoverClass);
                
      } else {    
        // mouse left the button
        over = false;
      
        var check = setInterval(function(){
          // if input was just clicked do not hide it
          // to prevent safari bug
           
          if (self._justClicked){
            return;
          }
          
          if ( !over ){
            input.style.display = 'none'; 
          }           
        
          clearInterval(check);
        
        }, 25);
          

        removeClass(self._button, self._settings.hoverClass);
      }     
    });     
      
  },
  /**
   * Creates iframe with unique name
   */
  _createIframe : function(){
    // unique name
    // We cannot use getTime, because it sometimes return
    // same value in safari :(
    var id = getUID();
    
    // Remove ie6 "This page contains both secure and nonsecure items" prompt 
    // http://tinyurl.com/77w9wh
    var iframe = toElement('<iframe src="javascript:false;" name="' + id + '" />');
    iframe.id = id;
    iframe.style.display = 'none';
    d.body.appendChild(iframe);     
    return iframe;            
  },
  /**
   * Upload file without refreshing the page
   */
  submit : function(){
    var self = this, settings = this._settings; 
          
    if (this._input.value === ''){
      // there is no file
      return;
    }
                    
    // get filename from input
    var file = fileFromPath(this._input.value);     

    // execute user event
    if (! (settings.onSubmit.call(this, file, getExt(file)) == false)) {
      // Create new iframe for this submission
      var iframe = this._createIframe();
      
      // Do not submit if user function returns false                   
      var form = this._createForm(iframe);
      form.appendChild(this._input);

      // A pretty little hack to make uploads not hang in Safari. Just call this
      // immediately before the upload is submitted. This does an Ajax call to
      // the server, which returns an empty document with the "Connection: close"
      // header, telling Safari to close the active connection.
      // http://blog.airbladesoftware.com/2007/8/17/note-to-self-prevent-uploads-hanging-in-safari
      if (settings.closeConnection && /AppleWebKit|MSIE/.test(navigator.userAgent)){
        var xhr = getXhr();
        // Open synhronous connection
        xhr.open('GET', settings.closeConnection, false);
        xhr.send('');
      }
      
      form.submit();
      
      d.body.removeChild(form);       
      form = null;
      this._input = null;
      
      // create new input
      this._createInput();
      
      var toDeleteFlag = false;
      
      addEvent(iframe, 'load', function(e){
          
        if (// For Safari
          iframe.src == "javascript:'%3Chtml%3E%3C/html%3E';" ||
          // For FF, IE
          iframe.src == "javascript:'<html></html>';"){           
          
          // First time around, do not delete.
          if( toDeleteFlag ){
            // Fix busy state in FF3
            setTimeout( function() {
              d.body.removeChild(iframe);
            }, 0);
          }
          return;
        }       
        
        var doc = iframe.contentDocument ? iframe.contentDocument : frames[iframe.id].document;

        // fixing Opera 9.26
        if (doc.readyState && doc.readyState != 'complete'){
          // Opera fires load event multiple times
          // Even when the DOM is not ready yet
          // this fix should not affect other browsers
          return;
        }
        
        // fixing Opera 9.64
        if (doc.body && doc.body.innerHTML == "false"){
          // In Opera 9.64 event was fired second time
          // when body.innerHTML changed from false 
          // to server response approx. after 1 sec
          return;       
        }
        
        var response;
                  
        if (doc.XMLDocument){
          // response is a xml document IE property
          response = doc.XMLDocument;
        } else if (doc.body){
          // response is html document or plain text
          response = doc.body.innerHTML;
          if (settings.responseType && settings.responseType.toLowerCase() == 'json'){
            // If the document was sent as 'application/javascript' or
            // 'text/javascript', then the browser wraps the text in a <pre>
            // tag and performs html encoding on the contents.  In this case,
            // we need to pull the original text content from the text node's
            // nodeValue property to retrieve the unmangled content.
            // Note that IE6 only understands text/html
            if (doc.body.firstChild && doc.body.firstChild.nodeName.toUpperCase() == 'PRE'){
              response = doc.body.firstChild.firstChild.nodeValue;
            }
            if (response) {
              response = window["eval"]("(" + response + ")");
            } else {
              response = {};
            }
          }
        } else {
          // response is a xml document
          var response = doc;
        }
                                      
        settings.onComplete.call(self, file, response);
            
        // Reload blank page, so that reloading main page
        // does not re-submit the post. Also, remember to
        // delete the frame
        toDeleteFlag = true;
        
        // Fix IE mixed content issue
        iframe.src = "javascript:'<html></html>';";                   
      });
  
    } else {
      // clear input to allow user to select same file
      // Doesn't work in IE6
      // this._input.value = '';
      d.body.removeChild(this._input);        
      this._input = null;
      
      // create new input
      this._createInput();            
    }
  },    
  /**
   * Creates form, that will be submitted to iframe
   */
  _createForm : function(iframe){
    var settings = this._settings;
    
    // method, enctype must be specified here
    // because changing this attr on the fly is not allowed in IE 6/7   
    var form = toElement('<form method="post" enctype="multipart/form-data"></form>');
    form.style.display = 'none';
    form.action = settings.action;
    form.target = iframe.name;
    d.body.appendChild(form);
    
    // Create hidden input element for each data key
    for (var prop in settings.data){
      var el = d.createElement("input");
      el.type = 'hidden';
      el.name = prop;
      el.value = settings.data[prop];
      form.appendChild(el);
    }     
    return form;
  } 
};
})();