/**
 * Author: Mattias Andersson
 * Contact: mattias.andersson@etraveli.com or mattias800@gmail.com
 * Last change: 2009-05-13
 */

function IBEProcess(url, resultReference, successCallbackFunction, failureCallbackFunction, cancelCallbackFunction,
                    parameters, method, callbackScope, parentObject) {

  this.url = url;
  this.parameters = parameters;
  this.method = method ? method : 'GET';
  this.successCallbackFunction = successCallbackFunction;
  this.failureCallbackFunction = failureCallbackFunction;
  this.cancelCallbackFunction = cancelCallbackFunction;
  this.callbackScope = callbackScope;
  this.resultReference = resultReference;
  this.isRunning = false;
  this.isCompleted = false;
  this.isCancelled = false;
  this.parentObject = parentObject;
  this.evaluateResponse = false;

  // Is aware of its manager, so it can callback when it is finished.
  this.parentManager = undefined;
  this.processIndex = undefined;

  this.copyToReference = function (o) {
    if (this.resultReference) {
      if (this.evaluateResponse) {
        try {
          var res = eval(o.responseText);
          for (var prop in res) {
            this.resultReference[prop] = res[prop];
          }
        } catch (e) {
          ibeerror('Unable to evaluate and store process JSON response in reference object.');
        }
      } else {
        this.resultReference.responseText = o.responseText;
        this.resultReference.responseXML = o.responseXML;
        this.resultReference.statusText = o.responseXML;
        this.resultReference.argument = o.argument;
        this.resultReference.getAllResponseHeaders = o.getAllResponseHeaders;
        this.resultReference.getResponseHeader = o.getResponseHeader;
        this.resultReference.statusText = o.statusText;
        this.resultReference.status = o.status;
        this.resultReference.tId = o.tId;
      }
    }

  };

  this.localSuccess = function(o) {
    if (!this.isCancelled) {
      this.isCompleted = true;
      this.isRunning = false;
      this.copyToReference(o);
      // Start callback to manager first, new processes are triggered and HTTP request will run while more callbacks are executed.
      if (this.parentManager && this.parentManager.processDoneCallback) {
        this.parentManager.processDoneCallback(this);
      }
      // Run external callback last.
      if (this.successCallbackFunction) {
        this.successCallbackFunction.apply(this.callbackScope ? this.callbackScope : this, [o, this, this.parentObject]);
      }
    }
  };

  this.localFailure = function(o) {
    if (!this.isCancelled) {
      this.isCompleted = true;
      this.isRunning = false;
      // Start callback to manager first, new processes are triggered and HTTP request will run while more callbacks are executed.
      if (this.parentManager && this.parentManager.processDoneCallback) {
        this.parentManager.processDoneCallback(this);
      }
      if (this.failureCallbackFunction) {
        this.failureCallbackFunction.apply(this.callbackScope ? this.callbackScope : this, [o, this, parentObject]);
      }
    }
  };

  this.localCancel = function(o) {
    if (this.isRunning) {
      this.isRunning = false;
      this.isCancelled = true;
      // Start callback to manager first, new processes are triggered and HTTP request will run while more callbacks are executed.
      if (this.parentManager && this.parentManager.processDoneCallback) {
        this.parentManager.processDoneCallback(this);
      }
      if (this.cancelCallbackFunction) {
        this.cancelCallbackFunction.apply(this.callbackScope ? this.callbackScope : this, [o, this, parentObject]);
      }
    }
  };

  this.startProcess = function() {
    if (this.isRunning === false) {
      var u = this.url;
      if (this.method == 'GET') {
        var time = new Date().getTime();
        var key = time / 15;
        u += (((u.indexOf('?') == -1) ? '?' : '&') + ('b' + key + '=' + time));
      }
      this.isRunning = true;
      this.isCompleted = false;
      this.isCancelled = false;
      YAHOO.util.Connect.asyncRequest(this.method, u, {
        success:this.localSuccess,
        failure:this.localFailure,
        scope: this
      }, this.method == 'POST' ? this.parameters : null);
    }
  };

  this.cancelProcess = function() {
    if (this.isRunning) {
      this.localCancel();
    }
    this.isCompleted = false;
  };
}
;


function IBEProcessManager(pmode, allDoneCallback, callbackScope) {

  /**
   * Mode:
   * "pushover" = add process and all current processes are cancelled and removed. Only one is allowed.
   * "allowone" = add process only if no process is running.
   * "seq" = one at the time, in priority order
   * "allowall" = all processes can be run freely and independently. They will run directly when added.
   *
   * default: allowall
   */
  this.mode = pmode ? pmode : 'allowall';
  this.queueMaxSize = 10;
  this.processesRunning = 0;
  this.queueFullAction = 'discard'; // 'discard' or 'expand', defaults to discard
  this.allDoneCallback = allDoneCallback;
  this.callbackScope = callbackScope;

  // private:
  this.processQueue = new Array();
  this.priorityList = new Array();

  this.addProcessList = function(list) {
    var i;
    for (i = 0; i < list.length; i++) this.addProcess(list[i], undefined, true);
    for (i = 0; i < list.length; i++) this.triggerScheduler(); // Trigger all of them, AFTER adding them.
  };

  this.addProcess = function (process, priority, disableTrigger) {
    if (this.processQueue.length >= this.queueMaxSize) {
      if (this.queueFullAction == 'expand') {
        this.queueMaxSize *= 2; // Double the size of the queue.
      } else {
        // Discard the process
        return;
      }
    }
    if (priority === undefined) {
      priority = 0; // if not specified, no priority!
    }
    if (this.mode == 'pushover') {
      this.clearQueue();
    } else if (this.mode == 'allowone' && this.processesRunning > 0) {
      return; // Only one process allowed, and it is already running. Can be implemented with queueMaxSize as well..
    }
    var i = this.findAvailableProcessIndex();
    this.processQueue[i] = process;
    this.priorityList[i] = priority;
    process.processIndex = i;
    process.parentManager = this;
    if (disableTrigger === undefined || disableTrigger === false) {
      this.triggerScheduler();
    }
    return i;
  };

  this.triggerScheduler = function () {
    var i = this.findHighestPriorityProcessIndex();
    if (this.mode == 'pushover' ||
        this.mode == 'allowall' ||
        (this.mode == 'allowone' && this.processesRunning < 1) ||
        (this.mode == 'seq' && this.processesRunning < 1)) {
      if (i >= 0) {
        this.processesRunning++;
        this.processQueue[i].startProcess();
      }
    }
  };

  this.findHighestPriorityProcessIndex = function () {
    var running = 0; // count running processes at the same time.
    var highestPrio = -1;
    var highestPrioIndex = -1;
    for (var i = 0; i < this.processQueue.length; i++) {
      if (this.processQueue[i]) {
        if (this.processQueue[i].isRunning) {
          running++;
        } else {
          if (this.priorityList[i] > highestPrio) {
            highestPrioIndex = i;
            highestPrio = this.priorityList[i];
          }
        }
      }
    }
    this.processesRunning = running;
    return highestPrioIndex;
  };

  this.findAvailableProcessIndex = function () {
    for (var i = 0; i < this.processQueue.length; i++) {
      if (this.processQueue[i] === undefined) {
        break;
      }
    }
    return i;
  };

  this.removeProcess = function (i) {
    if (this.processQueue !== undefined) {
      if (this.processQueue[i] !== undefined) {
        if (this.processQueue[i].isRunning) {
          this.processesRunning--;
        }
        this.processQueue[i].cancelProcess();
      }

      this.processQueue.splice(i, 1); // remove process from queue, length will decrease.
      // Update index of all processes
      for (; i < this.processQueue.length; i++) {
        this.processQueue[i].processIndex = i;
      }
    }
  };

  this.cancelAllProcesses = function () {
    for (var i = 0; i < this.processQueue.length; i++) {
      this.cancelProcess(i);
    }
  };

  this.cancelProcess = function (i) {
    if (this.processQueue !== undefined) {
      if (this.processQueue[i].isRunning) {
        this.processQueue[i].cancelProcess();
        this.processesRunning--;
      }
    }
  };

  this.clearQueue = function () {
    for (var i = 0; i < this.processQueue.length; i++) {
      this.removeProcess(i);
    }
  };

  this.clearIdleProcesses = function () {
    for (var i = 0; i < this.processQueue.length; i++) {
      if (this.processQueue[i]) {
        if (this.processQueue[i].isRunning) {
          this.removeProcess(i);
        }
      }
    }
  };

  this.processDoneCallback = function (process) {
    this.processesRunning--;
    this.removeProcess(process.processIndex);
    this.triggerScheduler();

    if (this.processQueue.length === 0) {
      var c = this.allDoneCallback;
      if (typeof c == 'function') {
        var s = this.allDoneCallback;
        if (s) {
          c.apply(s);
        } else {
          c();
        }
      }
    }

  };
}

