/**
 * AJAX Upload
 * 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;
   }  
};
})();
