////////////////////////////////////////////////////////////////////////////////
// E262-3: 11.4.3

Global = this;

var $E = function(tagName, attributes) {
  result = window.document.createElement(tagName);
  if (!attributes) return result;
  attrs = $H(attributes);
  attrs.keys().each(function(v, i) {
    result.setAttribute(v, attributes[v]);
  });
  return result;
}

var $T = function(text) {
  return window.document.createTextNode(text);
}

var types = {
  Function: "function",
  Undefined: "undefined",
  Object: "object",
  Number: "number",
  Boolean: "boolean",
		    
  isFunction: function(v) {
    return (typeof v) == types.Function;
  },
  
  isObject: function(v) {
    return (typeof v) == types.Object;
  },
  
  isMap: function(v) {
    return (typeof v) == types.Function
      || (typeof v) == types.Object;
  },
  
  isEnumerable: function(v) {
    return types.isMap(v);
  },
  
  isNumber: function(v) {
    return (typeof v) == types.Number;
  },
  
  isBoolean: function(v) {
    return (typeof v) == types.Boolean;
  }
};

Object.extend(Event, {
  stopObservingElement: function(element) {
    var element = $(element);
    var parts = Event.observers.partition(
      function (v, i) { return v[0] === element; });
    Event.observers = parts[1];
    var removeList = parts[0];
    removeList.each(function (v, i) { Event.stopObserving.apply(null, v); });
  }
});

var klass = {};

var $chaining = function() {};
klass.extend = function(base) {
  if ((typeof base) != types.Function)
    throw new TypeError("klass.extend: invalid base");
  var ctor = function() {
    if (arguments.length == 1 
    && arguments[0] === undefined) {
      return;
    }
    this.initialize.apply(this, arguments);
  }
  ctor.prototype = new base(undefined);
  ctor.prototype.constructor = ctor;
  ctor.superClass = base.prototype;
  return ctor;
}

klass.create = function() {
  return klass.extend(Object);
}

klass.instanceOf = function(obj, typeCtor) {
  var ctor = obj.constructor;
  while (ctor != null) {
    if (ctor === typeCtor)
      return true;
    ctor = (ctor.superClass && ctor.superClass.constructor)
      ? ctor.superClass.constructor : null;
  }
  return false;
}

var Coords = klass.create();
Object.extend(Coords.prototype, {
  initialize: function() {
    if (arguments.length == 1 
      && types.isEnumerable(arguments[0])) {
        var coords = arguments[0];
        if (coords.length
          && coords.length == 2) {
            this.x = coords[0];
            this.y = coords[1];
        }
    } else if (arguments.length == 2) {
      this.x = arguments[0];
      this.y = arguments[1];
    } else {
      this.x = 0;
      this.y = 0;
    }
  },

  tuple: function() {
    return [this.x, this.y];
  }
});

var Action = klass.create();
Object.extend(Action.prototype, {
  initialize: function(label, action, imageUrl) {
    this.label = label;
    this.action = action;
    this.imageUrl = imageUrl || null;
  }
});

Popup = {};
Popup.centered = function(url, width, height) {
  var w = width || 300;
  var h = height || 100;
  var x = 0
  var y = 0
  x = x + (window.screen.width / 2) - (w / 2);
  y = y + (window.screen.height / 2) - (h / 2);
  window.open(url, "_blank",
    "resizable=0,"
    + "status=0,"
    + "modal=1,"
    + "dependant=1,"
    + "location=0,"
    + "menubar=0,"
    + "toolbar=0,"
    + "left=" + x + ","
    + "top=" + y + ","
    + "width=" + w + ","
    + "height=" + h);
}

function removeChildren(parent) {
  var children = $A(parent.childNodes);
  children.each(function (v, i) {
    if (v.childNodes) removeChildren(v);
    parent.removeChild(v);
  });
}

function removeContainer(container) {
  var parent = container.parentNode;
  removeChildren(container);
  if (parent != null) {
    parent.removeChild(container);
  }
}
var Request = klass.create();
Object.extend(Request.prototype, {
  initialize: function(type, url, query, callback, extra) {
  
    ////////////////////////////////
    // Normal request parameters
  
    this.callback = callback;
    this.query = query;
    this.url = url;
  
    ////////////////////////////////
    // Ajax.Request reference
  
    this.request = null;
  
    ////////////////////////////////
    // Optional context information
  
    this.extra = extra || null;
    this.requestType = type;

    ////////////////////////////////
    // Required for aborting
    // open and pending requests
  
    this.parentQueue = null;
    this.aborted = false;
    this.completed = false;
  },

  isOpen: function() {
    var res = false;
    if (this.request && this.request.transport) {
      var rs = this.request.transport.readyState;
      if ((rs != 0) && (rs != 4) && (!this.completed)) {
        res = true;
      }
    }
    return res;
  },

  abort: function() {
    if (this.aborted) { return; }

    //
    // If this request hasnt been
    // processed yet then we simply
    // flag it to not be processed
    // in the request queue.
    //
    if (this.isOpen()) {
      //
      ////////////////////////////////////////
      // If the request is currently open then 
      // it is at the front of the request 
      // queue.  We need to abort the transport
      // mechanism and tell the request queue 
      // that it needs to resume with, 
      // potentially, waiting requests sitting 
      // on the queue.
      //
      // Clear the function first, as Mozilla
      // sets the state to 4 then 0.
      //
      this.request.transport.onreadystatechange = Prototype.emptyFunction();
      this.request.transport.abort();
      this.request.transport = null;
      this.parentQueue.resume();
    }
    this.aborted = true;
  }
});

var RequestQueue = klass.create();
Object.extend(RequestQueue.prototype, {
  initialize: function() {
    this.entries = $A([]);
    this.currentRequest = null;
  },

  abortAll: function() {
    if (this.currentRequest)
      this.currentRequest.abort();
    this.entries.each(function (v, i) {
      v.abort();
    });
  },

  performRequest: function() {
    var top = this.entries.first();
    this.currentRequest = top;
    //
    // Skip requests that were marked
    // as aborted while sitting in the
    // pending queue.
    //
    // ???
    //
    // Should be skipping aborted 
    // requests that have been 
    // sitting on the pipe.
    // 
    while (this.currentRequest.aborted) {
      this.entries.shift();
      if (this.entries.length === 0) return;
      this.currentRequest = this.entries.first();
    }
    if (this.currentRequest.requestType == 'GET') {
      this.currentRequest.request = new Ajax.Request(top.url,
        {parameters: top.query, 
         onComplete: this.onComplete.bind(this)});
    } else /* POST */ {
      this.currentRequest.request = new Ajax.Request(top.url,
        {postBody: top.query, 
         onComplete: this.onComplete.bind(this)});
    }
  },

  onComplete: function(req, json) {
    try {
      //
      // make sure we keep processing
      // even if the callback had an
      // error
      //
      if (req.responseXML && !this.currentRequest.aborted) {
        this.currentRequest.callback(req, json, this.currentRequest.extra);
        this.currentRequest.completed = true;
      }
    } catch (e) {
      alert(e);
    }

    // Resume processing
    this.resume();
  },

  resume: function() {
    //
    // Pop the finished request off
    // and resume with processing
    // requests until we have finished
    // off all pending requests
    //
    // These need to execute in order
    // to keep the queue from blocking
    //
    this.entries.shift();
    if (this.entries.length > 0) {
      this.performRequest();
    }
  },

  sendRequest: function(entry) {
    //
    // Create a mutual reference
    //
    entry.parentQueue = this;
    if (this.entries.length === 0) {
      //
      // If there arent any pending
      // requests then we have to resume
      // the requst chain.
      //
      this.entries.push(entry);
      this.performRequest();
    } else {
      //
      // Otherwise we are inserting
      // the request into the pending
      // queue
      //
      this.entries.push(entry);
    }
  }
});

function fold_left(l, i, f) {
  var a = i;
  for (var idx = 0; idx < l.length; ++idx) {
    a = f(a, l[idx]);
  }
  return a;
}

var reduce = fold_left;

function add(l, r) {
  return l + r;
}
//
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
//
//
// Here we have a simple implementation
// of a coroutine wrapper class that
// we are going to call 'Tasklet'.
// Instead of implementing any type of
// scheduler we just simply rely on the
// OS for scheduling by exploiting the
// standard global method setTimeout().
// 
// The unit of work callable is bound
// to the current instance as this.unit
// 
var Tasklet = Class.create();
Object.extend(Tasklet.prototype, {
  initialize: function(callable, nice) {
    this.callable = callable;

    ///////////////////////////////
    // Mozilla javascript forces 
    // a minimum timeout of 10ms
    // (nsGlobalWindow.cpp:
    //     DOM_MIN_TIMEOUT_VALUE)
    // which is fine for original
    // intent but it would be nice
    // of there was a special case
    // for 0 meaning relenquish 
    // control to other scheduled
    // routines (callbacks sitting
    // in the timeout list) or the
    // event queue.
    this.nice = nice || 10;

    this.cancel = false;

    //
    // Bind this instances work unit
    //
    this.unit = this.task.bind(this);
  },

  run: function() {
    this.unit();
  },

  halt: function() {
    this.cancel = true;
  },

  resume: function() {
    this.cancel = false;
    this.unit();
  },

  task: function() {
    if (this.cancel) return;
    try {
      this.callable();
    } catch (e) {
      // catch errors and the special
      // $break object for normal
      // termination
      return;
    }

    // reschedule
    window.setTimeout(this.unit, this.nice);
  }
});

function tasklet(fun, timeout) {
  var task = new Tasklet(fun, timeout);
  task.run();
  return task;
}
//
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Valued checks
function isNull(value) {
  return (value == null);
}

function isBlank(value) {
  if(isNull(value)) {
    return true;
  }
  for(var i = 0; i < value.length; i++) {
    if((value.charAt(i) != ' ') && (value.charAt(i) != '\t') 
      && (value.charAt(i) != '\n') && (value.charAt(i) != '\r')) {
        return false;
    }
  }
  return true;
}

function isNaN(value) {
  return true;
}
// Valued checks end
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Cookie checks
var cookies = {};
cookies['cookiesEnabled'] = false;
cookies['cookieRead'] = false;
var login = {};
login['username'] = null;
login['password'] = null;

function cookieEnabled() {
  if(navigator.cookieEnabled == 0) 
    return false;
  return true;
}

function cookieSet() {
  if(isBlank(document.cookie)) {
    return false;
  }
  return true;
}

function setCookie(name, value, expire) {
  var expiration = new Date();
  expiration.setDate(expiration.getDate() + parseInt(expire));
  document.cookie = escape(name) + '=' + escape(value)
    + ((isBlank(expire)) ? '' : ';expires=' + expiration.toGMTString());
}

function getCookie(name) {
  var begin = document.cookie.indexOf(name + '=');
  if(begin != -1) { //Cookie exists
    begin += name.length + 1;
    var end = document.cookie.indexOf(';', begin);
    if(end == -1) {
      end = document.cookie.length;
    }
    return unescape(document.cookie.substring(begin, end));
  }
  return null;
}

function checkCookies() {
  if(!cookieEnabled()) { 
    cookies['cookiesEnabled'] = false;
  } else { 
    cookies['cookiesEnabled'] = true;
    if(!cookieSet()) { 
      cookies['cookieRead'] = false;
    } else { 
      var username = getCookie('username');
      var password = getCookie('password');
      if(isNull(username) || isNull(password)) { 
        cookies['cookieRead'] = false;
      } else { 
        cookies['cookieRead'] = true;
        login['username'] = username;
        login['password'] = password;
      }
    }
  }
}
// Cookie checks end
////////////////////////////////////////////////////////////////////////////////

var Overlay = klass.create();
//
// Overlays are modal so we are going
// to define singleton state and callback
// members for innerHTML components to
// point back to.
//
// May eventually want to hook resize 
// callbacks so that a popup overlay
// can be continually centered on the
// client area.
//
Object.extend(Overlay, {
  currentOverlay: null,
  Cancel: function() {
    if (Overlay.currentOverlay) {
      Overlay.currentOverlay.hide();
    }
  }
});

Object.extend(Overlay.prototype, {
  initialize: function(hash) {
    this.overlay = $E('div');
    this.overlay.setAttribute('id', 'overlay');
    this.overlay.style.position = 'absolute';
    this.overlay.style.height = '100%';
    this.overlay.style.width = '100%';
    this.overlay.style.zIndex = '3';
    //
    // Creating a new div for the non-opac
    // popup data 
    //
    this.container = $E('div');
    this.container.style.position = 'absolute';
    this.container.style.backgroundColor = 'white';
    this.container.style.border = 'solid 1px black';
    this.container.style.color = 'black';
    this.container.style.zIndex = '5';

    // Passed in
    this.overlay.style.backgroundColor = hash['backgroundColor'] || 'white';
    this.overlay.style.color = hash['color'] || 'black';
    this.opacityMin = hash['opacityMin'] || 0;
    this.opacityMax = hash['opacityMax'] || 100;
    this.opacityShift = hash['opacityShift'] || 10;
    // this.overlayData = hash['data'] || null;
    this.width = hash.width || 300;
    this.height = hash.height || 200;

    this.sizeAndPlace();
    this.eventHandler = this.resize.bindAsEventListener(this);

    Event.observe(window, 'resize', this.eventHandler);
    Event.observe(window, 'scroll', this.eventHandler);
  },

  destroy: function() {
    Event.stopObserving(window, 'resize', this.eventHandler);
    Event.stopObserving(window, 'scroll', this.eventHandler);
  },

  sizeAndPlace: function() {
    var left = (document.viewport.getScrollOffsets()).left;
    var top = (document.viewport.getScrollOffsets()).top;
    var width = document.body.clientWidth
                || window.innerWidth
                || document.documentElement.clientWidth;
    var height = document.body.clientHeight
                 || window.innerHeight
                 || document.documentElement.clientHeight;

    this.overlay.style.left = left + 'px';
    this.overlay.style.top = top + 'px';

    this.container.style.width = this.width + 'px';
    this.container.style.height = this.height + 'px';
    this.container.style.left = ((width / 2) + left - (this.width / 2)) + 'px';
    this.container.style.top = ((height / 2) + top - (this.height / 2)) + 'px';
  },

  resize: function() {
    this.sizeAndPlace();
  },

  show: function(opacity) {
    if(Prototype.Browser.IE) {
      this.overlay.style.filter = 'alpha(opacity=' + opacity + ')';
    }

    if(Prototype.Browser.Gecko) {
      this.overlay.style.MozOpacity = (opacity / 100);
    }

    if(Prototype.Browser.Opera) {
      this.overlay.style.opacity = (opacity / 100);
    }

    // this.overlay.innerHTML = this.overlayData;
    document.body.appendChild(this.overlay);
  },

  hide: function() {
    document.body.removeChild(this.overlay);	
    document.body.removeChild(this.container);	
    Overlay.currentOverlay = null;
  },

  _fadeIn: function() {
    if(this.opacity > this.opacityMax) {
      throw $break;
    }

    // Should this be wrapped in a browser check like 'show' above is?
    this.overlay.style.filter = 'alpha(opacity=' + this.opacity + ')';
    this.overlay.style.MozOpacity = (this.opacity / 100);
    this.overlay.style.opacity = (this.opacity / 100);
	
    // Fader for popup area
    var popupOpacity
      = (this.opacity / (this.opacityMax - this.opacityMin)) * 100;
    this.container.style.filter = 'alpha(opacity=' + popupOpacity + ')';
    this.container.style.MozOpacity = (popupOpacity / 100);
    this.container.style.opacity = (popupOpacity / 100);
	
    this.opacity += this.opacityShift;
  },

  fadeIn: function() {
    // this.opacity = this.opacityMin;
    // this.overlay.innerHTML = this.overlayData;
    this.opacity = this.opacityMin;
    // this.container.innerHTML = this.overlayData;
    document.body.appendChild(this.overlay);
    document.body.appendChild(this.container);
    Overlay.currentOverlay = this;
    this.faderTask = new Tasklet(this._fadeIn.bind(this), 20);
    this.faderTask.run();
  }
});

function fuzzyTime(datetime) {
  fuzzy = [{name: "Mor", range: ['03:00', '11:59']},
           {name: "Aft", range: ['12:00', '17:59']},
           {name: "Eve", range: ['18:00', '20:59']},
           {name: "Ngt", range: ['21:00', '23:59']},
           {name: "Ngt", range: ['00:00', '02:59']}];
  var lexComp = datetime.getHours().toPaddedString(2)
                + ':' + datetime.getMinutes().toPaddedString(2);
  var match = fuzzy.find(function (entry) {
    var begin = entry.range[0];
    var end = entry.range[1];
    if (lexComp <= end && lexComp >= begin) {
      return true;
    } else {
      return false;
    }
  });
  return match.name;
}

function dayOfWeekShort(datetime) {
  days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
  return days[datetime.getDay()];
}

function dayOfWeekLong(datetime) {
  days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
  return days[datetime.getDay()];
}

function monthShort(datetime) {
  months = ['Jan', 'Feb', 'Mar',
            'Apr', 'May', 'Jun',
            'Jul', 'Aug', 'Sep',
            'Oct', 'Nov', 'Dec'];
  return months[datetime.getMonth()];
}

function monthLong(datetime) {
  months = ['January', 'February', 'March',
            'April', 'May', 'June',
            'July', 'August', 'September',
            'October', 'November', 'December'];
  return months[datetime.getMonth()];
}

function dateDisplay(datetime) {
  var year = String(datetime.getFullYear());
  var month = monthShort(datetime);
  var date = datetime.getDate().toPaddedString(2);
  var day = dayOfWeekShort(datetime);
  var tod = fuzzyTime(datetime);
  return tod + ' ' + day + ' ' + month + ' ' + date + ' '  + year;
}

///
//
// The following date functions are from:
// http://delete.me.uk/2005/03/iso8601.html
// 
// Thanks to - Paul Sowden
//
///

Date.prototype.setISO8601 = function (string) {
  var regexp = "([0-9]{4})(-([0-9]{2})(-([0-9]{2})" +
               "(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?" +
               "(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?";
  var d = string.match(new RegExp(regexp));
  
  var offset = 0;
  var date = new Date(d[1], 0, 1);
  
  if (d[3]) { date.setMonth(d[3] - 1); }
  if (d[5]) { date.setDate(d[5]); }
  if (d[7]) { date.setHours(d[7]); }
  if (d[8]) { date.setMinutes(d[8]); }
  if (d[10]) { date.setSeconds(d[10]); }
  if (d[12]) { date.setMilliseconds(Number("0." + d[12]) * 1000); }
  if (d[14]) {
    offset = (Number(d[16]) * 60) + Number(d[17]);
    offset *= ((d[15] == '-') ? 1 : -1);
  }
  
  offset -= date.getTimezoneOffset();
  time = (Number(date) + (offset * 60 * 1000));
  this.setTime(Number(time));
}

Date.prototype.toISO8601String = function (format, offset) {
  /*
   * accepted values for the format [1-6]:
   * 1 Year:
   *   YYYY (eg 1997)
   * 2 Year and month:
   *   YYYY-MM (eg 1997-07)
   * 3 Complete date:
   *   YYYY-MM-DD (eg 1997-07-16)
   * 4 Complete date plus hours and minutes:
   *   YYYY-MM-DDThh:mmTZD (eg 1997-07-16T19:20+01:00)
   * 5 Complete date plus hours, minutes and seconds:
   *   YYYY-MM-DDThh:mm:ssTZD (eg 1997-07-16T19:20:30+01:00)
   * 6 Complete date plus hours, minutes, seconds and a decimal
   *   fraction of a second
   *   YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00)
   */
  if (!format) { var format = 6; }
  if (!offset) {
    var offset = 'Z';
    var date = this;
  } else {
    var d = offset.match(/([-+])([0-9]{2}):([0-9]{2})/);
    var offsetnum = (Number(d[2]) * 60) + Number(d[3]);
    offsetnum *= ((d[1] == '-') ? -1 : 1);
    var date = new Date(Number(Number(this) + (offsetnum * 60000)));
  }
  
  var zeropad = function (num) { return ((num < 10) ? '0' : '') + num; }
  
  var str = "";
  str += date.getUTCFullYear();
  if (format > 1) { str += "-" + zeropad(date.getUTCMonth() + 1); }
  if (format > 2) { str += "-" + zeropad(date.getUTCDate()); }
  if (format > 3) {
    str += "T" + zeropad(date.getUTCHours()) +
           ":" + zeropad(date.getUTCMinutes());
  }
  if (format > 5) {
    var secs = Number(date.getUTCSeconds() + "." +
               ((date.getUTCMilliseconds() < 100) ? '0' : '') +
               zeropad(date.getUTCMilliseconds()));
    str += ":" + zeropad(secs);
  } else if (format > 4) { str += ":" + zeropad(date.getUTCSeconds()); }
  
  if (format > 3) { str += offset; }
  return str;
}

var Colors = [
  '#FF0000',
  '#00FF00',
  '#0000FF',
  '#FF00FF',
  '#00FFFF',
  '#FFFF00'
];

var ColorSelectHelper = klass.create();
Object.extend(ColorSelectHelper.prototype, {
  initialize: function(parent, container, color) {
    this.parent = parent;
    this.container = container;
    this.color = color;
    Event.observe(this.container, 'click',
                  this.colorSelection.bindAsEventListener(this));
  },

  colorSelection: function() {
    this.parent.colorSelected(this.color);
  }
});

var ColorSelect = klass.create();
Object.extend(ColorSelect.prototype, {
  initialize: function(parent, x, y, currentColor) {
    this.parent = parent;
    this.x = x; 
    this.y = y;
    this.helpers = [];
    this.div = null;
    this.color = null;
    this.currentColor = currentColor;
    this.buildSelection();
  },

  colorSelected: function(color) {
    this.parent.setColor(color);
    document.body.removeChild(this.div);
  },

  buildSelection: function() {
    self = this;
    var div = $E('div');
    this.div = div;
    div.style.position = 'absolute';
    div.style.top = this.y + 'px';
    div.style.left = this.x + 'px';
    div.style.backgroundColor = 'white';
    div.style.margin = '0';
    div.style.padding = '0';
    div.style.borderTop = '1px solid black';
    document.body.appendChild(div);
    var table = $E('table');
    table.style.borderCollapse = 'collapse';
    div.appendChild(table);
    var tbody = $E('tbody');
    table.appendChild(tbody);

    Colors.partition(function (m) {return self.currentColor == m}).flatten().each(function (n) {
      var row = $E('tr');
      tbody.appendChild(row);
      var cell = $E('td');
      cell.style.backgroundColor = n;
      cell.style.width = '18px';  //was 16 but seems like the one
      cell.style.height = '18px'; //coming across is bigger
      cell.style.borderBottom = '1px solid black';
      cell.style.borderLeft = '1px solid black';
      cell.style.borderRight = '1px solid black';
      row.appendChild(cell);
      self.helpers.push(new ColorSelectHelper(self, cell, n));
    });
  }
});

function showCommentBlock(shower, focuser) {
  $(shower).style.display = 'inline';
  $(focuser).focus();
}

function hideCommentBlock(hider) {
  $(hider).style.display = 'none';
}
// vim: set sts=2 sw=2 expandtab:
