/*==================================================
  = Archive Item Object
  ==================================================*/
function archiveItemObj(el)
{
  /* The DOM element that contains this content */
  this.element = el;

  /* The title is used to sort a group of archive items */
  this.title = this.getTitle();

  /* Comma-separated list of categories */
  this.category = this.getCategory();

  /* The inner HTML of the archive item */
  this.contentHTML = this.getInnerHTML();

  /* The inner text of the archive item - used for searching */
  this.contentText = getInnerText(el);
  this.contentText = this.contentText.replace(/\s+/g, ' ');
}

archiveItemObj.prototype.getTitle = function()
{
  /* Returns the text of the first H2 heading in the archive item */
  var h2 = this.element.getElementsByTagName("H2");
  if (h2) {
    return getInnerText(h2.item(0));
  } else {
    return "";
  }

}

archiveItemObj.prototype.getCategory = function()
{
  /* Returns the text within <DIV class="archiveCategory"> within the
     archive item, presumably a comma-separated list of category
     names */
  var div = getElementsByClass("archiveCategory",this.element,'*')[0];
  if (div) {
    var category = getInnerText(div);
    if (category) {
      return category;
    }
  }
  return "Uncategorized";
}

archiveItemObj.prototype.getInnerHTML = function()
{
  var len = 200;

  /* Truncate the description text and add an ellipses that expands
     the text back to its origial size.

     NOTE: the paragraph must only contain plain text and no HTML or
     this code might break the HTML.
  */
  
  /* Find the first P element in the archive item */
if (false) {
  var ps = this.element.getElementsByTagName("P");
  if (ps && ps[0] && ps[0].innerHTML) {
    var p = ps[0];
    var trunc = p.innerHTML;
    if (trunc.length > len) {

      /* Truncate the content of the P, then go back to the end of the
	 previous word to ensure that we don't truncate in the middle
	 of a word */
      trunc = trunc.substring(0, len);
      trunc = trunc.replace(/[\W\s]*\w+$/, '');

      /* Add an ellipses to the end and make it a link that expands
	 the paragraph back to its original size */
      trunc += '<a href="#" onclick="this.parentNode.innerHTML=unescape(\''+escape(p.innerHTML)+'\');return false;">...<\/a>';
      p.innerHTML = trunc;
    }
  }
}
  
  return '<div class="archiveItem">'+this.element.innerHTML+'<\/div>';
}

/*==================================================
  = Archive Object
  ==================================================*/
function archiveObj(argsObj)
{
  /* Constructor for an archive object.
     Requires the following external functions:
     function $()
     function getElementsByClass()
     function lowerCaseCompare()
   */

  /* Get the DOM elements that we will use */
  this.results = $(argsObj.dataId);
  this.message = $(argsObj.messageId);
  this.categorySelect = $(argsObj.categorySelectId);
  this.searchInput = $(argsObj.searchInputId);

  /* Array for the archive items */
  this.items = [];

  /* Associative array for the categories. Each item in this array is
     an array of items for that category. */
  this.categories = {};

  /* Call the init method */
  this.init();
}

archiveObj.prototype.init = function()
{
  /* Get all of the <DIV class="archiveItem"> elements */
  var itemElementArray = getElementsByClass('archiveItem', this.results);
  if (!itemElementArray || itemElementArray.length < 1) {
    /* Throw an error an die if we couldn't find anything */
    throw("Cannot find any archiveItem elements");
  }

  /* Loop through the archive items:
     - set up the archiveItem objects
     - set up the category array
  */
  for (var i in itemElementArray) {

    /* Create a new archiveItem object */
    var item = new archiveItemObj(itemElementArray[i]);

    /* Skip this one if we were not able to get the title */
    if (!item.title) {
      continue;
    }

    /* Add the object to the array of items */
    var end = this.items.length;
    this.items[end] = item;

    /* Determine the categories for this item, then add the item to
       the list for that category. */
    var categoryArray = item.category.split(',');
    for (var iCategory in categoryArray) {

      var categoryName = categoryArray[iCategory];

      /* Remove leading and training whitespace */
      categoryName = categoryName.replace(/^\s+|\s$/, '');

      /* If necessary, create the category */
      if (!this.categories[categoryName]) {
	this.categories[categoryName] = new Array();
      }

      /* Add this archiveItem to the category */
      var end = this.categories[categoryName].length;
      this.categories[categoryName][end] = item;

    }

  }

  /* Set up the category select options, and make the first category
     be selected.
  */

  /* First we must sort the category names */
  var categoryArray = [];
  for (var categoryName in this.categories) {
    categoryArray[ categoryArray.length ] = categoryName;
  }
  categoryArray = categoryArray.sort(lowerCaseCompare);

  /* Remove all the options and add one for all categories */
  this.categorySelect.options.length = 0;
  this.categorySelect.options[0] = new Option("All Projects", "", false);

  /* Now add the category names to the categorySelect list */
  for (var i=0; i<categoryArray.length; i++) {
    this.categorySelect.options[ this.categorySelect.options.length ] =
      new Option(categoryArray[i],categoryArray[i],false);
  }

  /* Select the "All Projects" category */
  this.categorySelect.selectedIndex = 0;

  /* Display the selected category */
  this.displayCategory();

  /* Add the user interface controls */
  this.categorySelect.onchange = function(){ this.displayCategory(); return false; };
}

archiveObj.prototype.displayAll = function(msg)
{
  this.categorySelect.options[0].selected = true;
  this.displayCategory(msg);
}

archiveObj.prototype.displayCategory = function()
{
  /* Get the category name */
  var category = this.categorySelect.options[ this.categorySelect.selectedIndex ].value;
  if (category) {
    var count = this.categories[category].length;
    this.display(this.categories[category], category + ' ('+count+')');
  } else {
    var count = this.items.length;
    this.display(this.items, 'All Projects ('+count+')');
  }
}

archiveObj.prototype.display = function(itemArray, msg)
{
  var sortedArray;

  if (!msg) { msg=""; }
  
  /* Sort the itemArray */
  if (itemArray.length > 1) {
    sortedArray = itemArray.sort(function(aObj,bObj){
      var a,b;
      a = aObj.title.toLowerCase();
      b = bObj.title.toLowerCase();
      if (a>b) return 1;
      if (a<b) return -1;
      return 0;
    } );
  } else {
    sortedArray = itemArray;
  }

  /* Display the msg */
  this.message.innerHTML = msg;

  var s = "";
  for (var i=0; i < sortedArray.length; i++) {
    s += sortedArray[i].contentHTML;
  }
  this.results.innerHTML = s;

  /* Add the onmouseover event */
  var itemElementArray = getElementsByClass('archiveItem', this.results);
  for (var i in itemElementArray) {
    itemElementArray[i].onmouseover = function(){this.style.backgroundColor='#e6f1f7';};
    itemElementArray[i].onmouseout = function(){this.style.backgroundColor='';};
    itemElementArray[i].onclick = function(){location=this.getElementsByTagName('a')[0].href;};
  }
}

archiveObj.prototype.search = function()
{
  /* Set the category select list to "All Projects" */
  this.categorySelect.selectedIndex = 0;

  /* Get the search keywords */
  var keywords = this.searchInput.value;

  /* Clean the search keywords and split into an array */

  /* Remove any non-alphanumeric characters */
  keywords = keywords.replace(/\W/g, ' ');

  /* Replace multiple spaces with a single space */
  keywords = keywords.replace(/\s+/g, ' ');

  /* Make all characters lower case */
  keywords = keywords.toLowerCase();

  /* Replace the search input with the cleaned up keywords so the user
     knows what was actually searched for */
  this.searchInput.value = keywords;

  /* If the keywords are blank, display all the entries and return */
  if (keywords.match(/^\s*$/)) {
    this.displayAll();
    return;
  }

  /* Split the keywords into an array so we can search on each one
     individually */
  var keywordArray = keywords.split(' ');

  /* Now we'll find the matches for each keyword.
     Only when an item matches EVERY keyword do we consider it a hit.
   */
  var hits = [];

  /* Loop through the keywords */
  for (var i=0; i < keywordArray.length; i++) {

    /* Create a regular expression for this keyword */
    var kRegExp = new RegExp(/*'\\b'+*/keywordArray[i]/*+'\\b'*/, 'gi');

    /* If this is the first keyword we'll search all of the items.
       For subsequent keywords we'll search only the items
       that were hits for previous keywords.
    */
    if (i==0) { searchIndexArray = this.items; }
    else { searchIndexArray = hits }

    for (var iItem in searchIndexArray) {

      /* var item = this.items[iItem]; */
      var item = searchIndexArray[iItem];

      if (item.contentText.match(kRegExp)) {

	/* A HIT: add to the hits list. If this is the first keyword
	 we'll add the item to the hits list, otherwise the hit is
	 already in the list so we don't have to do anything. */

	if (i==0) {
	  hits[hits.length] = item;
	}

      } else {

	/* NO HIT: remove from the hits list. If this is the first
	   keyword, then the item won't be in the hits list so we
	   don't have to do anything. */

	if (i!=0) {
	  delete hits[iItem];
	}

      }
    }
  }

  /* For multiple keywords, there might be undefined entries in the
     hits array, so we must remove them. */
  var finalHits = new Array();
  for (var i in hits) {
    if (hits[i]) {
      finalHits[finalHits.length] = hits[i];
    }
  }

  var msg = '';

  /* Make sure we don't break the display if keywords is too long -
     truncate it and add an ellipsis if necessary. */
  if (keywords.length > 16) {
    keywords = keywords.substring(0,15) + '...';
  }

  if (finalHits.length == 1) {

    msg = 'We found 1 result for "' + keywords + '":';

  } else if (finalHits.length > 0) {

    msg = 'We found ' + finalHits.length +
    ' results for "' + keywords + '":';

  } else {

    msg = 'Your search for "'+keywords+'" did not match any projects.';

  }

  this.display(finalHits, msg);
}

/*==================================================
  = Support functions
  ==================================================*/
function $()
{
  /* Replacement for document.getElementById() */
  var elements = new Array();

  for (var i = 0; i < arguments.length; i++) {
    var element = arguments[i];
    if (typeof element == 'string')
      element = document.getElementById(element);

    if (arguments.length == 1)
      return element;

    elements.push(element);
  }

  return elements;
}

function getElementsByClass(searchClass,node,tag)
{
  var classElements = new Array();
  if (node == null)
    node = document;
  if (tag == null)
    tag = '*';
  var els = node.getElementsByTagName(tag);
  var elsLen = els.length;
  var pattern = new RegExp("(^|\\s)"+searchClass+"(\\s|$)");
  var i,j;
  for (i = 0, j = 0; i < elsLen; i++) {
    if (pattern.test(els[i].className) ) {
      classElements[j] = els[i];
      j++;
    }
  }
  return classElements;
}

function getInnerText(el)
{
  if (!el) { return ""; }
  if (typeof el == "string") { return el; }
  if (typeof el == "undefined") { return ""; };
  if (el.innerText) { return el.innerText; };

  var str = "";

  var cs = el.childNodes;
  var l = cs.length;
  for (var i = 0; i < l; i++) {
    switch (cs[i].nodeType) {
    case 1: //ELEMENT_NODE
      str += this.getInnerText(cs[i]);
      break;
    case 3: //TEXT_NODE
      str += cs[i].nodeValue;
      break;
    }
  }
  return str;
}

function lowerCaseCompare(a, b)
{
  var aLC = a.toLowerCase();
  var bLC = b.toLowerCase();
  if (aLC > bLC) { return 1; }
  if (aLC < bLC) { return -1; }
  return 0;
}


/*==================================================
  = Create the archive object after the page finishes loading
  ==================================================*/

var archive = new archiveObj({
  'messageId':'archiveMsg',
    'dataId':'archiveDataReplace',
    'categorySelectId':'archiveControlCategorySelect',
    'searchInputId':'archiveControlSearchKeywords'
    });

/* Attach behaviors to the interface controls */

/*
$('archiveControlCategoryTrigger').onclick =
function() {archive.displayCategory();return false;}
*/

$('archiveControlCategorySelect').onchange =
function() {archive.displayCategory();}

$('archiveControlSearchTrigger').onclick =
function() {archive.search();return false;}

$('archiveControlAllTrigger').onclick =
function() {archive.displayAll();return false;}

