/* configuration
 ------------------------------------------------------- */
var domain = 'site';
var debug  = true;

/* _onload
 ------------------------------------------------------- */

$(document).ready(function() {
  // check for hanging connections every 2 seconds
  setUrlParams();
  doFocus();
  
  $('a.jumpTop').each(function(){
    this.href  ='javascript:;';
    this.title ='Jump to top';
  }).click(function(){
    this.blur();
    $('body').ScrollTo();
  });
  
  $('a.scrollTo').click(function(){
    this.blur();
    hrefSplit = this.href.split('#');
    if (hrefSplit[1]) {
      $('#' + hrefSplit[1]).ScrollTo();
    }
    return false;
  });
  
});


/* shared initialization
------------------------------------------------------- */
var WR={};
var global = {};
var config = {};


WR.windowReload = function() {
  window.location.reload();
};

/* Extending JS-Objects
------------------------------------------------------- */

// http://groups.google.de/group/de.comp.lang.javascript/browse_thread/thread/ab7f9b8f967c3193/48c51843bfa3d723?lnk=st&q=javascript+array+contains&rnum=4&hl=de#48c51843bfa3d723
Array.prototype.contains = function(searchValue) {
  for (var i = 0, len = this.length; i < len && this[i] !== searchValue; i++) ;
  return i < len;
};

// http://www.ditchnet.org/wp/2005/04/04/enhance-your-javascript-arrays/
Array.prototype.removeAt = function (iIndex) {
  return this.splice(iIndex, 1);
};

Array.prototype.remove = function(element) {
  var result = 0;
  for (var i = 0; i < this.length; ) {
    if (this[i] == element) {
      this.removeAt(i);
      ++result;
    }
    else {
      i++
    }
  }
  return result;
};

String.prototype.trim = function() {
  return this.replace(/^\s*([\s\S]*\S+)\s*$|^\s*$/, '$1');
};

String.prototype.nl2br = function() {
  return this.replace(/\r\n|\r|\n/g, '<br \/>');
};

String.prototype.isAlphaNum = function() {
  return !/\W/.test(this);
}

// credits to Christian Kruse: http://aktuell.de.selfhtml.org/tippstricks/programmiertechnik/email/
String.prototype.isEmail = function() {
  var proto  = "(mailto:)?";
  var usr    = "([a-zA-Z0-9][a-zA-Z0-9_.-]*|\"([^\\\\\x80-\xff\015\012\"]|\\\\[^\x80-\xff])+\")";
  var domain = "([a-zA-Z0-9][a-zA-Z0-9._-]*\\.)*[a-zA-Z0-9][a-zA-Z0-9._-]*\\.[a-zA-Z]{2,5}";
  var regex  = "^" + proto + "?" + usr + "\@" + domain + "$";

  var rgx    = new RegExp(regex);
  return rgx.exec(this) ? true : false;
};

String.prototype.isURL = function() {
  return (this.indexOf('http://') == 0 && this.length > 'http://'.length);
};

// from prototype...
String.prototype.stripTags = function() {
  return this.replace(/<\/?[^>]+>/gi, '');
};

String.prototype.startsWith = function(prefix, offset) {
  var offset = offset || 0;
  if(offset < 0 || offset > this.length) return false;
  return this.substring(offset, offset + prefix.length) == prefix;
};

String.prototype.endsWith = function(suffix) {
  return this.substring(this.length - suffix.length) == suffix;
};

// from prototype...
String.prototype.escapeHTML = function() {
  var div = document.createElement('div');
  var text = document.createTextNode(this);
  div.appendChild(text);
  return div.innerHTML;
};

// from http://www.crockford.com/javascript/remedial.html
// param = {domain: 'valvion.com', media: 'http://media.{domain}/'};
// url = "{media}logo.gif".supplant(param);

String.prototype.supplant = function (o) {
  var i, j, s = this, v;
  for (;;) {
    i = s.lastIndexOf('{');
    if (i < 0) {
      break;
    }
    j = s.indexOf('}', i);
    if (i + 1 >= j) {
      break;
    }
    v = o[s.substring(i + 1, j)];
    if (!isString(v) && !isNumber(v)) {
      break;
    }
    s = s.substring(0, i) + v + s.substring(j + 1);
  }
  return s;
};

String.prototype.ucfirst = function () {
   return this.substr(0,1).toUpperCase() + this.substr(1, this.length);
};

/* visual effects
------------------------------------------------------- */

// credits to Peter-Paul Koch, http://www.evolt.org/article/document_body_doctype_switching_and_more/17/30655/
function posTop() {
  if (window.pageYOffset)
    return window.pageYOffset;
  else if (document.documentElement && document.documentElement.scrollTop)
    return document.documentElement.scrollTop;
  else if (document.body)
    return document.body.scrollTop;
};

function charsRemaining(id, maxLength) {
  var inputField = document.getElementById(id);
  if(inputField.value.length <= maxLength)
    return ;
  
  inputField.value = inputField.value.substr(0, maxLength);
};

function scrollToTop() {
  $('#main').ScrollTo();
};

/* AJAX / XmlHelper
------------------------------------------------------- */


function XmlHelper() {
  // http://w3future.com/html/stories/hop.xml (Using methods as functions).
  var me = this;

  this.send = function(params, onload) {
    me.onload  = onload;
    
    // check if requested package/method needs login
    if (!global.accountId && 
        params.p && 
        params.m && 
        global.loginRequiringMethods[params.p] && 
        global.loginRequiringMethods[params.p].contains(params.m)) {
      new Modlog.construct(ViewAccount.loginInlay,{'errorText': 'Please login to do this.'}).open();
      return ;
    }
    
    data = [];
    $.each(params, function(key){data.push({name:key,value:this});});

    $.ajax({
      url:      config.API_URL,
      type:     'POST',
      data:     $.param(data),
      dataType: 'xml',
      //ifModified: 1,

      complete: me.ajaxComplete,
      success:  me.ajaxSuccess,
      error:    me.ajaxError
    });
  };
  
  this.formSubmit = function(form, callback, preProcessParamsCallback) {
    // initial state
    $(form).find('input[type=text]').removeClass().addClass('std');
    $(form).find('input[type=password]').removeClass().addClass('std');
    $(form).find('textarea').removeClass().addClass('std');
    $(form).find('div.hint-error').removeClass().addClass('hint');
    
    // params
    var paramsRaw = $(form).serialize().vars;
    var params = {};
    for (var i = 0; i < paramsRaw.length; i++) {
      params[paramsRaw[i].name] = paramsRaw[i].value;
    }
    if (preProcessParamsCallback)
      params = preProcessParamsCallback(params);
    
    // send
    xh.send(params, callback);
    return false;
  };
  
  this.ajaxSuccess = function(xml) {
    console.log('ajaxSuccess');
    statusCode     = null;
    subStatusCodes = null;

    var accountId = (xml.documentElement.getAttribute('accountId') !== null)
      ? xml.documentElement.getAttribute('accountId')
      : false;

    // do not do that for login, since the wrsess cookie will be possibly overridden
    // from listeners. only logout should be handled this way...
    if (global.accountId && !accountId) {
      global.accountId = false;
      loginState.notify();
    }

    if (xml.getElementsByTagName("status")[0]) {
      statusCode = xml.getElementsByTagName("status")[0].getAttribute('code');

      if (statusCode == 'FAIL') {
        var subStatusCodes = [];
        var childNodes = xml.getElementsByTagName("status")[0].childNodes;

        for(var i = 0; i < childNodes.length; i++) {
          subStatusCodes.push(childNodes[i].getAttribute('code'));
        }

        if (subStatusCodes.contains('API_UNEXPECTED_FATAL') || subStatusCodes.contains('METHOD_UNKNOWN')) {
          new Modlog.construct(ViewBase.notifyInlay, {
            'title': 'Unexpected error occured',
            'text' : 'Ooops, some piece of ' + config.APP_NAME + ' was lousy programmed. The ' + config.APP_NAME + ' team has been reproved automatically and is working hard to fix the error. Sorry for the unconvenience!',
            'btn'  : 'Close',
            'alert': true
          }).open();
          return ;
        }

        if (subStatusCodes.contains('API_ACCESS_BLOCKED')) {
          new Modlog.construct(ViewBase.notifyInlay, {
            'title': 'Access blocked',
            'text' : 'Access to ' + config.APP_DOMAIN + ' has been blocked. Check our <a href="' + config.SITE_URL + '">Homepage</a> for further information.',
            'btn'  : 'Close',
            'alert': true
          }).open();
          return ;
        }

        if (subStatusCodes.contains('AUTHORIZATION_FAILED')) {
          new Modlog.construct(ViewBase.notifyInlay, {
            'title': 'Authorization failed',
            'text' : 'Sorry, you are not allowed to do this.',
            'btn'  : 'Close',
            'alert': true
          }).open();
          return ;
        }

        if (subStatusCodes.contains('LOGIN_REQUIRED')) {
          if (global.accountId) {
            global.accountId = false;
            loginState.notify();
          }
          new Modlog.construct(ViewAccount.loginInlay,{'errorText': 'Please login to do this.'}).open();
          return ;
        }
      }
    }

    // proceed with callback handler. handles success AND fail
    me.onload(xml, statusCode, subStatusCodes);  
  };

  this.ajaxComplete = function(xmlhttp, JQueryStatus) {
    console.log('ajaxComplete:' + JQueryStatus);
  };

  this.ajaxError = function(xmlhttp, JQueryStatus) {
    console.log('ajaxError:' + JQueryStatus);
  
    $('#loading').hide();
    new Modlog.construct(ViewBase.notifyInlay, {
      'title': config.APP_DOMAIN + ' unreachable',
      'text' : config.APP_DOMAIN + ' seems to be out of reach right now. Maybe your network is down, or the ' + config.APP_NAME + ' server might be slowed down by  unexpectedly high traffic.<br /><br />So get yourself a tasty cup of coffee and try again later.',
      'btn'  : 'Close',
      'alert': true
    }).open();
  };
};

$(document).ready(function(){
  xh = new XmlHelper(); // global XmlHelper 
});

jQuery.fn.wrSubmit = function(callback, preProcessParamsCallback) {
  $(this).get(0).setAttribute('accept-charset', 'utf-8');
  $(this).get(0).setAttribute('method', 'post');
  $(this).submit(function(){
    return xh.formSubmit(this, callback, preProcessParamsCallback);
  });
};


/* Modlog (MODal diaLOG) class
------------------------------------------------------- */

var modlogStack = [];
var Modlog = {};

Modlog.CLOSE_MODE_OK     = 0;
Modlog.CLOSE_MODE_CANCEL = 1; // this forces onbeforecancel to be checked

Modlog.onkeypress = function(e) {
  e = e || window.event;
  if (e.keyCode == 27) {
    var closed = Modlog.close(Modlog.CLOSE_MODE_CANCEL); // closed == true indicates that a modlog was closed

    // stop progagation of key event, closing
    // multiple modlogs with one key press
    if (window.ActiveXObject)
      window.event.cancelBubble = true;

    if (e.stopPropagation)
      e.stopPropagation();
  }
};


// always closes the last opened modlog
Modlog.close = function(mode) {
  if (modlogStack.length == 0)
    return false;

  var modlogObj = modlogStack[modlogStack.length - 1];
  if (modlogObj.coObj.doScrollToTop())
    scrollToTop();

  modlogObj.close(mode);

  return true; // indicates that there might be further open modlogs
};

Modlog.replaceContent = function(contentObj, coParams, callback) {
  if (modlogStack.length != 0) {
    Modlog.close(Modlog.CLOSE_MODE_OK);
  }

  new Modlog.construct(contentObj, coParams, callback).open();
};

// only one purpose: redraw the cover so the
// complete screen is always locked for user
// interaction. if we do not do this, the
// locked area will remain static while we
// may maximize the window and the clickable
// parts, underlying the cover

Modlog.resize = function() {
  if (modlogStack.length == 0)
    return false;
  
  var ih = getWindowInnerHeight();
  $('.cover').css({
    'width': '100%',
    'height': ih + 'px'
  });
};

Modlog.changeState = function(modlogObjGuid, state) {
  switch (state) {
    case 'neutral':
      Modlog.color(modlogObjGuid, 'neutral');
      break;
    case 'failure':
      Modlog.color(modlogObjGuid, 'failure');
      break;
    case 'success':
      Modlog.color(modlogObjGuid, 'success');
      $('#modlog_' + modlogObjGuid + '.mlBody').cover(1);
      break;
  }
};


Modlog.color = function(modlogObjGuid, type) {
  switch (type) {
    case 'neutral': 
      $('#modlog_' + modlogObjGuid).css({'background':'url(/img/modlogCover.png)'});
      $('#modlog_' + modlogObjGuid + ' .mlInner').css({'borderColor':'#000'});
      $('#modlog_' + modlogObjGuid + ' .mlHeader').css({'background':'#000'});
      break;
    case 'failure': 
      $('#modlog_' + modlogObjGuid).css({'background':'url(/img/modlogCoverFailure.png)'});
      $('#modlog_' + modlogObjGuid + ' .mlInner').css({'borderColor':'#c03'});
      $('#modlog_' + modlogObjGuid + ' .mlHeader').css({'background':'#c03'});
      break;
    case 'success': 
      $('#modlog_' + modlogObjGuid).css({'background':'url(/img/modlogCoverSuccess.png)'});
      $('#modlog_' + modlogObjGuid + ' .mlInner').css({'borderColor':'#060'});
      $('#modlog_' + modlogObjGuid + ' .mlHeader').css({'background':'#060'});
      break;
  }
};


Modlog.construct = function(contentObj, coParams, callback) {
  if (contentObj == null) {
    contentObj = ViewBase.notifyInlay;
  }

  // test coObj interface
  if (!contentObj.guid) alert('Modlog.construct: contentObj interface not fully implemented');

  // only one modlog, specified by guid is allowed to open up
  if (typeof this.getDivByModlogGuid(contentObj.guid) == 'object')
    return false;

  this.coObj = contentObj;
  this.isModal = (typeof this.coObj.modal == 'undefined') || this.coObj.modal == true; // set this parameter in the definition of the contentObject
  this.coParams = coParams;
  this.callback = callback;

  if (this.coObj.init && !this.coObj.init())
    return false; // cancelled open
};

Modlog.construct.prototype = {

  open: function() {
    if (this.isModal) {
      var modlogZIndex = 2 * (modlogStack.length + 1) + 1000;
      $('.cover').css({
        'zIndex': modlogZIndex,
        'width': '100%',
        'display': 'block'
      });

      var ih = getWindowInnerHeight();
      $('.cover').css({'height':ih + 'px'});
    }

    if (modlogStack.length == 0) { // we are preparing a modlog to show up, so we must make our modlogs-container visible
      $('#modlogs').show();
    }

    this.createDiv();
    $('#modlog_' + this.coObj.guid).css({'zIndex': 2*(modlogStack.length+1)+1001});
    $('#modlog_' + this.coObj.guid).Draggable({ // http://www.eyecon.ro/interface/
        handle:'.mlTitle',
        zIndex:modlogZIndex,
        containment:'document'
    });


    if (this.coObj.maxWidth)
      $('#modlog_' + this.coObj.guid).css({'max-width':this.coObj.maxWidth});

    if (this.coObj.doScrollToTop())
      scrollToTop();

    var modlogObj = this;
    modlogStack.push(modlogObj);

    // show modlog
    $('#modlog_' + this.coObj.guid).show();

    if (this.coObj.url) {
      $('#mlBody_' + this.coObj.guid).cover('Loading...');
      var innerThis = {
        coObj:this.coObj,
        coParams:this.coParams?this.coParams:null,
        callback:this.callback
      };
      //log(innerThis);
      
      $('#mlBody_' + this.coObj.guid).load(this.coObj.url + '?v=' + config.v, innerThis, function(){
        $(this).uncover();
        if (!innerThis.coObj.onopen)
          return ;
          
        if (innerThis.coParams) {
          innerThis.coObj.onopen(innerThis.coParams,innerThis.callback); // ViewBase.notifyInlay, ViewAccount.loginInlay
        } else {
          innerThis.coObj.onopen(innerThis.callback);
        }
      });
    }
    else {
      if (this.coParams)
        this.coObj.onopen(this.coParams,this.callback); // ViewBase.notifyInlay
      else
        this.coObj.onopen(this.callback);
    }
  },

  close: function(mode) {
    if (mode == Modlog.CLOSE_MODE_CANCEL) {
      if (this.coObj.onbeforeclose && !this.coObj.onbeforeclose())
        return false;
    }

    this.deleteDiv();

    if (modlogStack.length == 0) {
      $('#modlogs').hide();
    }

    // delete from stack
    modlogStack.pop();

    if (this.isModal) {
      if (modlogStack.length == 0) {
        // css-variant. makes cursor invisble :(
        $('.cover').css({
          width:0,
          height:0,
          zIndex:0
        });
      }
      else {
        $('.cover').css('zIndex', 2 * modlogStack.length + 1000);
      }
    }

    if (this.coObj.onclose)
      this.coObj.onclose();
  },

  // creates a new modlog-div, appends it to the modlogs-div
  createDiv: function() {
    var modlog = document.createElement('div');
    modlog.id = 'modlog_' + this.coObj.guid;
    modlog.className = 'modlog';

    modlog.style.top = this.coObj.doScrollToTop()
      ? (40 + (modlogStack.length - 1 ) * 10) + 'px'
      : (20 + posTop() + (modlogStack.length - 1 ) * 10) + 'px';

    if (this.coObj.width) modlog.style.width = this.coObj.width;

    modlog.style.left = (6 + (modlogStack.length - 1 ) * 2) + '%';
    $('#modlogs').get(0).appendChild(modlog);

    $('#' + modlog.id).html([
      '<div class="mlInner">',
        '<div class="cf mlHeader">',
          '<table>',
          '<tr>',
            '<td class="mlTitle" id="mlTitle_' + this.coObj.guid + '"></td>',
            (this.coObj.hideClose ? '' : '<td class="mlClose"><a href="javascript:;" onclick="Modlog.close(Modlog.CLOSE_MODE_CANCEL)" title="Tipp: press &lt;ESC&gt; on your keyboard">close</a></td>'),
          '</tr>',
          '</table>',
        '</div>',
        '<div id="mlBody_' + this.coObj.guid + '" class="cf mlBody">&nbsp;</div>',
      '</div>'
      ].join('')
    );
  },

  // deletes the modlog div, created previously
  deleteDiv: function() {
    var node = $('#modlogs').get(0);
    node.removeChild(this.getDivByModlogGuid(this.coObj.guid));
  },

  getDivByModlogGuid: function(modlogGuid) {
    var node = $('#modlogs').get(0);
    var childNodes = node.childNodes;
    for(i = 0; i < childNodes.length; i++) {
      if (childNodes[i].id == 'modlog_' + modlogGuid) {
        return childNodes[i];
      }
    }
  }
};


$(window).resize(Modlog.resize); // adjust cover
$(document).keypress(Modlog.onkeypress); //ESC


/* Cookie handling
------------------------------------------------------- */

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

function readCookie(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 eraseCookie(name) {
  createCookie(name,"",-1);
};

// reads querystringified (a=1&b=2&c=...) value from
// cookie and adds/replaces a key/value pair
function setCookieListItem(name, key, value) {
  c = readCookie(name);
  if (c === null) {
    var cookieVal = key + '=' + value;
    createCookie(name,cookieVal,365);
  }
  else {
    var cookieValArr = [];
    var pair = c.split('&');
    var set = 0;
    for (i = 0; i < pair.length; i++) {
      if (pair[i].split('=')[0] == key) {
        set = 1;
        cookieValArr.push(pair[i].split('=')[0] + '=' + value);
      }
      else {
        cookieValArr.push(pair[i].split('=')[0] + '=' + pair[i].split('=')[1]);
      }
    }
    if (set == 0) {
      cookieValArr.push(key + '=' + value);
    }

    cookieVal = cookieValArr.join('&');
  }
  createCookie(name,cookieVal,365);
};

// turns a querystringified cookie value and
// turns it to an array
function getCookieListAsObj(name) {
  c = readCookie(name);
  if (c === null)
    return null;

  var obj={};
  var options=c.split('&');
  for (i = 0; i < options.length; i++) {
    obj[options[i].split('=')[0]] = options[i].split('=')[1];
  }
  return obj;
};

/* parse querystring and save params in "urlParams"
------------------------------------------------------- */
function setUrlParams() {
  var urlParams = {};
  var query = location.search.substring(1);
  var pairs = query.split("&");

  for (var i = 0; i < pairs.length; i++) {
    var pos = pairs[i].indexOf('=');
    if (pos == -1) continue;
    var argname = pairs[i].substring(0,pos);
    var value = pairs[i].substring(pos+1);
    urlParams[argname] = unescape(value);
  }

  global.urlParams = urlParams;
};


function getWindowInnerHeight() {
  // this one seems to work better than the thickbox implementation (20060908)
  if ('undefined' != typeof document.documentElement && document.documentElement.clientHeight > document.body.scrollHeight)
    return parseInt(document.documentElement.clientHeight,10);

  return parseInt(document.body.scrollHeight,10);
}

function doFocus() {
  if (typeof global.focusFieldId == 'undefined')
    return ;

  $('#' + global.focusFieldId).get(0).focus();
};

function delayedFocus(id) {
  // TODO: https://bugzilla.mozilla.org/show_bug.cgi?id=236791
  window.setTimeout('if ($("#' + id + '").get(0)) $("#' + id + '").get(0).focus();', 500);
};

function DOMNodeCreate(nodeName, html, attribs) {
  var node = document.createElement(nodeName);
  if (html)
    node.innerHTML = html;

  for (a in attribs)
    node.setAttribute(a, attribs[a]);

  return node;
};


/*****************************************************************************
 *
 * Views
 *
 ****************************************************************************/
 
 
/* Base
-------------------------------------------------------------------------- */

var ViewBase = {

  captchaInlay: {

    guid: 'base-captcha',
    url: '/fetch/base/captcha/captcha.html',
    doScrollToTop: function() {return false;}, // !

    onbeforeclose: function() {
      return confirm('You are about to close the dialog. The whole process will be cancelled! Please confirm.');
    }
  },

  notifyInlay: {

    guid: 'base-notify',
    doScrollToTop: function() {return false;}, // !

    onopen: function(params) {
      if ((params.alert ? params.alert : false)) {
        $('#modlog_' + ViewBase.notifyInlay.guid + '').css({'background':'url(/img/modlogCoverFailure.png)'});
        $('#modlog_' + ViewBase.notifyInlay.guid + ' .mlInner').css({'borderColor':'#C03'});
        $('#modlog_' + ViewBase.notifyInlay.guid + ' .mlHeader').css({'backgroundColor':'#C03'});
      }

      $('#mlTitle_'  + ViewBase.notifyInlay.guid).html(params.title);
      $('#mlBody_'  + ViewBase.notifyInlay.guid).html(params.text +
        '<div style="margin-top:20px"><input type="button" id="btn_' + this.guid + '" value=" ' + (params.btn ? params.btn : 'OK') + ' " onclick="Modlog.close(Modlog.CLOSE_MODE_CANCEL)" /></div>');
      delayedFocus('btn_' + this.guid);
    }
  }
}


/*****************************************************************************
 *
 * jquery extensions
 *
 ****************************************************************************/

jQuery.fn.blink=function(duration,times,callback){
  return this.each(function(){
    me = this;
    times = times * 2;
  
    blinkOn = function(){
      $(me).addClass('blink');
      
      if (--times > 0)
        setTimeout(blinkOff, duration);
      else
        if (callback) callback();
    };
    blinkOff = function(){
      $(me).removeClass('blink');
      
      if (--times > 0)
        setTimeout(blinkOn, duration);
      else
        if (callback) callback();        
    };
  
    blinkOn();
  });
};

jQuery.fn.cover = function(text) {
  return this.each(function() {
    //debugger;
    jq = $(this);
    jq.css({'position':'relative'});

    // get current z-index and add 1 to cover the actual element
    var z = parseInt(jq.css('zIndex')) + 1;
    var z = isNaN(z)?1:z;

    jq.prepend('<div class="cover" style="z-index:'+z+';display:block"></div>');
    if (text) jq.prepend('<div class="coverText" style="z-index:'+(z+1)+';display:block"><span class="loading">' + text + '</span></div>');
  });
};

jQuery.fn.uncover = function() {
  return this.each(function(){
    $(this).css({'position':'static'});
    $(this).find('div.coverText').remove().end().find('div.cover').remove().end();
  });
};


var loadScriptCache = [];
jQuery.fn.loadScript = function(src) {
  if (loadScriptCache.contains(src)) {
    // remove already loaded script from document
    $(this,document).find('script[@src="' + src + '"]').remove();
  }
  else
    loadScriptCache.push(src);
    
  var script = document.createElement('script');
  script.type = 'text/javascript';
  script.src = src;
  
  console.log('loadScript: ' + src);
  $(this,document).append(script);
};

var loadStyleCache = [];
jQuery.fn.loadStyle = function(href) {
  if (loadStyleCache.contains(href)) {
    // css files won't change and won't execute any dynamic code
    return ;
  }

  loadStyleCache.push(href);
    
  var script = document.createElement('link');
  script.type = 'text/css';
  script.rel  = 'stylesheet';
  script.href = href;
  
  console.log('loadStyle: ' + href);
  $(this,document).append(script);
};


// reloads image src
jQuery.fn.reload = function() {
  return this.each(function(){
    $(this).get(0).src = $(this).get(0).src + '?' + (new Date()).getTime();
  });
};

// cuts a text short and adds an expand/shrink link to it
jQuery.fn.cutShort = function(maxLength, moreCaption, lessCaption) {
  return this.each(function(){

    var text = $(this).html();
    if (text.length <= maxLength) 
      return this; // nothing to do

    if (typeof moreCaption == 'undefined') moreCaption = 'more »';;
    if (typeof lessCaption == 'undefined') lessCaption = '« less';;
  
    var visText = text.substring(0,maxLength);
    var hidText = text.substring(maxLength);

    var uniqid = Math.random() * Math.pow(10,17) + Math.random() * Math.pow(10,17)
               + Math.random() * Math.pow(10,17) + Math.random() * Math.pow(10,17);
    
    cutShort = // csH=Hidden,csT=Toggle
      visText 
      + '<span id="csH'+uniqid+'">' + hidText + '</span>'
      + ' <a class="inline" id="csT'+uniqid+'" href="javascript:;">' + moreCaption + '</a>';


    $(this).html(cutShort);
    $('#csH'+uniqid).css({'display':'none'});
    $('#csT'+uniqid).css({'whiteSpace':'nowrap'});
    $('#csT'+uniqid).click(function(){
      this.blur();
      var caption=($(this).html()==lessCaption)?moreCaption:lessCaption;
      $(this).html(caption);
      $('#csH'+uniqid).toggle();
    });
      
    return this;
  });
};

jQuery.fn.hintText = function(hintText) {
  return this.each(function(){
    if ($(this).val() === '') {
      $(this).val(hintText);
      $(this).addClass('hintText');
    }
    else {
      $(this).removeClass('hintText');
    }
    
    $(this).blur(function(){
      if ($(this).val() === '' || $(this).val() === hintText) {
        $(this).val(hintText);
        $(this).addClass('hintText');
      }
    });
    
    $(this).focus(function(){
      if ($(this).val() === hintText) {
        $(this).val('');
        $(this).removeClass('hintText');
      }
    });    
  });
}

/**
 * taken and modified from http://jquery.com/dev/svn/plugins/form/form.js
 *

 * This function gathers form element variables into an array that
 * is embedded into the current "this" variable as "this.vars". It
 * is normally used in conjunction with formdata() or ajaxSubmit() but can
 * be used standalone as long as you don't need the x and y coordinates
 * associated with an <input type="image"/> element..
 *
 * Standalone usage examples:
 *
 * 1. Gather form vars and return array to LHS variable.
 *    var myform = $('#form-id').serialize();
 *
 * 2. Provide a serialized URL-ready string (after 1. above).
 *    var mystring = $.param(myform.vars);
 *
 * 3. Gather form vars and send to RHS plugin via "this.vars".
 *    $('#form-id').serialize().some_other_plugin();
 *
 * @return   the jQuery Object
 * @return jQuery
 * @see      ajaxForm(), ajaxSubmit()
 * @author   Mark Constable (markc@renta.net)
 * @author   G. vd Hoven, Mike Alsup, Sam Collett, John Resig
 */
$.fn.serialize = function() {
  var a = [];
  var ok = {INPUT:true, TEXTAREA:true, OPTION:true};

  $('*', this).each(function() {
    var par = this.parentNode;
    var p = par.nodeName.toUpperCase();
    var n = this.name || p == 'OPTGROUP' && par.parentNode.name || p == 'SELECT' && par.name || this.id;

    if ( !n || this.disabled || this.type == 'reset' ||
      (this.type == 'checkbox' || this.type == 'radio') && !this.checked ||
      !ok[this.nodeName.toUpperCase()] ||
      (this.type == 'submit' || this.type == 'image') && this.form.clicked != this ||
      (p == 'SELECT' || p == 'OPTGROUP') && !this.selected ) return;

    a.push({name: n, value: this.value});
  });
  this.vars = a;

  return this;
};

if (typeof console == 'undefined') {
  // Try to be compatible with other browsers
  // Only use firebug logging when available
  console = new Object;
  console.trace = function() {};
  console.log = function() {};
  console.debug = function() {};
  console.info = function() {};
  console.warn = function() {};
  console.error = function() {};
  console.time = function() {};
  console.timeEnd = function() {};
  console.count = function() {};
};
