//////////////////////////////////////////////////////////////////////////////////

var util = {};

function dbg(msg) {};
function dbgx(msg) {};

//////////////////////////////////////////////////////////////////////////////////

/**
 * The add/remove stuff (below) wasn't working for me. I use this to 
 * manage the closers for my popups (like topbar and sidebar).
 */

util.bodyClosers = Array();

util.registerCloser = function(func)
{
  util.bodyClosers.push(func);
  if ( document.body.onclick == null )
  { document.body.onclick = util.bodyCloser; }
}

util.unregisterCloser = function(func)
{
  var idx = util.bodyClosers.indexOf(func);
  if ( idx != -1 )
  { util.bodyClosers.splice(idx, 1); }  
  if ( util.bodyClosers.length < 1 )
  { document.body.onclick = null; }
}

util.bodyCloser = function(event)
{
  for (var idx = 0; idx < util.bodyClosers.length; idx++)
  {
    (util.bodyClosers[idx])(event); // execute the function
  }    
}

//////////////////////////////////////////////////////////////////////////////////

/**
 * Returns an array [x,y] that is the pixel position of the mouse pointer within
 * the display area in global co-ordinates. It requires an input event which is 
 * assumed to be valid (not null, etc). So, if you are scrolled far to the right
 * and far down and you click at the top left of the display area, you don't get
 * [0,0], you get [bigx,bigy]. Global coords are from the original unscrolled
 * origin of the document. See util.screen for more details.
 *
 * http://www.quirksmode.org/js/events_properties.html#position
 */
util.mouse = function(e)
{
  dbgx('util.mouse');

  return [ e.pageX || e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft
         , e.pageY || e.clientY + document.body.scrollTop  + document.documentElement.scrollTop ];
};

//////////////////////////////////////////////////////////////////////////////////

/**
 * Test if the click is a right-click. When we have disabled the default right-click
 * popup, our regular events will fire on right-clicks also, so they have to be
 * filtered out.
 */
util.isrc = function(e)
{
  dbgx('util.isrc');

  var rc = false;
  if      ( e.which  ) { rc = (e.which  == 3); }
  else if ( e.button ) { rc = (e.button == 2); }
  return rc;
};

//////////////////////////////////////////////////////////////////////////////////

/**
 * Returns the target of an event, (i.e. the thing that was actually clicked, not
 * the thing that has the handler registered). But this is browser dependant. Plus
 * there is a safari bug that for text gives you one node up, hence the nodeType
 * check. For details: http://www.quirksmode.org/js/events_properties.html
 */
util.elm = function(e)
{
  dbgx('util.elm');

  var elm = e.target || e.srcElement;
  if ( elm.nodeType == 3 ) { elm = elm.parentNode; }
  return elm;
};

//////////////////////////////////////////////////////////////////////////////////

/**
 * Returns an array [x,y] that is the pixel position of the top-left of the given
 * element within the display area in global co-ordinates. I.e. its position as it 
 * relates to global mouse coordinates.
 */
util.pos = function(elm)
{
  dbgx('util.pos');

  var ary = [0,0];
  while ( elm )
  {
    if ( elm.offsetLeft ) { ary[0] += elm.offsetLeft; }
    if ( elm.offsetTop  ) { ary[1] += elm.offsetTop;  }
    elm = elm.parentNode;
  }
  return ary;
};

//////////////////////////////////////////////////////////////////////////////////

/**
 * Returns an array [x,y,w,h] that is the global coords of the [0,0] of the 
 * display area (meaningfull when scrolled) and the pixel width/height of the
 * display area.
 *
 * From my tests, a raw mouse-click [e.clientX,e.clientY] is always in display
 * coords, i.e. [0,0] is the top-left of the visible area. If you are scrolled
 * then this is not the top-left of the document. That's often not helpful,
 * hence util.mouse which converts it to global coords. However, if you need
 * someting to fit in the displayable area then you need to know the scroll
 * offset, i.e. what is [0,0] in dispaly coords converted to global coords?
 * Like most things, it depends on the browser. In each case the 'wrong' vals
 * are zero, so (as is done in util.mouse), you can just add them to get past
 * the browserness.
 * 
 * FF & IE -- [ document.documentElement.scrollLeft, document.documentElement.scrollTop ]
 * Safari  -- [ document.body.scrollLeft, document.body.scrollTop ]
 *
 */
util.screen = function()
{
  dbgx('util.screen');

  var ary = null;
  if (window.innerWidth) { ary = [ 0, 0, window.innerWidth,         window.innerHeight + document.body.scrollTop ]; }
  else                   { ary = [ 0, 0, document.body.clientWidth, document.documentElement.clientHeight        ]; }
  ary[0] = document.documentElement.scrollLeft + document.body.scrollLeft;
  ary[1] = document.documentElement.scrollTop  + document.body.scrollTop;
  return ary;
};

//////////////////////////////////////////////////////////////////////////////////

/**
 * If the object is outside of the displayable area, move it so that it is just
 * inside the displayable area. It's assumed to be not null and absolutely or
 * relatively positioned. But it doesn't have to be in global coordinates. It
 * can be a child of some random div. It doesn't have to be a child of the body.
 */
util.inview = function(elm)
{
  dbgx('util.inview');

  var pos = util.pos(elm);
  var see = util.screen();

  // convert left/width to right
  pos[2] = pos[0] + elm.offsetWidth;
  see[2] = see[0] + see[2];

  // convert top/height to bottom
  pos[3] = pos[1] + elm.offsetHeight;
  see[3] = see[1] + see[3];

  // if elm is past the right of the screen, move it left
  if ( pos[2] > see[2] )
  { elm.style.left = ( elm.offsetLeft - ( pos[2] - see[2] ) ) + 'px'; }

  // if elm is past the bottom of the screen, move it up
  if ( pos[3] > see[3] )
  { elm.style.top = ( elm.offsetTop - ( pos[3] - see[3] ) ) + 'px'; }

  // recalculate because we may have updated elms [x,y] and above
  // all we want the top/left to be within the screen.
  pos = util.pos(elm)

  // if elm is past the left of the screen, move it right
  if ( pos[0] < see[0] )
  { elm.style.left = ( elm.offsetLeft + ( see[0] - pos[0] ) ) + 'px'; }

  // if elm is past the top of the screen, move it down
  if ( pos[1] < see[1] )
  { elm.style.top = ( elm.offsetTop + ( see[1] - pos[1] ) ) + 'px'; }
};

//////////////////////////////////////////////////////////////////////////////////

/**
 * The problem with event handlers is that all you get is the event object which 
 * only contains mouse coords, etc. Fortunately when handlers are assigned properly
 * you can access the object to which they are assigned (the handlee) from within 
 * them via the 'this' keyword. But what we really want is a method of some class
 * to handle the event (given the event object and handlee as input args). That way
 * we are inside an instance of our class and can access other members and functions
 * appropriately. We accomplish that via some closure magic. Basically you assign
 * as the event handler a returned function which as a product of its innerness
 * can remember the instance that you want to do the handling. And all it does is
 * call the appropriate func on that instance, passing it the event and handlee.
 * Plus you get your event normalized for free (some IE don't pass the event and
 * you have to fetch it from global) because the returned func can do that for you.
 * These will definitely cause memory leaks in IE if you forget to remove them.
 *
 * http://www.jibbering.com/faq/faq_notes/closures.html#clObjI
 *
 * @param obj  - an instance of your class that will do the event handling
 * @param func - the method of your class that you want to handle the event
 *               it must be of the form obj.func(e,o)
 *               @param e - the event object
 *               @param o - the dom object that registered the event handler
 * @param kind - the kind of event you want handled, i.e. 'click'
 * @param elm  - the dom object that will register the event handler
 *
 * @return [elm,kind,bound] - The (obj,kind,func) that were added. These must
 *   be later removed to avoid IE memory leaks. Hence the caller should keep
 *   these in remover list somewhere until they get cleaned up.
 */
util.bind = function( obj, func, kind, elm )
{
  dbgx('util.bind');

  var bound = ( function(e) { e = e || window.event; return obj[func](e,this); } );

  util.add( elm, kind, bound ); 

  return [elm,kind,bound];
};

//////////////////////////////////////////////////////////////////////////////////

/**
 * Event registration. IE only supports bubbling, which means that when div2 is
 * inside div1 and they both have onclick registered and you click on them, then
 * div2 (i.e. the child, i.e. the top-most) fires first and unless you cancel()
 * div1 will fire second. Note that this doesn't apply to two absolutely positioned
 * elements that overlap. In that case if both are siblings then one is simply 
 * covering the other, so the bottom one won't see the event at all. Note that 
 * we avoid the use of attachEvent() because handlers registered in that manner
 * don't give you access to the registered element via the 'this' keyword.
 *
 * http://www.quirksmode.org/js/events_order.html
 *
 * @param obj  - the dom object that will register the event handler
 * @param kind - the kind of event you want handled, i.e. 'click'
 * @param func - the function that will do the handling
 */
util.add = function( obj, kind, func )
{
  dbg('util.add '+kind);

  if (obj.addEventListener)
  { obj.addEventListener(kind,func,false); } // false means use bubbling
  else
  { obj['on'+kind] = func; } // IE only supports bubbling
};

//////////////////////////////////////////////////////////////////////////////////

/**
 * All handlers must be removed before objects are garbage collected else 
 * and IE bug will cause memory leaks.
 */
util.remove = function( obj, kind, func )
{
  dbg('util.remove '+kind);

  if (obj.removeEventListener)
  { obj.removeEventListener(kind,func,false); }
  else
  { obj['on'+kind] = null; }
};

//////////////////////////////////////////////////////////////////////////////////

/**
 * For handlers that were bound the caller received a three part remover array
 * to be later fet to util.remove. This func passes such an array to util.remove
 * and wipes out each elements.
 */
util.remover = function( ary )
{
  dbgx('util.remover');

  util.remove( ary[0], ary[1], ary[2] );
  ary[0] = null;
  ary[1] = null;
  ary[2] = null;
  ary = null;
};

//////////////////////////////////////////////////////////////////////////////////

/**
 * Tell the rest of the document that you've handled this event.
 * This can be called from within an event handler, or it can be assigned
 * as an event handler, in which case it simply kills the event.
 * If you don't cancel an event then it will continue to bubble down
 * to the bottom of the document and may fire other handlers.
 */
util.cancel = function(e)
{
  dbgx('util.cancel');

  if (!e) { var e = window.event; }

  // prevent the default action of the event (like putting the cursor into a text box)
  e.returnValue = false;
  if ( e.preventDefault ) { e.preventDefault(); }

  // prevent other event handlers from firing
  e.cancelBubble = true;
  if ( e.stopPropagation ) { e.stopPropagation(); }

  return false;
};

//////////////////////////////////////////////////////////////////////////////////

/**
 * Limit the input integer v to a minimum of m. This is useful if you are
 * setting div.style.width to someone else's width minus some value and
 * need to avoid the possibility of negative values.
 */
util.lmin = function(v,m)
{
  dbgx('util.lmin');

  if (v<m) { return m; }
  else { return v; }
};

//////////////////////////////////////////////////////////////////////////////////

/**
 * Remove leading and trailing spaces
 *
 * http://lawrence.ecorp.net/inet/samples/regexp-intro.php
 */
util.trim = function(s)
{
  dbgx('util.trim');

  return s.replace(/^\s+|\s+$/g, '');
};

//////////////////////////////////////////////////////////////////////////////////

/**
 * Make input safe to display in html.
 * http://lawrence.ecorp.net/inet/samples/regexp-intro.php
 */
util.hFix = function(s)
{
  dbgx('util.hFix');

  var v = s.replace(/&/g,"&amp;");
      v = v.replace(/>/g,"&gt;");
      v = v.replace(/</g,"&lt;");
  return v;
};

//////////////////////////////////////////////////////////////////////////////////

/**
 * Convert > to &gt; and < to &lt;, then convert \r\n to <br/>
 * So this is really fixThenToBR(). Note that I had weird problems
 * when I was converting to <BR/> which isn't proper xhtml. I would
 * get <br> actually stored in the innerHMTL and that was breaking
 * my conversion back to \r
 * 
 * http://lawrence.ecorp.net/inet/samples/regexp-format.php
 */
util.toBR = function(s,fix)
{
  dbgx('util.toBR');

  var v = s.replace(/>/g,"&gt;");
      v = v.replace(/</g,"&lt;");
      v = v.replace(/(\r\n|[\r\n])/g, "<br/>");
  return v;
};

//////////////////////////////////////////////////////////////////////////////////

/**
 * Replace <br/> with \r
 */
util.toCR = function(s)
{
  dbgx('util.toCR');

  return s.replace(/<br\/>/g, "\r");
};

//////////////////////////////////////////////////////////////////////////////////

/**
 * Return the key code from the keyboard event. Note that this isn't the ascii
 * byte, but just the most recent keycode. So if you hold down shift and press
 * colon, you actually get the ascii for semi-colon, because the colon key is
 * really the semi-colon key. I.e. this code is pre-normal-transforms.
 *
 * http://www.quirksmode.org/js/keys.html
 */
util.key = function(e)
{
  dbgx('util.key');

  if (e.which) { return e.which; }
  else { return e.keyCode; }
};

//////////////////////////////////////////////////////////////////////////////////

/**
 * Return true if the user was trying to type the colon.
 *
 * http://www.quirksmode.org/js/keys.html
 */
util.colon = function(e)
{
  dbgx('util.colon');

  if ( e.keyCode == 59 && e.shiftKey == true )
  { return true; }
  else { return false; }
};

//////////////////////////////////////////////////////////////////////////////////

/**
 * Return true if the user was trying to type the tab.
 */
util.tab = function(e)
{
  dbgx('util.tab');

  if ( e.keyCode == 9 )
  { return true; }
  else { return false; }
};

//////////////////////////////////////////////////////////////////////////////////

/**
 * Return true if the user was trying to type the crlf.
 */
util.crlf = function(e)
{
  dbgx('util.crlf');

  if ( e.keyCode == 10 || e.keyCode == 13 )
  { return true; }
  else { return false; }
};

//////////////////////////////////////////////////////////////////////////////////

/**
 * Convert integers to signed string format. i.e. '+1', '-1'.
 */
util.signed = function(i)
{
  dbgx('util.signed');

  if (i<0) { return ''+i; }
  else { return '+'+i; }
};

//////////////////////////////////////////////////////////////////////////////////

/**
 * Fetch the cookie's value or '' if it doesn't exist. When set by php
 * (which for me is all the time), the cookies seem to all be url encoded
 * so I call util.decode before returning them.
 */
util.cookie = function(s)
{
  dbgx('util.cookie');

  s = util.trim(s)+"=";
  var ca = document.cookie.split(';');
  for ( var i=0; i<ca.length; i++ )
  {
    var c = util.trim(ca[i]);
    if ( c.indexOf(s) == 0 )
    { return util.decode(util.trim(c.substr(s.length))); }
  }
  return '';
};

//////////////////////////////////////////////////////////////////////////////////

/**
 * Set the cookie's value. This looks like you're blowing away the document.cookie
 * value but really it's just adding to it (or replacing an existing entry).
 * Note that in order to test this the page has to actually be served from the evilgoblin
 * domain. Also note that the path and domain parms must be the same on the php side
 * and the javascript side.
 */
util.setCookie = function(name,value)
{
  var date = new Date();
  date.setTime(date.getTime()+25920000000); // 300 days
  document.cookie = name+"="+value+"; expires="+date.toGMTString()+"; path=/; domain=.evilgoblin.com;"; // ### hardcode ###
};

//////////////////////////////////////////////////////////////////////////////////

/**
 * Get a timestamp of the local time.
 */
util.now = function()
{
  dbgx('util.now');

  var now = new Date();
  return Math.round(now.getTime()/1000)-now.getTimezoneOffset()*60;
};

//////////////////////////////////////////////////////////////////////////////////

/**
 * If the keypress is enter then execute the provided function.
 * Example usage: 
 * <input type="password" name="none" onkeypress="return util.enterkey(event,main.login);"/>
 */
util.enterkey = function(e,func)
{
  dbgx('util.enterkey');

  e = e || window.event;

  if ( util.crlf(e) )
  {
    func();
    return util.cancel(e);
  }
  else { return true; } // don't cancel
};

//////////////////////////////////////////////////////////////////////////////////

/**
 * If certain criteria aren't met, display an err.msg and return false.
 */
util.checktxt = function(elm,name,len,req)
{
  dbgx('util.checktxt');

  if (elm.value.length > len) 
  {
    err.msg("Sorry, " + name + " can't be longer than " + len + " characters. Yours is " + elm.value.length + " characters.");
    elm.focus();
    return false;
  }
  if (req && util.trim(elm.value).length < 1) 
  {
    err.msg("Sorry, the " + name + " is required.");
    elm.focus();
    return false;
  }
  return true;
};

//////////////////////////////////////////////////////////////////////////////////

/**
 * http://www.phpbuilder.com/board/showthread.php?t=10318476
 * http://cass-hacks.com/articles/discussion/js_url_encode_decode/
 * http://cass-hacks.com/articles/code/js_url_encode_decode/
 * But is has a problem. For example if you paste the euro into a textarea
 * and then encode it you get %20AC and then your decode gives " AC"
 * You can't solve this problem when passing to PHP because it automatically
 * does the decode before giving you the data from the get, so for these
 * unusable characters, I've decided to use the underscore.
 */
util.encode = function(str)
{
  dbgx('util.encode');

  var out = '';
  var x = 0;
  str = str.toString();
  var regex = /(^[a-zA-Z0-9_.]*)/;
  while (x < str.length) {
    var match = regex.exec(str.substr(x));
    if (match != null && match.length > 1 && match[1] != '') {
    	out += match[1];
      x += match[1].length;
    } else {
      if (str[x] == ' ')
        out += '+';
      else {
        var charCode = str.charCodeAt(x);
        var hexVal = charCode.toString(16);
        if (charCode<256) { out += '%' + ( hexVal.length < 2 ? '0' : '' ) + hexVal.toUpperCase(); }
        else { out += '%5F'; }
      }
      x++;
    }
  }
  return out;
};


//////////////////////////////////////////////////////////////////////////////////

/**
 * http://www.phpbuilder.com/board/showthread.php?t=10318476
 * http://cass-hacks.com/articles/discussion/js_url_encode_decode/
 * http://cass-hacks.com/articles/code/js_url_encode_decode/
 * But I use this to decode values encoded by php, so I need 
 * to turn the plus into the space as well.
 */
util.decode = function(str)
{
  dbgx('util.decode');

  var out = str;
  var binVal, thisString;
  var myregexp = /(%[^%]{2})/;
  while ((match = myregexp.exec(out)) != null
             && match.length > 1
             && match[1] != '') {
    binVal = parseInt(match[1].substr(1),16);
    thisString = String.fromCharCode(binVal);
    out = out.replace(match[1], thisString);
  }
  out = out.replace(/\+/g," ");
  return out;
};

//////////////////////////////////////////////////////////////////////////////////

/**
 * url encode all characters so that if the uri is in the history
 * somewhere it's value won't be obvious.
 *
 * http://bytes.com/topic/javascript/answers/92079-how-can-i-transform-string-code-ascii
 * http://cass-hacks.com/articles/discussion/js_url_encode_decode/
 */
util.hide = function(str)
{
  dbgx('util.hide');

  var out='';
  var num;
  for( var i=0; i<str.length; i++ )
  {
    num = str.charCodeAt(i);
    if (num<16) { out += '%0'+ num.toString(16).toUpperCase(); }
    else if (num<256) { out += '%'+ num.toString(16).toUpperCase(); }
    else { out += '%5F'; }
  }
  return out;
};

//////////////////////////////////////////////////////////////////////////////////

