/* EXTERNAL helper functions (external apps should be reasonly okay to use them)

   Only move functions here which are considered of good enough quality to never have to change their calling syntax :)
*/


/* todd's mouse move handler

   We take care of (de-)registering the events on all iframes. The user should call
   toddSetupMouseMoveHandler in his mousedown handler, and pass a 'mousemove' function,
   a 'mouseup' function and an opaque pointer. The opaque pointer is passed as the first
   parameter to the mousemove and mouseup function. Additionally, the mousemove function
   will receive the last 'x' and 'y' coordinates.

   When a dragaction is active global toddMMH contains info on the dragaction,
   when there's no active dragaction toddMMH will be null */

var toddMMH=null;
function toddMMH_MouseDummy(e) //eat new down events caught while handling the first move to prevent odd drag behaviours
{
  if(!toddMMH) return true; //no drag action active to handle at the moment?
  if(!e) e=this.parentWindow ? this.parentWindow.event : window.event;
  toddDontPropagateEvent(e);
  return false;
}

function toddMMH_MouseMove(e)
{
  if(!toddMMH) return true; //no drag action active to handle at the moment?

  if(!e) e=this.parentWindow ? this.parentWindow.event : window.event;

  if (toddMouseOverCanSeeButtonDown && toddGetMouseButton(e)!=0)
    return toddMMH_MouseUp(e);

  toddMMH.x = toddGetMouseX(e); //ADDME: Merge getmousex/y into this function ?
  toddMMH.y = toddGetMouseY(e);
  toddMMH.lastthis = this;

  if(!toddMMH.didtimer)
  {
    window.setTimeout(toddMMH_MouseMoveFinalize,1);
    toddMMH.didtimer=true;
  }
}
function toddMMH_BoundsCheck() //Keep inside bounds
{
  if(toddMMH.x<0)
    toddMMH.x = 0;
  else if(toddMMH.viewport && toddMMH.x >= toddMMH.viewport.width)
    toddMMH.x = toddMMH.viewport.width - 1;

  if(toddMMH.y<0)
    toddMMH.y = 0;
  else if(toddMMH.viewport && toddMMH.y >= toddMMH.viewport.height)
    toddMMH.y = toddMMH.viewport.height - 1;
}
function toddMMH_MouseMoveFinalize()
{
  if(!toddMMH) return true; //no drag action active to handle at the moment?

  toddMMH_BoundsCheck();
  toddMMH.didtimer=false;
  toddMMH.onmove(toddMMH.opaque, toddMMH.x, toddMMH.y);
}
function toddMMH_MouseUp(e)
{
  if(!toddMMH) return true; //no drag action active to handle at the moment?
  if(!e) e=this.parentWindow ? this.parentWindow.event : window.event;
  if (e)
  {
    toddMMH.x = toddGetMouseX(e); //ADDME: Merge getmousex/y into this function ?
    toddMMH.y = toddGetMouseY(e);
  }
  toddMMH_BoundsCheck();

  // clean up iframe overlappers
  for (var i=0;i<toddMMH.iframes.length;++i)
    toddMMH.iframes[i].overlapper.parentNode.removeChild(toddMMH.iframes[i].overlapper);

  toddMMH.onup(toddMMH.opaque, toddMMH.x, toddMMH.y);
  toddMMH=null;
  return false;
}

function toddSetupMouseMoveOnDoc(doc)
{
  try
  {
    // we only add these once, and then ignore them. repeated adding/moving slows down the use of the global handler
    if(doc.toddMouseMoveHandlerConfigured)
      return;

    if(doc.addEventListener)
    {
      doc.addEventListener("mousemove", toddMMH_MouseMove,  false);
      doc.addEventListener("mouseup",   toddMMH_MouseUp,    false);
      doc.addEventListener("mousedown", toddMMH_MouseDummy, false);
      doc.addEventListener("mouseover", toddMMH_MouseDummy, false);
      doc.addEventListener("mouseout",  toddMMH_MouseDummy, false);
    }
    else if(doc.attachEvent)
    {
      doc.attachEvent('onmousemove', toddMMH_MouseMove);
      doc.attachEvent('onmouseup',   toddMMH_MouseUp);
      doc.attachEvent('onmousedown', toddMMH_MouseDummy);
      doc.attachEvent('onmouseover', toddMMH_MouseDummy);
      doc.attachEvent('onmouseout',  toddMMH_MouseDummy);
    }
    doc.toddMouseMoveHandlerConfigured=true;
  }
  catch(e) //iframes with an external src..
  {
    return;
  }
}

function toddSetupMouseMoveHandler(opaque, onmove, onup)
{
  if(toddMMH)
    toddMMH_MouseUp();

  toddMMH = { onmove: onmove
            , onup: onup
            , opaque: opaque
            , iframes: new Array()
            , x: 0
            , y: 0
            , viewport: toddCurrentViewPort
            , max_y: 0
            , lastthis: null
            , didtimer: false
            };

  toddSetupMouseMoveOnDoc(document);

  var iframes = document.getElementsByTagName('iframe');
  for(var i=0;i<iframes.length;++i)
  {
    var overlapper = toddCreateElement('div');
    overlapper.className = 'toddModalityLayer';
//    overlapper.style.top = toddGetObjectPosY(iframes[i]) + 'px';
//    overlapper.style.left = toddGetObjectPosX(iframes[i]) + 'px';
    overlapper.style.width = iframes[i].clientWidth + 'px';
    overlapper.style.height = iframes[i].clientHeight + 'px';
    overlapper.style.zIndex = 999999;

    // Fix for IE, which somehow seems to add baseline pixels
    // as soon as we insert our overlapper before the iframe into the <td>
    iframes[i].parentNode.style.fontSize = '0px';

    iframes[i].parentNode.insertBefore(overlapper, iframes[i]);

    toddMMH.iframes.push ( { el: iframes[i], overlapper: overlapper } );
    //toddSetupMouseMoveOnDoc(doc);
  }
}

/////////////////////////////////////////////////////////////////////
//
// Generic utilities

function toddSearchElement(list, el)
{
  if (typeof list.indexOf == 'function')
    return list.indexOf(el);
  for(var i=0;i<list.length;++i)
    if(list[i]===el)
     return i;
  return -1;
}

function toddLowerBound(array, value, comp)
{
  var curstart=0, curlimit=array.length;
  var segmentsize = curlimit-curstart;
  while(segmentsize>0)
  {
    var half=parseInt(segmentsize/2);
    var middle=curstart+half;

    if(comp(array[middle],value)) // array[middle] < value
    {
      curstart = middle+1;
      segmentsize = segmentsize - half - 1;
    }
    else
    {
      segmentsize = half;
    }
  }
  return curstart;
}

// Same as Math.max() but set non-number arguments to 0 instead of just returning NaN (this function always returns a number).
function toddMax()
{
  var retval=-Infinity;
  for(var i=0;i<arguments.length;++i)
    if(typeof arguments[i] == 'number' && arguments[i] > retval)
      retval = arguments[i];
  return retval;
}
// Same as Math.min() but set non-number arguments to 0 instead of just returning NaN (this function always returns a number).
function toddMin()
{
  var retval=Infinity;
  for(var i=0;i<arguments.length;++i)
    if(typeof arguments[i] == 'number' && arguments[i] < retval)
      retval = arguments[i];
  return retval;
}

function toddSubstitute(indata, olddata, newdata)
{
  return indata.split(olddata).join(newdata);
}
function toddEncodeValue(indata)
{
  if (!indata)
    return '';
  return indata.split('&').join('&#38;')
               .split('<').join('&#60;')
               .split('"').join('&#34;')
               .split("'").join('&#39;')
               .split('\t').join('&#9;')
               .split('\n').join('&#10;')
               .split('\r').join('');
}

// A dummy handler that eats the event
function toddDummyHandler(e)
{
  if(!e)e=window.event;
  return toddDontPropagateEvent(e);
}

function toddGetMainWindowDimensions()
{
  var mainwindow = document.getElementById("toddTolliumMainWindow");
  if(mainwindow)
  {
    return { left: toddGetObjectScreenX(mainwindow)
           , top: toddGetObjectScreenY(mainwindow)
           , width: mainwindow.offsetWidth
           , height: mainwindow.offsetHeight
           };
  }

  var startoffset = document.getElementById("toddTolliumStartOffset");
  if(startoffset)
  {
    var desktop_x = toddGetObjectScreenX(startoffset);
    var desktop_y = toddGetObjectScreenY(startoffset);
    var xlen = toddCurrentViewPort.width - desktop_x - toddCalcAbsSize(toddRightMargin,true);
    var ylen = toddCurrentViewPort.height - desktop_y - toddCalcAbsSize(toddBottomMargin,false);
    return { left: desktop_x, top: desktop_y, width: xlen, height: ylen };
  }
  return null;
}


/** @short Request the todd application to recalculate its main window. Use after updating toddRightMargin/toddBottomMargin or moving its start offset */
function toddRecalculateMainWindow()
{
  toddCurrentViewPort = toddGetViewPortDimensions();
  toddMainWindow = toddGetMainWindowDimensions();
  toddDebugLog('Root window space: top(' + toddMainWindow.left + ',' + toddMainWindow.top + ') dim(' + toddMainWindow.width + 'x' + toddMainWindow.height + ')');

  //relayout the desktop window for all applications
  for(var i=0; i<toddApplications.length; ++i)
  {
    if(toddApplications[i].windowlist.length>=1 && toddApplications[i].windowlist[0].maximized)
      toddApplications[i].windowlist[0].SetMaximized(true);
  }
}

function toddResizeDesktop(viewport)
{
  var desktop = document.getElementById('toddTolliumDesktop');
  if (desktop)
  {
    var desktopwidth = toddMax(1,viewport.width);
    var desktopheight = toddMax(1,viewport.height - toddGetObjectPosY(desktop));

    var wallpaperimg = desktop.firstChild.todd ? desktop.firstChild : null;
    if (wallpaperimg)
    {
      var orgwidth = wallpaperimg.offsetWidth;//wallpaperimg.todd.imgwidth;
      var orgheight = wallpaperimg.offsetHeight;//wallpaperimg.todd.imgheight;
      if (orgwidth == 0 || orgheight == 0)
      {
        wallpaperimg.style.top = '0';
        wallpaperimg.style.left = '0';
        wallpaperimg.style.width = '100%';
        wallpaperimg.style.height = '100%';
      }
      else
      {
        var noderatio = (1.0*desktopwidth)/(1.0*desktopheight);
        var imgratio = (1.0*orgwidth)/(1.0*orgheight);
        var imgtop = 0;
        var imgleft = 0;
        var imgwidth = 0;
        var imgheight = 0;
        if (noderatio > imgratio)
        {
          imgwidth = desktopwidth;
          imgheight = Math.round(imgwidth / imgratio);
          imgtop = -Math.round((imgheight-desktopheight)/2);
        }
        else
        {
          imgheight = desktopheight;
          imgwidth = Math.round(imgheight * imgratio);
          imgleft = -Math.round((imgwidth-desktopwidth)/2);
        }
        wallpaperimg.style.top = imgtop + 'px';
        wallpaperimg.style.left = imgleft + 'px';
        wallpaperimg.style.width = imgwidth + 'px';
        wallpaperimg.style.height = imgheight + 'px';
        desktop.style.overflow = 'hidden';
      }
    }

    // Set desktop dimensions
    desktop.style.width = desktopwidth + 'px';
    desktop.style.height = desktopheight + 'px';
  }
}


// External callback which tries to close the current application (used by back button)
function toddTryCloseCurrentApplication()
{
  //ADDME: hwo can we try to close all windows (eg back button used with a dialog open?)
  if(toddApplications.length==1)
    toddApplications[0].TryCloseApplication();
}

function toddIsObjectClassSet(obj, cssClass)
{
  if(!obj || typeof obj.className == 'undefined')
    return alert("toddIsObjectClassSet invalid object received"); //ADDME throw?
  var cssClasses = obj.className.split(' ');
  for(var i=0;i<cssClasses.length;++i)
    if(cssClasses[i] == cssClass)
      return true;
  return false;
}

function toddSetObjectClass(obj, cssClass, adding)
{
  if(!obj)
    return alert('Trying to toddSetObjectClass undefined object'); //ADDME throw?
  if(typeof obj.className == 'undefined')
    return;

  var cssClasses = obj.className.split(' ');
  if (cssClasses.length == 1 && cssClasses[0] == '')
    cssClasses = [];

  if (adding)
  {
    // If not added yet, add CSS class
    for (var i = 0; i < cssClasses.length; ++i)
      if (cssClasses[i] == cssClass)
        return; // already added, nothing to do
    cssClasses.push(cssClass);
  }
  else
  {
    // Remove CSS class
    var foundClass = false;
    for (var i = 0; i < cssClasses.length; ++i)
      if (cssClasses[i] == cssClass)
      {
        cssClasses.splice(i, 1);
        foundClass = true;
        break;
      }
    if (!foundClass)
      return; // class not found
  }
  obj.className = cssClasses.join(' ');
}

var toddConsoleLogInit=false;
function toddConsoleLog()
{
  if(!toddConsoleLogInit)
  {
    toddConsoleLogInit=true;
    // For Firebug version 1.2 and up in Firefox 2, the debug console must be initialized explicitly
    if (typeof loadFirebugConsole == 'function')
      loadFirebugConsole();
  }
  try
  {
    if(!window.console)
      return;

    var text='';
    for(var i=0;i<arguments.length;++i)
     text += arguments[i];
    console.log(text);
  }
  catch(e)
  {
  }
}

// Cookie code based on http://www.quirksmode.org/js/cookies.html
function toddWriteCookie(name, value, days)
{
  var expires = "";
  if (days)
  {
    var date = new Date();
    date.setTime(date.getTime() + (days*24*60*60*1000));
    expires = "; expires=" + date.toGMTString();
  }
  document.cookie = name + "=" + value + expires + "; path=/";
}

function toddReadCookie(name)
{
  var nameEQ = name + "=";
  var ca = document.cookie.split(';');
  for(var i=0; i < ca.length; i++)
  {
    var c = ca[i];
    while (c.charAt(0)==' ')
      c = c.substring(1, c.length);
    if (c.indexOf(nameEQ) == 0)
      return c.substring(nameEQ.length, c.length);
  }
  return null;
}

function toddDeleteCookie(name)
{
  toddWriteCookie(name, "", -1);
}

if (typeof toddCurrentLoads != 'undefined')
  toddCurrentLoads["api.js"]=true;
