/*****************************************************************************
 * $Header$
 * $Author: ddoiselet $
 * $Revision: 29916 $
 * $Date: 2008-05-28 12:23:44 +0200 (Wed, 28 May 2008) $
 *
 * Core classes of the xtk javascript framework.
 *
 * Copyright: Neolane 2001-2008
 *****************************************************************************/


/** Test if a string is null or empty */
function isEmptyString(str)
{
  return !(str != null && str.length > 0);
}

// Helpers functions 
function ASSERT(strMessage)
{
  alert(strMessage);
}

/* Log a message in the debug console (if feature is available in the browser) */
function logConsole(str)
{
  if ( window.console )
    window.console.log(str);
  else if( typeof str != "undefined" )
    document.writeln(str + "<br>");
}

/* Print the given object in the debug console */
function DEBUGPRINT(obj)
{
  if ( typeof obj == "string" )
    logConsole(obj);
  else
  {
    for (var i in obj)
      logConsole(i + ": " + obj[i]);
  }
}

function XtkWndObject()
{
  this.strName = null;
}

/*******************************************************************************
 * Event management
 *
 *
 *
 ******************************************************************************/

// IE implementation
function XtkIEEvent()
{
  this.data = event; // event is a global variable under IE
}

XtkIEEvent.prototype.getSource = function()
{
  return this.data.srcElement;
}

// Gecko implementation
function XtkGeckoEvent(event)
{
  this.data = event;
}

XtkGeckoEvent.prototype.getSource = function()
{
  return this.data.target;
}

function NewEvent(evt)
{
  if ( typeof event == "undefined" )
    return new XtkGeckoEvent(evt);
  else
    return new XtkIEEvent();
}

/** Find the XTK object associated to an HTML element */
function findXtkObject(htmlObject)
{
  if ( htmlObject == null )
    return null;
    
  if ( htmlObject.xtkObject != undefined )
    return htmlObject.xtkObject;
  
  return findXtkObject(htmlObject.parentNode);
}

XtkWndObject.prototype.addEventListener = function(domElement, strEvent, fpNotify)
{
  if ( domElement.attachEvent )
    // IE case
    domElement.attachEvent("on" + strEvent, fpNotify);
  else if ( domElement.addEventListener )
    // Greco
    domElement.addEventListener(strEvent, fpNotify, false);
  else
    // browser not supported
    throw ("Unable to attach object to the given element: '" + domElement.tagName + "'");
}
 
function XtkContext()
{ 
  this.timers           = new XtkVector();
  this.tooltipManager   = null;
  this.uniqueID         = 0;                // used to generate uniqueId under gecko
  this.includes         = new Array();
  this.baseURI          = null;             // base URI of XTK datakit (css, js, img)
  this.mouseCapture     = null;
  this.vecDelayLoading  = new XtkVector();
  this.tmrDelayLoading  = null;
  
  // -------------------------------------------------------------------------
  // the event manager
  // -------------------------------------------------------------------------
  this.eventManager = {
    beforeGarbage : 100,
    map : new Array(),
    dispatcher : function(evt) { 
      var e     = NewEvent(evt);
      var type  = e.data.type;
      var mapEvent = xtkContext.eventManager.map[type];
      // window.status = ">" + (new Date()) + ":" + type;
      if ( mapEvent != null )
      {
        var objSource;
        if ( xtkContext.mouseCapture != null )
           objSource = xtkContext.mouseCapture; 
        else
           objSource = e.getSource();
           
        var evInfo = mapEvent[objSource.uniqueID];
        if ( evInfo == null || typeof evInfo == "undefined" )
        { // target not registered, may be the event has been trigerred by a child element
          // of the registered element
          var htmlParent = objSource;
          do
          {
            htmlParent = htmlParent.parentNode;
            if ( htmlParent.uniqueID )
              evInfo = mapEvent[htmlParent.uniqueID];
          } while ( (evInfo == null || typeof evInfo == "undefined") && htmlParent != null );
        }

        var jsTarget  = evInfo.obj;        
        if ( jsTarget != null )
        {
          var fnHandler;
          if ( typeof evInfo.mth != "undefined" )
            fnHandler = jsTarget[evInfo.mth];
          else
            fnHandler = jsTarget["on" + type];
            
          return fnHandler.call(jsTarget, e);
        }
      }
    }
  }
}

XtkContext.prototype.onTimer = function(tmr)
{
  if ( tmr == this.tmrDelayLoading )
  { // call onLoad function on registered objects
    var nObject = this.vecDelayLoading.size;
    var object;
    for (var i=0; i < nObject; i++)
    {
      object = this.vecDelayLoading.item(i);
      object.onLoad.call(object);
    }  
    this.tmrDelayLoading.stop();
  }
}

XtkContext.prototype.registerDelayLoading = function(object)
{
  this.vecDelayLoading.add(object);
  if ( this.tmrDelayLoading == null )
  {
    this.tmrDelayLoading = new XtkTimer(this, 1);
    this.tmrDelayLoading.start();
  }
  else if ( this.tmrDelayLoading.started == false )
    this.tmrDelayLoading.start();
}

XtkContext.prototype.addEventListener = function(strEvent, htmlObject, jsObject, jsMethod)
{
  // 1) find the map associated to the event
  var mapEvent      = this.eventManager.map[strEvent];
  var fnDispatcher  = this.eventManager.dispatcher;
  if ( mapEvent == null )
  {
    mapEvent = new Array();
    this.eventManager.map[strEvent] = mapEvent;
  }
  
  if ( htmlObject.uniqueID == null )
    // generate an uniqueID under Gecko
    htmlObject.uniqueID = "gk_" + this.uniqueID++;
  
  mapEvent[htmlObject.uniqueID] = { obj: jsObject, mth: jsMethod };
  if ( htmlObject.attachEvent )
    // IE case
    htmlObject.attachEvent("on" + strEvent, fnDispatcher);
  else if ( htmlObject.addEventListener )
    // Greco
    htmlObject.addEventListener(strEvent, fnDispatcher, false);
  else
    // browser not supported
    throw ("Unable to attach object to the given element: '" + htmlObject.tagName + "'");
    
  this.eventManager.beforeGarbage--;
  if ( this.eventManager.beforeGarbage <= 0 )
    this.garbageEventListener();
}

/** Remove delete HTML objects from the eventManager */
XtkContext.prototype.garbageEventListener = function()
{
  for (var evt in this.eventManager.map)
  {
    var mapEvent = this.eventManager.map[evt];
    for (var name in mapEvent)
    {
      var o = mapEvent[name];
      if ( (typeof o.obj.htmlElement.parentElement != "undefined" && o.obj.htmlElement.parentElement == null) /* IE */
        || o.obj.htmlElement.parentNode == null /* gecko */ )
        delete mapEvent[name];        
    }
  }
  
  this.eventManager.beforeGarbage = 100;
}

XtkContext.prototype.captureMouse = function(obj)
{
  this.mouseCapture = obj;
  obj.setCapture();
}

XtkContext.prototype.releaseMouse = function()
{
  this.mouseCapture.releaseCapture();
  this.mouseCapture = null;
}

/** Generate an unique identifier.
  *
  * @return an unique identifier. */
XtkContext.prototype.getUniqueId = function()
{
  return this.uniqueID++;
}

XtkContext.prototype.getDataIsland = function(strName)
{
  var node = document.getElementById(strName);  
  if( node )
  {
    if ( node.hasChildNodes )
    {
      if (  node.firstChild.nodeType !=3 )
        return node.firstChild;
      if ( node.childNodes[1].nodeType == 1 )
        return node.childNodes[1];
      if ( node.firstChild.nodeType == 1 )
        return node.firstChild;
    }      
    else
    {
      var strXMLSource = node.outerHTML;
      if (document.implementation.createDocument)
      { // Mozilla, create a new DOMParser
        var parser = new DOMParser();
        xmlDocument = parser.parseFromString(strXMLSource, "text/xml");
      }
      else if ( window.ActiveXObject )
      { // IE, create a new XML document using ActiveX
        // and use loadXML as a DOM parser.
        xmlDocument = new ActiveXObject("Microsoft.XMLDOM")
        xmlDocument.async="false";
        xmlDocument.loadXML(strXMLSource);      
        return xmlDocument;
      }       
    }
  }
  return null;
}

/** Get the base of the URI of the XTK datakit. */
XtkContext.prototype.getBaseURI = function()
{
  if ( this.baseURI == null )
  { // try to find locations of scripts
    if ( core.isIE )
    { // IE specific code: because IE could be embebed in the application, 
      // the location the document is not always the path to the datakit. Then 
      // we use the URL of core.js to find the right URI.
      var strSrc, nScript = document.scripts.length;
      for(var i=0; i < nScript; i++)
      {
        strSrc = document.scripts[i].src;
        if ( strSrc.indexOf("xtk/js/core.js") != -1 )
        {
          this.baseURI = strSrc.substring(0, strSrc.length - ("xtk/js/core.js").length);
          break;
        }
      }

      if ( this.baseURI == null || this.baseURI.length == 0 )
      { // last chance, use the <base> HTML element
        var nlBase = document.getElementsByTagName("base");
        if ( nlBase != null && nlBase[0] != null )
          this.baseURI = nlBase[0].href;
      }
    }
    else
      this.baseURI = document.location.protocol + '//' + document.location.host;
  }

  return this.baseURI;
}

/** Get a value of the node from a XPath
  *
  * @in node the node containing the requested value.
  * @in the requested value.
  * @return the value as a string. */
XtkContext.prototype.getXMLValue = function(node, strXPath)
{
  if ( strXPath.charAt(0) == '@' )
    return node.getAttribute(strXPath.substring(1));
    
  var iSepIndex = strXPath.indexOf("/");
  if ( iSepIndex != 0 )
  {
    var strLeftPart   = strXPath.substring(0, iSepIndex);
    var strRightPart  = strXPath.substring(iSepIndex+1);
    var nChild        = node.childNodes.length; 
    for (var i=0; i < nChild; i++)
      if ( node.childNodes[i].nodeName == strLeftPart )
        return this.getXMLValue(node.childNodes[i], strRightPart);
  }

  throw "XtkContext.getXMLValue: '" + strXPath + "' xpath to complex. Not yet supported";
}

var xtkContext = new XtkContext();

/*******************************************************************************
 * XtkVector
 *
 *
 *
 ******************************************************************************/
 
/** Constructor */ 
function XtkVector()
{
  this.inc  = 20;
  this.size = 0;
  this.data = null;
}

/** Remove all content from the vector */
XtkVector.prototype.clear = function()
{
  this.data = null;
  this.size = 0;
}

/** Increment the size of the vector */
XtkVector.prototype.resize = function()
{
  if ( this.data == null )
    this.data = new Array(this.inc);
  else
    this.data.concat(new Array(this.inc));
}

/** Sorts the elements in the entire vector using the fnCompare comparaison 
  * function. 
  * 
  * @in fnCompare a comparaison function. */
XtkVector.prototype.sort = function(fnCompare)
{
  if ( this.data != null )
  {
    if ( this.data.length != this.size )
      // ajust the array size before calling the javascript sort function
      // to avoid to sort void items
      this.data.splice(this.size, this.data.length-this.size);
    
    this.data.sort(fnCompare);
  }
}

/** Add an item to the vector.
  *
  * @in item the item to add.
  * @return the item added to the vector. */
XtkVector.prototype.add = function(item)
{
  if ( this.data == null || this.size == this.data.length )
    this.resize();
  
  this.data[this.size] = item;
  this.size++;
  return item;
}

/** Remove an item from the vector.
  *
  * @in item the item to remove.
  * @return the item removed from the vector. */
XtkVector.prototype.remove = function(item)
{
  this.removeFromIndex(this.indexOf(item));
  return item;
}

/** Search the index of the given item.
  * 
  * @in item the item to search.
  * @return the index of the item or -1 if the item has not been found. */
XtkVector.prototype.indexOf = function(item)
{
  var size  = this.size;
  var data  = this.data;
  for (var i=0; i < size; i++)
    if ( data[i] == item )
      return i;

  return -1;
}

/** Remove an item from its index.
  * 
  * @in index the index of the item to remove. */
XtkVector.prototype.removeFromIndex = function(index)
{
  if ( index == -1 )
    return;
  var size  = this.size;
  var data  = this.data;
  for (var i=index; i < size; i++)
    data[i] = data[i+1];
  
  data[this.size-1] = null;
  this.size--;
}

/** Get an item from its index.
  * 
  * @in index the index of the item. */
XtkVector.prototype.item = function(index)
{
  return this.data[index];
}

/** Convert vector contant as a string (using comma as separator) */
XtkVector.prototype.toString = function()
{
  var strResult = "";
  var size  = this.size;
  var data  = this.data;
  for(var i=0; i < size; i++)
    strResult += (i > 0) ? ',' + data[i] : data[i];
    
  return strResult;
}

/*******************************************************************************
 * XtkTimer
 *
 *
 *
 ******************************************************************************/
 
/** Constructor
  *
  * @in target        the target object. That object MUST have a onTimer method.
  * @in firstTimeout  period in milliseconds of the first timeout.
  * @in timeout       period in milliseconds of other timouts. If that value is
  *                   not defined, the timer object use the firstTimeout. */
function XtkTimer(target, firstTimeout, timeout)
{
  this.target           = target;
  this.firstTimeout     = firstTimeout;
  this.timeout          = (typeof timeout == "undefined") ? firstTimeout : timeout;
  this.started          = false;
  this.nextExpiration   = 0;
}

/** Internal method called when a timer expire to trigger event to the right 
  * targets */
XtkTimer.doTimeout = function()
{
  var expiration = new Date().getTime();
  var timers = xtkContext.timers;
  var size   = timers.size;
  var timer  = null;
    
  for (var i=0; i < size; i++)
  {
    if ( timers.size < size )
      // timer list has changed
      break;
      
    timer = timers.data[i];
    if ( timer.nextExpiration <= expiration )
    { // the timer must be triggered
      timer.target.onTimer(timer);
      timer.nextExpiration = Math.max(expiration, timer.nextExpiration+timer.timeout);
    }
  }
  
  XtkTimer.resetTimer();
}

/** Internal method called when one timer state change. */
XtkTimer.resetTimer = function()
{
  var timers = xtkContext.timers;
  var size   = timers.size;
  if ( size > 0 )
  {
    var nextExpiration = timers.data[0].nextExpiration;
    var target         = timers.data[0].target;
    for (var i=1; i < size; i++)
    {
      if ( timers.data[i].nextExpiration < nextExpiration )
      {
        nextExpiration = timers.data[i].nextExpiration;
        target         = timers.data[i].target;
      }
    }
    
    setTimeout("XtkTimer.doTimeout()", 
      Math.max(0, nextExpiration-((new Date()).getTime())));
  }
} 

/** Start or restart the timer.
  *
  * @in target        the target object. That object MUST have a onTimer method.
  * @in firstTimeout  period in milliseconds of the first timeout.
  * @in timeout       period in milliseconds of other timouts. If that value is
  *                   not defined, the timer object use the firstTimeout. */
XtkTimer.prototype.start = function(target, firstTimeout, timeout)
{
  if ( typeof target != "undefined" )
  {
    this.target = target;
    if ( typeof firstTimeout != "undefined" )
    {
      this.firstTimeout = firstTimeout;
      if ( typeof timeout != "undefined" )
        this.timeout = timeout;
      else
        this.timeout = firstTimeout;
    }
  }
  
  // check if the timer is not allready started
  if ( this.started == false )
  {
    xtkContext.timers.add(this);
    this.started = true;
  }
  
  this.nextExpiration = (new Date()).getTime() + this.firstTimeout;
  XtkTimer.resetTimer();
}

/** Stop the timer if it's started */
XtkTimer.prototype.stop = function()
{
  if ( this.started )
  {
    xtkContext.timers.remove(this);
    this.started = false;
  }
}

function Cookie(strName)
{
  this.strName  = strName;
  this.strValue = null;
  
  var start = document.cookie.indexOf( strName + "=" );
  var len = start + strName.length + 1;
  if ( ( !start ) && ( strName != document.cookie.substring( 0, strName.length ) ) )
    return;
    
  if ( start == -1 ) 
    return;
  
  var end = document.cookie.indexOf( ";", len);  
  if ( end == -1 ) 
    end = document.cookie.length;
  
  this.strValue = unescape(document.cookie.substring(len, end));
}

/** Get the cookie if it exists.
  *
  * @return the value of the cookie. */
Cookie.prototype.getValue = function()
{
  return this.strValue;
}

Cookie.prototype.setValue = function(strValue, days)
{
  var tmExpiration = new Date();
  tmExpiration.setTime(tmExpiration.getTime()+86400*days);
  document.cookie = this.strName + "=" + strValue + "; expires=" + tmExpiration.toUTCString() + "; path=/";
} 

Cookie.prototype.erase = function()
{
  var tmExpiration = new Date();
  tmExpiration.setTime(tmExpiration.getTime()-86400); // expire yesterday
  document.cookie = this.strName + "=; expires=" + tmExpiration.toUTCString() + "; path=/";
}