// Implements indexes on grouped items.
// Needs to target other frames.
// Needs safeguards:
// - do not add a group item before checking existance of item
// - do not roll an item before checking existance

// ========== Browser Detection Code ==========

// what platform the user is using
roIsWinOS = (navigator.appVersion.indexOf("Windows") != -1) ? true : false;
roIsMacOS = (navigator.appVersion.indexOf("Macintosh") != -1) ? true : false;

// what browser the user is using
roVerInt = parseInt(navigator.appVersion.substring(0,1));
roUsingMSIE = (navigator.appVersion.indexOf("MSIE") == -1) ? false : true;
roUsingMSIE3 = (roUsingMSIE && (roVerInt == 2 || roVerInt == 3)) ? true : false;
roUsingMSIE4 = (roUsingMSIE && roVerInt == 4) ? true : false;
roUsingMSIE5 = (roUsingMSIE && roVerInt == 5) ? true : false;
roUsingNN = (navigator.appName.indexOf("Netscape") == -1) ? false : true;
roUsingNN2 = (roUsingNN && roVerInt == 2) ? true : false;
roUsingNN3 = (roUsingNN && roVerInt == 3) ? true : false;
roUsingNN4 = (roUsingNN && roVerInt == 4) ? true : false;
roUsingNN5 = (roUsingNN && roVerInt == 5) ? true : false;

// what technologies the browser implements
roImplementsVB = ((roUsingMSIE && roIsWinOS) || (roIsMacOS && roUsingMSIE3)) ? true : false;  // VBScript
roImplementsImgObj = document.images ? true : false;  // Image Object
roImplementsMSDOM = document.all ? true : false;  // Microsoft Document Object Model
roImplementsNSDOM = document.layers ? true : false;  // Netscape Document Object Model

// ========== Rollover Code ==========

function RolloverObject (arg_path, arg_ovrExt) {
  // Data - private
  this.srcPath = (arg_path == null ? "./" : arg_path);  // virtual path to images
  this.overExt = (arg_ovrExt == null ? "1" : arg_ovrExt);  // the filename extender that indicates an "on" image
  this.hashOvers = new roEmptyHash();  // all sources of  "on" images: imageId => {seqIndex => source}
  this.hashOuts = new roEmptyHash();   // all sources of "off" images: imageId => {seqIndex => source}
  this.hashGroups = new roEmptyHash();  // all groups of images: groupId => {imageId => seqIndex}
  this.hashLocked = new roEmptyHash();  // list of images with a locked "out" source: imageId => source
  this.hasGroups = false;  // indicates if any groups exist -- some javascrit code can be ignored if no groups exist
  this.bgBlank = null;  // replaces images with this intermediate source (for blanking purposes) before placing the over or out source
  // Methods - private
  this.getNextItemIndex = roGetNextItemIndex;  // calculates the next available index (slot) for an image source
  this.roll = roRoll;  // performs image replacement -- as called by on and off methods
  this.replace = roReplace;  // raw, basic, single-source replacement
  // Methods - public
  this.add = roAddItem;  // add image source
  this.lock = roLockItem;  // lock the out source of an image
  this.group = roGroupItems;  // group images
  this.preload = roPreload;  // preload images
  this.on = roOn;  // turn on images and groups of images
  this.off = roOff;  // turn off images and groups of images
  this.showGroup = roShowGroup;  // debug: display group insides
  this.showOvers = roShowOvers;  // debug: display all "on" sources
  this.showOuts = roShowOuts;  // debug: display all "off" sources
}

// Calculates an id for the next item in the specified list.
// An item may contain multiple sources; these sources are index from zero through n.
// Item indexing stars at 0;
function roGetNextItemIndex(iid) {
  var srcCtr = 0;
  for (var szSrc in this.hashOvers[iid])
    srcCtr = srcCtr + 1;
  return srcCtr;
}

// Adds sources for an image to the over and out hashes.
// Multiple sources can be added under the same id in the case an image needs to be replaced by more than one source.
// Source indexing is automatic; sources are indexed based on the order they are received.
// When adding a source to an unlocked image:
//  -specified source is "out" image
//  -"on" source is calculated
// When adding a source to a Locked image:
//  -"out" source is retrieved from the "locked" hash
//  -specified source is "on" image
function roAddItem(arg_iid, arg_file, arg_path, arg_ovrExt) {
  // exit if no item id or file was specified
  if (arg_iid == null || arg_file == null) return null;
  // for this id create a source hash if none exists
  if (roCountMembers(this.hashOvers[arg_iid]) == 0) {
    this.hashOvers[arg_iid] = new roEmptyHash();
    this.hashOuts[arg_iid] = new roEmptyHash();
  }
  // create over and out paths for this item
  var idxNext = this.getNextItemIndex(arg_iid);
  var baseFile = (arg_path ? arg_path : this.srcPath) + (arg_file);
  if (this.hashLocked[arg_iid] != null) {  // Locked
    this.hashOuts[arg_iid][idxNext] = this.hashLocked[arg_iid];
    this.hashOvers[arg_iid][idxNext] = baseFile;
  }
  else { // Not locked
    this.hashOuts[arg_iid][idxNext] = baseFile;
    var pieces = roParseFileSpec(this.hashOuts[arg_iid][idxNext]);
    this.hashOvers[arg_iid][idxNext] = pieces["path"] + pieces["file"] + (arg_ovrExt ? arg_ovrExt : this.overExt) + pieces["extension"];
  }
  // return the item id added
  return arg_iid;
}

// Locks the "out" version of an image to the specified source, even though multiple "over" sources may be active.
// Replaces all "out" sources of the specified id with the specified source.
// Updates the hash of locked images.
// NOTE: See roAddItem for a description of adding sources to a locked image.
function roLockItem(arg_iid, arg_file, arg_path) {
  var theFile = (arg_path ? arg_path : this.srcPath) + arg_file;
  this.hashLocked[arg_iid] = theFile;
  for (var idx in this.hashOuts[arg_iid])
    this.hashOuts[arg_iid][idx] = theFile;
}

// Groups images. All images in a group will be affected together by the "on" and "off" methods.
// Images that do not exist are ignored; they are not added to the group.
// A numerical index following an image indicates the source index to group.
function roGroupItems() {  // arguments: expect a group id followed by a list of item ids (and optional indexes)
  if (roUsingMSIE3 == true)
    return;
  this.hasGroups = true;
  // get group id
  var gid;
  if (roGroupItems.arguments.length > 0)
    gid = roGroupItems.arguments[0];
  else
    return;
  // create this group if it doesn't exist
  if (roCountMembers(this.hashGroups[gid]) == 0)
    this.hashGroups[gid] = new roEmptyHash();
  // add each item (and optional index position -- defaults to 0) to this group
  var arg, iidSave;
  var seekIdx = false; // expect an item before an index
  for (var ctr = 1; ctr < roGroupItems.arguments.length; ctr++) {
    arg = roGroupItems.arguments[ctr];
    if (seekIdx == true) {  // seeking an index for the item on hold
      if (roIsNumeric(arg) == true) {  // this is an index
        // save the item on hold with this index
        this.hashGroups[gid][iidSave] = arg;
        iidSave = null;
        // one index can not be followed by another
        seekIdx = false;
      }
      else {  // this is not an index, but an item
        // save the item on hold (with default index)
        this.hashGroups[gid][iidSave] = 0;
        iidSave = null;
        // place this item on hold (may be followed by an index)
        iidSave = arg;
        // seekIdx remains true (for this item)
      }
    }
    else {  // expecting an item (not an index)
      // place this item on hold (may be followed by an index)
      iidSave = arg;
      seekIdx = true;
    }
  } //end-for
  // save item on hold (if one remains)
  if (iidSave != null) {
    this.hashGroups[gid][iidSave] = 0;
    iidSave = null;
  }
}

// Show Roll Over Sources -- debug
function roShowOvers() {
  var msg = "NAME\tINDEX : SOURCE";
  for (var list in this.hashOvers) {
    msg = msg + "\n" + list;
    for (var item in this.hashOvers[list]) {
      msg = msg + "\n\t" + item + " : " + this.hashOvers[list][item];
    }
  }
  window.alert(msg);
}

// Show Roll Out Sources -- debug
function roShowOuts() {
  var msg = "NAME\tINDEX : SOURCE";
  for (var list in this.hashOuts) {
    msg = msg + "\n" + list;
    for (var item in this.hashOuts[list]) {
      msg = msg + "\n\t" + item + " : " + this.hashOuts[list][item];
    }
  }
  window.alert(msg);
}

// Show items and indexes of a group -- debug
function roShowGroup(grp) {
  var msg = "";
  for (var i in this.hashGroups[grp])
    msg = msg + "\n" + i + ": " + this.hashGroups[grp][i];
  window.alert("Group '" + grp + "':\n" + msg);
}

// Tells if the specified argument is a populated object.
function roCountMembers(argAnything) {
  var ctr = 0;
  for (var i in argAnything) ctr++;
  return ctr;
}

// Tells if the specified argument is a number.
function roIsNumeric(argAnything) {
  return ((argAnything + 0) == argAnything);
}

// For the specific purpose of having a completely empty object.
// NOTE: Arrays are not instantiated empty (see length property).
function roEmptyHash() {
  // populate me
}

// Initiates roll Over of the specified images or groups of images.
// If multiple images exist under the same id, you may specify the index of the desired image by
// following the id argument with a numeric argument inidicating the index position; otherwise the
// first image is returned.
function roRoll() { // arguments: expect a roll direction followed by a list of item ids
  // grab immediate arguments.
  var hashRoll = roRoll.arguments[0];  // rollover hash to use
  var aryArgs = roRoll.arguments[1];  // an argument array from roOn() or roOff()
  var numArgs = roRoll.arguments[2];  // the number of argments

  //var numArgs = roOn.arguments.length; #trash
  var item, iidSave;
  var seekIdx = false;  // expect an image item before an index
  // loop over the items (which may be ids or indexes)
  for (var ctr = 0; ctr < numArgs; ctr++) {
    item = aryArgs[ctr];

    if (this.hasGroups == true && this.hashGroups[item] != null) {  // render the group item
      // display the on hold item (because no index is coming)
      if (iidSave != null) {
        this.replace(iidSave, hashRoll[iidSave][0], self)
        iidSave = null;
      }
      // display each group item (at group-specified index value)
      for (var id in this.hashGroups[item]) {
        this.replace(id, hashRoll[id][this.hashGroups[item][id]], self);
      }
      // the first item after a group can not be an index
      seekIdx = false;
    }
    else { // render the single (non-group) item
      // Are we seeking the index of an item or an item?
      if (seekIdx ==  true) {  // seeking an index for the item on hold
        if (roIsNumeric(item) == true) {  // this item is an index
          // display the item on hold for this index
          this.replace(iidSave, hashRoll[iidSave][item], self);
          iidSave = null;
          // the next item will not be an index because one index can not be followed by another
          seekIdx = false;
        }
        else {  // this item is not an index
          // display the item on hold by defaulting to the first index
          this.replace(iidSave, hashRoll[iidSave][0], self);
          // put this item on hold for the desired index
          iidSave = item;
          // seekIdx remains true for this image item
        }
      }
      else {  // not seeking an index meaning this item is an image
        // put this item on hold in case it is followed by an index
        iidSave = item;
        // prepare to seek the index of this image in the next item
        seekIdx = true;
      }
    }
  } // end-for
  // display item on hold if one remains
  if (iidSave != null) {
    this.replace(iidSave, hashRoll[iidSave][0], self);
    iidSave = null;
  }
}

function roOn() {
  if (roUsingMSIE3 == true)
    return;
  if (roUsingNN3 == true) {
    var roAryNN3 = new Array(roOn.arguments.length);
    for (var i = 0; i < roOn.arguments.length; i++) {
      roAryNN3[i] = roOn.arguments[i];
    }
  }
  this.roll(this.hashOvers, (roUsingNN3 == true ? roAryNN3 : roOn.arguments), roOn.arguments.length);
}

function roOff() {
  if (roUsingMSIE3 == true)
    return;
  if (roUsingNN3 == true) {
    var roAryNN3 = new Array(roOff.arguments.length);
    for (var i = 0; i < roOff.arguments.length; i++) {
      roAryNN3[i] = roOff.arguments[i];
    }
  }
  this.roll(this.hashOuts, (roUsingNN3 == true ? roAryNN3 : roOff.arguments), roOff.arguments.length);
}

// Performs actual image replacement.
function roReplace(iNm, sSrc, oPane) {
  if (roUsingMSIE3 == true)
    return;
  if (this.bgBlank != null)
    oPane.document.images[iNm].src = this.bgBlank;
  oPane.document.images[iNm].src = sSrc;
}

// Preload secondary rollover imagesj only.
// Specify item ids or groups to preload; otherwise all secondary images are preloaded.
function roPreload() {
  if (roUsingMSIE3 == true)
    return null;
  var imgLoader = new roEmptyHash();
  var itemlist, item, imgId;
  //iterate through hashOvers
  for (itemlist in this.hashOvers) {
    for (item in this.hashOvers[itemlist]) {
      imgId = itemlist + "." + item;
      imgLoader[imgId] = new Image();
      imgLoader[imgId].src = this.hashOvers[itemlist][item];
    }
  }
  return imgLoader;
}

// Parses the specified file spec into pieces: path, filename, file extension. No separator characters are dropped.
function roParseFileSpec(fileSpec) {
  pieces = new roEmptyHash();
  // Parse path.
  pos = fileSpec.lastIndexOf("/");
  if (pos == -1)  // then no path specified
    pieces["path"] = "";
  else
    pieces["path"] = fileSpec.substring(0, pos + 1);
  // Parse full file name.
  fullfile = fileSpec.substring(pos + 1);
  // Parse full file name.
  pos = fullfile.lastIndexOf(".");
  pieces["file"] = fullfile.substring(0, pos);
  pieces["extension"] = fullfile.substring(pos);
  return pieces;
}
