/**
 * This script attempts to provide a clean interface for performing/handling
 * AJAX requests.
 *
 * Here is a basic work flow:
 *
 * request = ajax.createRequest('/path/to/script');
 * request.setRequestType(ajax.POST); // Default is ajax.GET
 * // Parameters can also be set as strings: 'username=uname&password=123abc'
 * request.setParameters({username: 'uname', password: '123abc'});
 * request.setResponseType(ajax.JSON); // Data to handler will be pre-evaled
 * request.setResponseHandler(function(responseData) { alert(responseData); });
 * request.send(); // Could also use ajax.sendRequest(request), your call
 *
 * When using the ajax.JSON response type make sure you trust the souce of the
 * response since the response is passed through eval();
 *
 * @author Philip Graham
 */
var ajax = function() {

    /*
     * The maximum number of concurrent requests.
     */
    var maxConnections = 2;

    /*
     * Array of connections. Contains maxConnections elements
     * Each connection is an object with two properties:
     *   isReady - Indicates whether or not the connection is ready to send a
     *             request
     *   http    - An XMLHttpRequest object that does the dirty work
     */
    var connectionPool = buildRequestConnectionPool();
    var sending = false;

    /*
     * Request queue.
     */
    var requestQueue = new Array();


    var uploading = false;
    var uploadingObjectId=''; //null;
    var uploadingStatusDivId=''; //null;
    var uploadingFileName='';
    var uploadingFileSize=0;
    var uploadingFileContents='';
    var uploadingChunkNumber=0;
    var uploadingChunkTotal=0;
    var uploadingChunkSize=0;
    var uploadingFilePath='';
    var uploadingFileSecret=0;

    var uploadingFunctionFinished=null;
    var uploadingFunctionError=null;

    /*========================================================================*
     * Class Public Methods/Members
     *========================================================================*/
    return {
        /**
         * Request types.
         */
        GET: 'GET',
        POST: 'POST',

        /**
         * Return types for requests.
         */
        RAW: 0, // Response text will be passed to callback as is
        XML: 1, // XMLHttpRequest.responseXML will be passed to callback
        JSON: 2, // Response text will be evaled then passed to callback
        
        /**
         * Creates a new request.  See the AjaxRequest object API below.
         */
        createRequest: function(url) {
            return new AjaxRequest(url);
        },

        /**
         * Adds the given request to the request queue.
         */
        sendRequest: function(request) {
            if(sending) {
                setTimeout(function () {
                    sendRequest()
                }, 250);
                return;
            }
            sending = true;
            // Add the request to the end of the queue
            requestQueue.push(request);

            // Try and send it right away
            sendNextRequest();
            sending = false;
        },

        doAsyncUpload: function (uploadobjid,statusdivid,path,fnfinish,fnerror,formid) {
            uploadobj=document.getElementById(uploadobjid);

            uploadingObject=uploadobj;
            uploadingStatusDivId=statusdivid;
            uploadingObjectId=uploadobjid;

            uploadingFunctionFinished=fnfinish;
            uploadingFunctionError=fnerror;

			uploadingStatusObj=document.getElementById(uploadingStatusDivId);

			//legacy upload using a hidden iframe as the form target
			//if(uploadobj.files==null) {
			//lets always force this one.. i guess...for now...
			if(true) {
				uploading=true;
			//	var formobj=document.getElementById('editor');
				var formobj=$('#'+formid);
				formobj.attr("action","async.new.upload_handler.php?legacy=true&path="+path);
				formobj.attr("method","post");
				formobj.attr("target","upload_target");
				$('#'+statusdivid).html("<span style=\"font-size: 1.5em; font-weight: bold;\"><img src=\"/images/ajax-loader.gif\" align=\"left\" width=\"32\" style=\"margin-left: 10px; margin-right: 10px; \">Uploading...<br />Please wait</span>");
//				$('#'+uploadingObjectId).attr("disabled","disabled");
				$('#'+uploadingObjectId).hide();

				$("#upload_target").load(function() { 

					if(uploadingStatusObj) {
						var ret = frames['upload_target'].document.getElementsByTagName("body")[0].innerHTML;
//						var ret = document.getElementById['upload_target'];
//						alert(ret); //.innerHTML;
//						alert(ret);
						var data=eval("("+ret+")");
//						alert(data);

						if(data.status=='finished') {
							uploadingStatusObj.innerHTML='Upload Complete! ('+data.filename+')'; //+' '+error;
						}
						else if(data.status=='error') {
							uploadingStatusObj.innerHTML='Upload Failed: '+data.error;
						}
						else {
							uploadingStatusObj.innerHTML='Unknown upload status';
						}
						$('#'+uploadingObjectId).show();

						//uploadingObject.style.display='';
						//uploadingObject.style.visibility='visible';
					}
					uploading=false;
					uploadingFunctionFinished(uploadingFileName);

				});
				formobj.submit();
				return true;
			}

            if(uploading) {
                setTimeout(function () {
                    doAsyncUpload()
                }, 250);
                return;
            }

            uploading = true;
            uploadobj.style.display='none';

            var chunkSize=20*1024; //20KB
            var minChunks=3;

            uploadingFileSize=uploadobj.files.item(0).fileSize;
            uploadingFileName=uploadobj.files.item(0).fileName;
           
            uploadingFileContents=uploadobj.files.item(0).getAsBinary();

            var chunks=Math.ceil(uploadingFileSize/chunkSize);
            if(chunks<minChunks) {
                chunks=minChunks;
                chunkSize=Math.ceil(uploadingFileSize/minChunks);
            }
//            console.log('path: '+path+' -- uploading '+chunks+' chunks of size '+chunkSize);

            uploadingChunkNumber=1;
            uploadingChunkTotal=chunks;
            uploadingChunkSize=chunkSize;

            var ar=new AjaxRequest("async.new.upload_handler.php");
            ar.setRequestType(ajax.POST);
            ar.setResponseType(ajax.JSON);
            ar.setResponseHandler(handleAsyncUploadStart);
            ar.setParameters({action:'start',path:path,chunks:chunks,chunksize:chunkSize,filesize:uploadingFileSize,filename:uploadingFileName});
            ar.send();
        }

    };

    function handleAsyncUploadStart(response) {
//        console.log(response);
        if(response.status=='ok') {
            uploadingFilePath=response.filepath;
            uploadingFileSecret=response.secret;
            AsyncUploadChunk();
        }
        else if(response.status=='error') {
            uploadingStatusObj=document.getElementById(uploadingStatusDivId);
            //only show the status if the status element is valid.  we could be on a differen tab where the status doesnt exist anymore
            if(uploadingStatusObj) {
                uploadingStatusObj.style.display='';
                uploadingStatusObj.style.visibility='visible';
                uploadingStatusObj.innerHTML=response.error;
                uploadingFunctionError();
            }
            
            uploading=false;
        }

    }

    function AsyncUploadChunk() {
            var chunkStart=(uploadingChunkNumber-1)*uploadingChunkSize;
            var chunkEnd=chunkStart+uploadingChunkSize;

            //dont read past the end of the file, duh
            if(chunkEnd>uploadingFileContents.length)
                chunkEnd=uploadingFileContents.length;

            var chunkdata=Base64.encode(uploadingFileContents.substring(chunkStart,chunkEnd));

//            console.log('prepared chunk '+uploadingChunkNumber+' ('+chunkStart+' to '+chunkEnd+') after escape: '+chunkdata.length+' bytes'); //:'+chunkdata);

            var ar=new AjaxRequest("async.new.upload_handler.php");
            ar.setRequestType(ajax.POST);
            ar.setResponseType(ajax.JSON);
            ar.setResponseHandler(handleAsyncUploadChunk);

            ar.setParameters({action:'chunk',filepath:uploadingFilePath,secret:uploadingFileSecret,chunk:uploadingChunkNumber,chunkdata:chunkdata});
            ar.send();
    }

    function handleAsyncUploadChunk(response) {
        uploadingStatusObj=document.getElementById(uploadingStatusDivId);
        uploadingObject=document.getElementById(uploadingObjectId);
        if(response.status=="ok") {
//            console.log("got ack for chunk "+response.okchunk);
            var percent=Math.round(response.okchunk/uploadingChunkTotal*100);
            var byteCount=uploadingChunkSize*response.okchunk;
            //we have to re-get it each time incase you navigate off and it doesnt exist anymore and then come back
            //only show the status if the status element is valid.  we could be on a differen tab where the status doesnt exist anymore
            if(uploadingStatusObj) {
                uploadingStatusObj.style.display='';
                uploadingStatusObj.style.visibility='visible';
                uploadingStatusObj.innerHTML=percent+'% '+uploadingFileName+' ('+byteCount+' of '+uploadingFileSize+' bytes)';
                uploadingObject.style.display='none';
                uploadingObject.style.visibility='hidden';
            }
            uploadingChunkNumber=response.nextchunk;
            AsyncUploadChunk();
        }
        else if(response.status=="finished") {
            if(uploadingStatusObj) {
                uploadingStatusObj.innerHTML='(Complete) '+uploadingFileName;
                uploadingObject.style.display='';
                uploadingObject.style.visibility='visible';
            }
            uploading=false;
            uploadingFunctionFinished(uploadingFileName);
        }
    }

    /*========================================================================*
     * Request Queue and XMLHttpRequest object pool
     *========================================================================*/

    /*
     * Sends the next request in the queue.
     */
    function sendNextRequest() {
        // See if there are any requests in the queue
        var next = requestQueue.shift();
        if(!next) {
            return;
        }

        // Check for an inactive connection
        var conn = null;
        for(var i = 0; i < maxConnections; i++) {
            if(connectionPool[i].isReady) {
                conn = connectionPool[i];
                conn.isReady = false;
                break;
            }
        }

        if(!conn) {
            // All connections are busy so we need to wait
            // Add the request back onto the front of the queue
            requestQueue.unshift(next);
            return;
        }

        // Open the connection
        conn.http.open(next.getRequestType(), next.getUrl(), true);

        // Set the response handler
        var responseCallback = next.getResponseHandler();
        var responseType = next.getResponseType();
        conn.http.onreadystatechange =
            buildResponseHandler(conn, responseType, responseCallback);

        // Prepare POST request
        // @todo - Abstract this functionality into its own function and then
        // use it to implement setParameters functionality in the
        // request object for GET requests
        if(next.getRequestType() == ajax.POST) {
            var params = next.getParameters();
            if('object' == typeof params) {
                var data = null;
                for(var prop in params) {
                    if('object' == typeof(params[prop]) &&
                        params[prop] !== null) {

                        for(var i = 0; i < params[prop].length; i++) {
                            data = data ? data + '&' : '';
                            data = data + prop + '=' + params[prop][i];
                        }
                    } else {
                        data = data ? data + '&' : '';
                        data = data + prop + '=' + params[prop];
                    }
                }
                params = data;
            }
            conn.http.setRequestHeader('Content-type',
                'application/x-www-form-urlencoded');
            conn.http.setRequestHeader('Content-length', params.length);
            conn.http.setRequestHeader('Connection', 'close');
            var additionalHeaders = next.getHeaders();
            for(var header in additionalHeaders) {
                conn.http.setRequestHeader(header, additionalHeaders[header]);
            }

            // Send the request
            conn.http.send(params);
        } else {
            conn.http.send(null);
        }
    }

    function buildResponseHandler(theConn, type, callback) {
        var conn = theConn;
        var responseType = type;
        var responseCallback = callback;

        return function () {
            if(conn.http.readyState == 4) {
                if(conn.http.status == 200) {
                    // Make sure the user was authenticated
                    if(conn.http.responseText.substr(0, 10) == 'f:authent:') {
                        alert('Authentication Failure: ' +
                            conn.http.responseText.substr(10)
                        );
                        window.location.href = '/login/';
                        return;
                    } else if (conn.http.responseText.substr(0, 2) == 'f:') {
                        alert(conn.http.responseText.substr(2));
                        window.location.href = 'home.php';
                        return;
                    }

                    // Parse the data to pass to the callback function
                    // depending on the type set
                    var responseData = null;
                    if(responseType == ajax.JSON) {
                        try {
                            responseData = eval('(' + conn.http.responseText +
                                ')');
                        } catch(e) {
                            console.log('Bad JSON' + conn.http.responseText);
                            responseData = null;
                        }
                  //      SITE.clearMessages();
                        if(responseData && responseData.error) {
                  //          SITE.addMessage(responseData.error, 'error');
                        }
                        if(responseData && responseData.debug) {
                            console.log('Debug\n' + responseData.debug);
                        }
                    } else if(responseType == ajax.XML) {
                        responseData = conn.http.responseXML;
                    } else {
                        responseData = conn.http.responseText;
                    }

                    // Call the response handler
                    if(responseData) {
                        responseCallback(responseData);
                    }
                } else {
                    alert('Request failed: ' + conn.http.statusText);
                }

                // Reset the connection and send the next request in the queue
                conn.isReady = true;
                setTimeout(function () {
                    sendNextRequest();
                }, 100);
            }
        };
    }




    /*========================================================================*
     * INSTANCE
     * --------
     * This is the request object returned by the createRequest() static
     * method.  It has it's own set of private/public functions/properties.
     * The difference being that each call to ajax.createRequest creates a new
     * 'object' with it's own set of properties.  The class implements a
     * minimal set of functions and is mostly a glorified propery holder.
     *========================================================================*/
    function AjaxRequest(theUrl) {
        var url = theUrl;
        var requestType = ajax.GET;
        var parameters = null;
        var responseType = ajax.RAW;
        var responseHandler = null;
        var addtnlHeaders = {};

        return {
            /**
             * Getter for the target URL of the request, only settable through
             * the constructor.
             */
            getUrl: function() {
                return url;
            },

            /**
             * Setter for the type of request, can be either ajax.GET or
             * ajax.POST
             */
            setRequestType: function(type) {
                requestType = type;
            },

            /**
             * Getter for the request type
             */
            getRequestType: function () {
                return requestType;
            },

            /**
             * Setter for additional headers to send with the request.
             */
            setHeader: function (header, value) {
                addtnlHeaders[header] = value;
            },

            /**
             * Getter for any additional header to send with the request
             */
            getHeaders: function () {
                return addtnlHeaders;
            },

            /**
             * Setter for the requests parameters.  Only relevant to POST
             * requests.
             *
             * @param params - Can be either the encoded string or an object
             * containing a mapping of name value pairs to POST with the
             * request.  The object properties can be Arrays but in order to be
             * properly encoded the property name of an array needs to end with
             * '[]'.
             *
             * Example:
             * var params = {
             *     input1: val1,
             *     input2: val2,
             *     arr[]: [ arrVal1, arrVal2, arrVal3 ]
             * }
             */
            setParameters: function(params) {
                // @todo - If the request is GET manipulate the URL
                parameters = params;
            },

            /**
             * Getter for the requests parameters.
             */
            getParameters: function() {
                return parameters;
            },

            /**
             * Setter for how the response data should be handled.
             * Possible values are:
             *   ajax.RAW - No manipulation is done to response text
             *   ajax.JSON - Response text is eval-ed automatically
             *   ajax.XML - Response XML is passed to callback
             */
            setResponseType: function(type) {
                responseType = type;
            },

            /**
             * Getter for the response type.
             */
            getResponseType: function() {
                return responseType;
            },

            /**
             * Setter for the function that will handle a successful request.
             */
            setResponseHandler: function(fn) {
                responseHandler = fn;
            },

            /**
             * Getter for the response handler callback function.
             */
            getResponseHandler: function() {
                return responseHandler;
            },

            /**
             * Sends the request.
             */
            send: function() {
                ajax.sendRequest(this);
            }
        };
    }

    /*========================================================================*
     * Initialization -- These functions are used to initialize the singleton
     *========================================================================*/

    /*
     * Builds a pool AjaxConnection objects.
     */
    function buildRequestConnectionPool() {
        var pool = new Array();
        for(var i = 0; i < maxConnections; i++) {
            var conn = getXMLHttpRequest();
            pool[i] = {
                id: i,
                isReady: true,
                http: conn
            };
        }
        return pool;
    }
    
    /*
     * Constructor for an XMLHttpRequest object.
     */
    function getXMLHttpRequest() {
     //   if(window.XDomainRequest) { // IE8
      //      return new XDomainRequest();
        //} else if(window.ActiveXObject) { // IE <=6
        if(window.ActiveXObject) { // IE <=6
            var activeXModes = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP'];
            for(var i = 0; i < activeXModes.length; i++) {
                try {
                    return new ActiveXObject(activeXModes[i]);
                } catch(e) {
                    //swallow
                }
            }
        } else if(window.XMLHttpRequest) { // IE7, Firefox, Safari
            return new XMLHttpRequest();
        } else { // No Ajax support
            return false;
        }
    }

    
}();
