if (typeof(RequestBatch) === 'undefined') {
    throw new Error('requestbatch.js must be included before this file');
}

RequestBatch.debugging = true;    
(function() {
    
    // if the browser doesn't have a way to log anything then
    // fake it so we don't cause errors
    if (typeof(console) === 'undefined') {
        console = {
            'log': function() { }
        }
    }
    var debug = function(message) {
		if (typeof(RequestBatch.debugging) && RequestBatch.debugging) {
            console.log(message);
		}
    }
    
    // have we gotten the signal from the tunnel that it's ready?
    var isFlashReady = false;
    // the reference to the proxy itself
    var flashProxy = null;
    // the requests that have yet to be sent through the proxy
    var pendingRequests = [];
    // the callbacks that the proxy will invoke upon successful communication with
    // the server. keyed off an 'asyncToken' that is generated at the time the callback
    // is registered.
    var callbackDictionary = {};
    
    var processQueue = function() {
        debug('processQueue')
        if (isFlashReady) {
            while (pendingRequests.length > 0) {
                var func = pendingRequests.pop();
                func();
                // make sure flash wasn't unloaded somehow during call
                if (typeof(flashProxy.CallDAPI) === 'undefined') {
                    // schedule the function to be called again
                    pendingRequests.push(func);
                    debug('we lost flash for some reason!');
                    break;
                }
            }
        }
        else {
            debug('flash not ready yet, aborting');
        }
    }
    
    var generateUniqueId = function() {
        var seed = new Date().getTime();
        return Math.floor(Math.random() * seed) + '_' + Math.floor(Math.random() * seed);
    }
    
    var registerCallback = function(callback) {
        // store the callback in a dictionary linked to an async token because
        // the flash layer can only pass names, not function objects
        var asyncToken = 'request_' + generateUniqueId();
        callbackDictionary[asyncToken] = callback;
        return asyncToken;
    }
    
    var sendRequests = function(serverUrl, requests, callback) {
        debug('sendRequests');
        pendingRequests.push(function() {
            transmit(serverUrl, requests, callback);
        })
        processQueue();
    }
    
    var transmit = function(serverUrl, requests, callback) {
        debug('transmit');
        // register callback
        var asyncToken = registerCallback(callback);
        var jsonRequest = YAHOO.lang.JSON.stringify(requests);
        debug('sending requests to server: ' + jsonRequest);
        // Post the requests to the server via the flash proxy. We pass in the asyncToken
        // because flash can't handle the actual function objects. The proxy returns the asyncToken
        // back to us on the callback, which we can use to locate and execute the callback function.
        // do a last minute check in case flash has become disabled for some reason
        // return true if called, false if not
        if (isFlashReady && typeof(flashProxy.CallDAPI) !== 'undefined') {
			if (dlabs.user.isLoggedIn)
				flashProxy.CallDAPI(serverUrl, jsonRequest, 'slFlashCallback', asyncToken, document.location.toString());
			else
				flashProxy.CallDAPIAsGet(serverUrl, jsonRequest, 'slFlashCallback', asyncToken, document.location.toString(), false);
        }
        else {
            debug('error finding flash tunnel');
        }
    }

    // called by the proxy when it is ready. if the proxy is ready, sets the flashProxy
    // variable so other functions in this scope have access to the proxy
    var flashReady = function() {
        var proxy = document.getElementById(RequestBatch.proxyId);
        if (proxy !== null && typeof(proxy.CallDAPI) !== 'undefined') {
            debug('flash is ready');
            flashProxy = proxy;
            isFlashReady = true;
            processQueue();
        }
        else {
            debug('Flash proxy could not be found! Is RequestBatch.proxyId set correctly?');
        }
    }
    
    // called by the proxy to emit status messages
    var flashStatus = function(status) {
        debug('flashStatus');
        debug('flash proxy status: ' + status);
    }
    
    var decodeServerResponse = function(response) {
        var response = unescape(response);
        // script prefixed strip if present
        response = response.replace(/\\\>/g, ">");
        var m = response.match(/^[^\{]*?(\{.+\}[;]*?)\s*$/im);
        if (m != null) {
          response = m[m.length - 1];
        }
        var batchResponse = YAHOO.lang.JSON.parse(response);
        if (typeof(batchResponse.ResponseBatch) !== 'undefined') {
          batchResponse = batchResponse.ResponseBatch;
        }
        return batchResponse;
    }
    
    // called by the proxy when communication with the server has completed.
    var flashCallback = function(successful, response, asyncToken) {
        try {
            if (!successful) {
                debug('communication error!');
                // not sure what correct behavior is at this point... how to
                // recover from async error?
                return;
            }
            // fetch the caller's callback from the dictionary
            var callback = callbackDictionary[asyncToken];
            // remove from dictionary, we are done with it
            delete callbackDictionary[asyncToken];
            var batchResponse = unescape(response);
            callback(decodeServerResponse(batchResponse));
        }
        catch (e) {
            debug('error!!');
            debug(e);
        }
    }
    
    var oldBeginRequest = RequestBatch.prototype.BeginRequest;
    
    RequestBatch.prototype.BeginRequest = function(serverUrl, callback) {
        if (typeof(RequestBatch.proxyId) === 'undefined') {
            oldBeginRequest.apply(this, [serverUrl, callback]);
            return;
        }
        sendRequests(serverUrl, this, callback);
    }
    
    // expose the flash callbacks to the global scope
    window['slFlashReady'] = function() { flashReady(); }
    window['slFlashStatus'] = function(status) { flashStatus(status); }
    window['slFlashCallback'] = function(successful, response, asyncToken) { flashCallback(successful, response, asyncToken); }
})();
