/**
 * Copyright 2005 Netfluid Ltd. All rights reserved.
 * Use is subject to license terms.
 *
 */


//TODO: include("javascript/util/StringTokenizer.js");


/*
function isDefined(ckVar) {
  var result;
  var scriplet = "result = (typeof(" + ckVar + ") != 'undefined')";
  eval(scriplet);
  return result;
}
*/


// This function returns the name of a given function. It does this by
// converting the function to a string, then using a regular expression
// to extract the function name from the resulting code.
function funcname(f) {
  var s = f.toString().match(/function (\w*)/)[1];
  if ((s == null) || (s.length == 0))
    return "anonymous";

  return s.replace(/_/g, ".");
}

// This function returns a string that contains a "stack trace."
function stacktrace() {
  var s = "";  // This is the string we'll return.
  // Loop through the stack of functions, using the caller property of
  // one arguments object to refer to the next arguments object on the
  // stack.

  var ret = new Array();
  var index = 0;
  for(var a = arguments.caller; a != null; a = a.caller) {
    var s = "";
    // Add the name of the current function to the return value.
    s += funcname(a.callee) + "("; //" - " +  a.length;
    for (i=0; i < a.length; ++i) {
      if (typeof(a[i]) == "string")
	s += '"' + a[i] + '"';
      else
	s += a[i];
      if (i != (a.length-1))
	s += ", ";
    }

    s += ")";

    //s += " in " + a.callee.src;
    
    ret[index] = s;
    ++index;
    // Because of a bug in Navigator 4.0, we need this line to break.
    // a.caller will equal a rather than null when we reach the end 
    // of the stack. The following line works around this.
    if (a.caller == a)
      break;
  }
  //return s;
  return ret;
}


var DEBUG = 0;
var INFO = 1;
var ERROR = 2;

/**
 * LoggingEvent
 *
 */

function LoggingEvent(category, logger, timestamp, level, message) {
  this._category = category;
  this._logger = logger;
  this._timestamp = timestamp;
  this._level = level;
  this._message = message;
}

LoggingEvent.prototype.getLevel = function () {
  return this._level;
}

LoggingEvent.prototype.getMessage = function () {
  return this._message;
}


/**
 * Logger
 */
function Logger_makeTimestamp() {
  var now = new Date();
  return now.getYear() + "-" + 
    now.getMonth() + "-" + 
    now.getDate() + " " +
    now.getHours() + ":" + 
    now.getMinutes() + ":" + 
    now.getSeconds()
    ;
}

function Logger_getLoggerLevelString(level) {
  if (level == DEBUG)
    return "DEBUG";
  else if (level == INFO)
    return "INFO";
  else if (level == ERROR)
    return "ERROR";
}

/**
 * @see http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/ecma-script-binding.html for CharacterData interface
 */
function Logger_write(level, msg) {
  /**
  var loggerView = xGetElementById(this._loggerViewId);
  var textNode = document.createTextNode(this.makeTimestamp() + " " + this.getLoggerLevelString(level) + " " + msg);
  loggerView.appendChild(textNode);
  loggerView.appendChild(document.createElement('br'));
  **/
  if (this._appender != null) {
    this._appender.doAppend(new LoggingEvent("*", this, 0, level, msg));
  }
}

Logger.prototype.addAppender = function (appender) {
  this._appender = appender;
}

function Logger() {
  this.write = Logger_write;
  this.getLoggerLevelString = Logger_getLoggerLevelString;
  this.makeTimestamp = Logger_makeTimestamp;

  this._appender = null;
}

/**
 *
 */

function Appender(elementId) {
  this._loggerViewId = elementId;
}

Appender.prototype.doAppend = function (event) {
  var msg = Logger_makeTimestamp() + " " + Logger_getLoggerLevelString(event.getLevel()) + " " + event.getMessage();
  var loggerView = xGetElementById(this._loggerViewId);


  var t = new StringTokenizer(msg, '\n');
  while (t.hasMoreTokens()) {
    var textNode = document.createTextNode(t.nextToken());
    loggerView.appendChild(textNode);
    loggerView.appendChild(document.createElement('br'));
  }
}



/**
 * FileAppender
 */

function FileAppender() {
  this._logger = window.external.newObject("Logger");
}

FileAppender.prototype.doAppend = function (event) {
  var level = event.getLevel();
  var logger = this._logger;
  var msg = event.getMessage();

  if (level == DEBUG) {
    logger.debug(msg);
  } else if (level == INFO) {
    logger.info(msg);
  } else if (level == ERROR) {
    logger.error(msg);
  }
}


/**
 * Global logging functions
 */

//TODO: _logger = new Logger("loggerView");
//TODO: _logger.addAppender(new FileAppender());


function debug(msg) {
  get("_logger").write(DEBUG, msg); 
}

function trace() {
  var trace = "";
  var st = stacktrace();
  for (i=1; i<st.length; ++i)
    trace += st[i] + "\n";
  get("_logger").write(DEBUG, trace);
}

function info(msg) {
  get("_logger").write(INFO, msg); 
}

function error(msg) {
  get("_logger").write(ERROR, msg); 
}

function insertLoggerView() {
  //document.write('<p id="loggerView" style="font-size: 9px; font-family: Tahoma;"/>');
  //get("_logger").addAppender(new Appender("loggerView"));
  //get("_logger").addAppender(new FileAppender());
}

/*
 * Scheduling functions
 *
 */

// GLOBAL
_tokenAllocator = null;

function getTokenAllocator() {
  if (_tokenAllocator == null)
    _tokenAllocator = new SlotAllocator();
  return _tokenAllocator;
}

function PeriodicTimer_start() {
  this._startTime = (new Date()).getTime();
  if (this.onBefore)
    this.onBefore();
  this.loop();
}


function PeriodicTimer_loop() {
  do {
    // Call template method
    this.doAction();
    var i = ++(this._i);

    if (this._hInterval != null) {
      clearTimeout(this._hInterval);
      this._hInterval = null;
    }
    
    var delay;
    // Re-schedule next round if not disposed
    if (this._token != 0) {
      var now = (new Date()).getTime();
      delay = (i * this._period + this._startTime) - now;
      if (delay > 0) {
	//debug("delay " + delay);
	this._hTimeout = setTimeout(this._codette, delay);
      }
    } else {
      if (this.onAfter)
	this.onAfter();
      break;
    }
  } while (delay <= 0);
  
}

function PeriodicTimer_dispose() {
  getTokenAllocator().free(this._token);
  this._token = 0;
}

function PeriodicTimer_ctr(obj, period) {
  obj._i = 0;
  obj._period = period;
  obj._token = getTokenAllocator().allocate(obj);
  obj._codette = "getTokenAllocator().getObject(" + obj._token + ").loop()";
  this._hInterval = null;
  obj.loop = PeriodicTimer_loop;
  obj.start = PeriodicTimer_start;
  obj.dispose = PeriodicTimer_dispose;
}

function PeriodicTimer(period) {
  PeriodicTimer_ctr(this, period);
}


/**
 * Implement
 *
 * virtual void doAction();
 *
 */
function TimePeriodTimer_ctr(_this, duration, period) {
  if (period == null)
    period = 1/30; // 30fps
  if (duration == null)
    duration = 1000; // 1sec

  PeriodicTimer_ctr(_this, period);
  
  _this._duration = duration;
  _this._elapsed = 0;
  _this.doAction = TimePeriodTimer_doAction;
  _this.getDuration = function() { return this._duration; }
}

function TimePeriodTimer_doAction() {
  this._elapsed += this._period;
  if (this._elapsed >= this._duration)
    this.dispose();
}

function TimePeriodTimer(clip, period, step) {
  TimePeriodTimer_ctr(this, clip, period, step);
}

function newObject(className, arg0, arg1, arg2) {
  return window.external.newObject(className, arg0);
}


/*
 * Redirect all errors - which normally would appear in a pop-up window - to the logger.
 */

function logError(msg, url, line){
  var trace = "";
  var st = stacktrace();
  for (i=1; i<st.length; ++i)
    trace += st[i] + "\n";

  var caller = arguments.caller;
    
  error(msg + " @ line " + (line-1) + "\n" + trace);
  return true;
}

//TODO: window.onerror = logError;


/*
 *
 */




/**
 * Transitions
 * 
 * param is [0..1]
 * abstract void doTransition(param);
 */

function Transition_doAction() {
  //alert( this._elapsed + " " + this._duration );
  this._doAction();
  var k = this._elapsed / this._duration;
  if (k > 1)
    k = 1;
  this.doTransition(k);
}

function Transition_ctr( obj, clip, duration, period ) {
  TimePeriodTimer_ctr( obj, duration, period );
  obj._clip = clip;
  obj._doAction = obj.doAction;
  obj.doAction = Transition_doAction;
}


