/**
 * 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/)
 * Version 3.5 (23.06.2009)
 */

/**
 * Changes from the previous version:
 * 1. Added better JSON handling that allows to use 'application/javascript' as a response
 * 2. Added demo for usage with jQuery UI dialog
 * 3. Fixed IE "mixed content" issue when used with secure connections
 *
 * For the full changelog please visit:
 * http://valums.com/ajax-upload-changelog/
 */

(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()) : '';
   }

   // 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,
         // 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 ajaxupload
      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
         };
         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;
            },3000);
         });

         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,'hover');
               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,'hover');
            } else {
               // mouse left the button
               over = false;
               if(!self.justClicked) {
                  input.style.display = 'none';
               }
               removeClass(self._button,'hover');
            }
         });

      },
      /**
       * 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);

            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;
      }
   };
})();
