// -------------------------------------------------------------------
// dicemachine.js
// 
// This is the JavaScript code for the dicemachine.html page.
// 
// Copyright 2004 by Brian Douglas Skinner <skinner@gumption.org>
// -------------------------------------------------------------------

// -------------------------------------------------------------------
// Calls all the functions that do initialization when the page is 
// first loaded.
// -------------------------------------------------------------------
function onLoad() {
  externalLinks();
  setGlobalVariables();
  redisplayPage();
}

function onWindowResize() {
  // displayDebugText("window resize!");
  // PENDING -- scale fonts
  redisplayTurnScroller();
}

// -------------------------------------------------------------------
// Initializes global variables.
// Sets global variables to point to elements in the HTML document
// -------------------------------------------------------------------
function setGlobalVariables() {
 
  // variables used in the "logical model"
  turnArray = new Array();
  turnArray[0] = new Turn();
  clearRollHistory();

  // constants -- CSS style colors
  COLOR_RED = "rgb(100%,0%,0%)";  // COLOR_RED = "#FF0000";
  COLOR_TUMBLE_GRAY = "#666666";
  COLOR_IGNORE_ME_GRAY = "#CCCCCC";
  COLOR_DARK_GRAY = "rgb(30%,30%,30%)";
  COLOR_BLACK = "rgb(0%,0%,0%)";
  
  // constants
  MENU_BORDER_WIDTH = "2px";
  // ONE_HUNDRED_PERCENT = 100;
  BULLET_POINT = "="; // would like this to be "&#9830;", but it shows up as &#9830;, rather than a bullet point
  INITIAL_BAR_GRAPH_WIDTH_IN_TURNS = 8;
  NUM_TUMBLES = 5;

  // HTML elements for the menus
  elementMenuBar             = document.getElementById("menuBar");
  elementMenuBarBlankSpace   = document.getElementById("menuBarBlankSpace");
  elementMenuItemDiceMachine = document.getElementById("menuItemDiceMachine");
  elementMenuItemPlayers     = document.getElementById("menuItemPlayers");
  
  elementMenuItemDiceMachine.onmouseover = mouseOverMenuItem;
  elementMenuItemDiceMachine.onmouseout = mouseOutMenuItem;
  elementMenuItemDiceMachine.onmousedown = mouseDownMenuItem;
  elementMenuItemPlayers.onmouseover = mouseOverMenuItem;
  elementMenuItemPlayers.onmouseout = mouseOutMenuItem;
  elementMenuItemPlayers.onmousedown = mouseDownMenuItem;

  // HTML elements on the left side of the page
  elementCurrentRoll         = document.getElementById("currentRoll");
  elementRollDiceButton      = document.getElementById("rollDice");
  elementTurnCounter         = document.getElementById("turnCounter");
  elementOuterTurnScrollWell = document.getElementById("outerTurnScrollWell");
  elementInnerTurnScrollWell = document.getElementById("innerTurnScrollWell");
  elementTurnScrollGrip      = document.getElementById("turnScrollGrip");
  elementRollCustomButton    = document.getElementById("rollCustom");
  elementCustomRollMax       = document.getElementById("customRollMax");
  elementCustomRoll          = document.getElementById("customRoll");

  document.body.onkeydown = keyDownOnBody;
  elementRollDiceButton.onclick = clickOnRollDiceButton;
  elementRollDiceButton.onkeydown = rollDiceKeyDown;
  elementTurnScrollGrip.onmousedown = mouseDownOnTurnScroller;
  elementTurnScrollGrip.onmousemove = mouseMoveOnTurnScroller; 
  elementTurnScrollGrip.onmouseup = mouseUpOnTurnScroller;
  elementRollCustomButton.onclick = clickOnRollCustomButton;
  elementRollCustomButton.onkeydown = rollCustomKeyDown;
  elementCustomRollMax.onkeydown = maxRollKeyDown;
  
  // variables used to keep track of the UI state
  idOfOpenSubMenu = null;
  customMaxRollFieldCapturedEvent = null;
  rollDiceButtonCapturedEvent = null;
  rollCustomButtonCapturedEvent = null;
  isMouseDownOnGrip = false;
  selectedCell = null; // PENDING: HACK -- this should be a property of a subMenu, not a global variable
  makeNoteOfScrollWellWidth();
  buildSubMenus();

  // HTML elements for the histogram bars
  bar = new Array(13);
  bar[2]  = document.getElementById("bar2");
  bar[3]  = document.getElementById("bar3");
  bar[4]  = document.getElementById("bar4");
  bar[5]  = document.getElementById("bar5");
  bar[6]  = document.getElementById("bar6");
  bar[7]  = document.getElementById("bar7");
  bar[8]  = document.getElementById("bar8");
  bar[9]  = document.getElementById("bar9");
  bar[10] = document.getElementById("bar10");
  bar[11] = document.getElementById("bar11");
  bar[12] = document.getElementById("bar12");
}

// -------------------------------------------------------------------
// Set global variables to reflect the scroll well dimensions
// -------------------------------------------------------------------
function makeNoteOfScrollWellWidth() {
  // var borderWidth = 1;
  // scrollWellMin = getOffsetLeftFromElement(elementTurnScrollWell) + borderWidth;
  // scrollWellWidth = elementTurnScrollWell.offsetWidth - borderWidth - borderWidth;
  scrollWellMin = getOffsetLeftFromElement(elementInnerTurnScrollWell);
  scrollWellWidth = elementInnerTurnScrollWell.offsetWidth - 2; // shouldn't need the fudge constant 2, but in Mozilla the dimensions are off without it 
}

// -------------------------------------------------------------------
// Constructor for Turn object. Creates a new Turn and initializes it.
// -------------------------------------------------------------------
function Turn(previousTurn) {
  if (previousTurn == null) {
    this.turnNumber = 0; 
    this.diceRollThisTurn = 0;  // roll on two dice -- a number from 2 to 12
    this.numberOfPlayers = 0;
    this.numberOfTurnsSkipped = 0;
  } else {
    this.turnNumber = previousTurn.turnNumber + 1;
    this.diceRollThisTurn = getRandomDiceRoll();
    this.numberOfPlayers = previousTurn.numberOfPlayers;
    this.numberOfTurnsSkipped = previousTurn.numberOfTurnsSkipped;
  }
}

// -------------------------------------------------------------------
// Initializes (or re-initializes) the variables that keep track of
// the dice rolls.
// -------------------------------------------------------------------
function clearRollHistory() {
  totalTurnCount = 0;
  currentTurn = turnArray[0];
  selectedTurn = currentTurn;
  
  var elementDebugOutput = document.getElementById("debugOutput");
  elementDebugOutput.value = "";
}

// -------------------------------------------------------------------
// Updates the global turn variables so that it's as if the last turn
// never happened.
// -------------------------------------------------------------------
function undoLastRoll() {
  if (totalTurnCount > 0) {
    totalTurnCount--;
    currentTurn = turnArray[totalTurnCount];
    selectedTurn = currentTurn;
  }
}

// -------------------------------------------------------------------
// Sets the numberOfPlayers variable, and updates the display to 
// reflect the new value.
// -------------------------------------------------------------------
function setNumberOfPlayers(n) {
//  var oldElement = document.getElementById("menuDot" + currentTurn.numberOfPlayers);
//  var newElement = document.getElementById("menuDot" + n);
//  oldElement.textContent = "";
//  newElement.textContent = BULLET_POINT;
  
  currentTurn.numberOfPlayers = n;
  redisplayPage();
}

// -------------------------------------------------------------------
// Returns a integer between 2 and 12, the sum of two 6-sided dice.
// -------------------------------------------------------------------
function getRandomDiceRoll() {
  var dieOne = Math.floor(Math.random()*6)+1;
  var dieTwo = Math.floor(Math.random()*6)+1;
  return (dieOne + dieTwo);
}

// -------------------------------------------------------------------
// Rolls a random number (2 to 12), updates the global variables, 
// and updates everything on the display page.
// -------------------------------------------------------------------
function rollDiceOnce() {
  currentTurn = new Turn(currentTurn);
  totalTurnCount++;
  turnArray[totalTurnCount] = currentTurn;
  selectedTurn = currentTurn;
  
  elementCustomRoll.style.color = COLOR_IGNORE_ME_GRAY;
  elementCurrentRoll.style.color = COLOR_RED;
  redisplayPage();
}

// -------------------------------------------------------------------
// Called when the user clicks on the rollDice button.
// -------------------------------------------------------------------
function clickOnRollDiceButton(eventObject) {
  rollDiceWithTumbleEffect();
}

// -------------------------------------------------------------------
// Called when the user clicks on the rollCustom button.
// -------------------------------------------------------------------
function clickOnRollCustomButton(eventObject) {
  rollCustomDieWithTumbleEffect();
}


// -------------------------------------------------------------------
// Displays a split-second "animation" of a half-dozen greyed-out 
// values, cycling through the currentRoll text field.  Then calls 
// rollDiceOnce() to get the final roll and update the display.
// -------------------------------------------------------------------
function rollDiceWithTumbleEffect(numTumblesLeft) {
  closeSubMenu(idOfOpenSubMenu);
  if (!numTumblesLeft) numTumblesLeft = NUM_TUMBLES;
  if (numTumblesLeft == 1) {
    showTumbleRoll();
    rollDiceOnce();
  } else {
    showTumbleRoll();
    numTumblesLeft -= 1;
    var nextFunctionCall = "rollDiceWithTumbleEffect(" + numTumblesLeft + ")";
    setTimeout(nextFunctionCall, 70);
  }
}

// -------------------------------------------------------------------
// Update the currentRoll text field to show a single "frame" of the
// dice tumbling "animation".
// -------------------------------------------------------------------
function showTumbleRoll() {
  var tumbleRoll = getRandomDiceRoll();
  elementCurrentRoll.innerHTML = tumbleRoll;
  elementCurrentRoll.style.color = COLOR_TUMBLE_GRAY;
}

// -------------------------------------------------------------------
// Makes 100 calls to rollDiceOnce().
// -------------------------------------------------------------------
function rollDice100(n) {
  if (n > 0) {
    rollDiceOnce();
    var nextFunctionCall = "rollDice100(" + (n - 1) + ")";
    setTimeout(nextFunctionCall, 5);
  }
}

// -------------------------------------------------------------------
// Displays a split-second "animation" of a half-dozen greyed-out 
// values, cycling through the customRoll text field.  Then calls 
// rollDiceOnce() to get the final roll and update the display.
// -------------------------------------------------------------------
function rollCustomDieWithTumbleEffect(numTumblesLeft) {
  if (!numTumblesLeft) numTumblesLeft = NUM_TUMBLES;
  var n = elementCustomRollMax.value; 
  closeSubMenu(idOfOpenSubMenu);
  if (!((n > 0) && (n < 1000))) {  // PENDING -- we really just want to make sure n is an integer
    return;
  }
  if (numTumblesLeft == 1) {
    showTumbleCustomRoll();
    rollCustomDieOnce();
  } else {
    showTumbleCustomRoll();
    numTumblesLeft -= 1;
    var nextFunctionCall = "rollCustomDieWithTumbleEffect(" + numTumblesLeft + ")";
    setTimeout(nextFunctionCall, 70);
  }
}

// -------------------------------------------------------------------
// Update the currentRoll text field to show a single "frame" of the
// dice tumbling "animation".
// -------------------------------------------------------------------
function showTumbleCustomRoll() {
  var n = elementCustomRollMax.value; 
  var tumbleRoll = Math.floor(Math.random()*n)+1;
  elementCustomRoll.innerHTML = tumbleRoll;
  elementCustomRoll.style.color = COLOR_TUMBLE_GRAY;
}

// -------------------------------------------------------------------
// Roll a number between 1 and N, where N is the value in the input 
// field "customRollMax"
// -------------------------------------------------------------------
function rollCustomDieOnce() {
  var n = elementCustomRollMax.value; 
  elementCustomRoll.innerHTML = Math.floor(Math.random()*n)+1;
  elementCustomRoll.style.color = COLOR_RED;
  elementCurrentRoll.style.color = COLOR_IGNORE_ME_GRAY;
}

// -------------------------------------------------------------------
// Called whenever the user types a key on the keyboard, regardless
// of where the current focus is.
// -------------------------------------------------------------------
function keyDownOnBody(eventObject) {
  if (!eventObject) var eventObject = window.event;

  var focusInCustomMaxField = (customMaxRollFieldCapturedEvent == eventObject);
  var focusInRollDiceButton = (rollDiceButtonCapturedEvent == eventObject);
  var focusInRollCustomButton = (rollCustomButtonCapturedEvent == eventObject);

  var asciiValueOfKey = getAsciiValueOfKeyFromEvent(eventObject);
  closeSubMenu(idOfOpenSubMenu);
  if (asciiValueOfKey == 13) {
    // the user pressed the "Enter" key
    if (!focusInCustomMaxField && !focusInRollDiceButton && !focusInRollCustomButton) { 
      rollDiceWithTumbleEffect();
    }
    return;
  }
  if (!focusInCustomMaxField) {
    var number = -1;
    if ((asciiValueOfKey >= 48) && (asciiValueOfKey <= 57)) {
      number = asciiValueOfKey - 48;
    }
    if ((asciiValueOfKey >= 96) && (asciiValueOfKey <= 105)) {
      number = asciiValueOfKey - 96;
    }
    if (number != -1) {
      elementCustomRollMax.value = number;
      rollCustomDieWithTumbleEffect();
    }
    if (asciiValueOfKey == 67) {
      // user pressed the "c" key (or alt-c, or shift-c, or cntl-c)
      rollCustomDieWithTumbleEffect();
      return;
    }
  }
}

// -------------------------------------------------------------------
// Called when the user types a key on the keyboard while the focus
// is in the customMaxRoll text field.
// -------------------------------------------------------------------
function maxRollKeyDown(eventObject) {
  if (!eventObject) var eventObject = window.event;
  var asciiValueOfKey = getAsciiValueOfKeyFromEvent(eventObject);

  customMaxRollFieldCapturedEvent = eventObject; 

  closeSubMenu(idOfOpenSubMenu);
  if (asciiValueOfKey == 13) {
    // the user pressed the "Enter" key
    rollCustomDieWithTumbleEffect();
  }
  return false;
}

// -------------------------------------------------------------------
// Called when the user types a key on the keyboard while the focus
// is on the rollDice button.
// -------------------------------------------------------------------
function rollDiceKeyDown(eventObject) {
  if (!eventObject) var eventObject = window.event;
  rollDiceButtonCapturedEvent = eventObject; 
}

// -------------------------------------------------------------------
// Called when the user types a key on the keyboard while the focus
// is on the rollCustom button.
// -------------------------------------------------------------------
function rollCustomKeyDown(eventObject) {
  if (!eventObject) var eventObject = window.event;
  rollCustomButtonCapturedEvent = eventObject; 
}

// -------------------------------------------------------------------
// Called when there's a mouseDown event on the turnScrollGrip
// -------------------------------------------------------------------
function mouseDownOnTurnScroller(eventObject) {
  if (!eventObject) var eventObject = window.event;
  isMouseDownOnGrip = true;
  makeNoteOfScrollWellWidth();
}

// -------------------------------------------------------------------
// Called when there's a mouseMove event on the turnScrollGrip
// -------------------------------------------------------------------
function mouseMoveOnTurnScroller(eventObject) {
  if (!eventObject) var eventObject = window.event;
  if (isMouseDownOnGrip == true) {
    var currentX = (getPageXFromEvent(eventObject) - scrollWellMin);
    var turnNumberToSelect = 0;
    if (currentX <= 1) {
      turnNumberToSelect = 1;
    }
    if (currentX >= scrollWellWidth) {
      turnNumberToSelect = totalTurnCount;
    }
    if (turnNumberToSelect == 0) {
      turnNumberToSelect = Math.floor((currentX / scrollWellWidth) * totalTurnCount)+1;
    }
    if (turnNumberToSelect != selectedTurn.turnNumber) {
      selectedTurn = turnArray[turnNumberToSelect];
      redisplayPage(); 
    }
  }
}

// -------------------------------------------------------------------
// Called when there's a mouseUp event on the turnScrollGrip
// -------------------------------------------------------------------
function mouseUpOnTurnScroller(eventObject) {
  if (!eventObject) var eventObject = window.event;
  isMouseDownOnGrip = false;
  redisplayPage();
}

// -------------------------------------------------------------------
// Updates the HTML element for the turnScrollGrip on the display page
// to reflect the values in the global variables.
// -------------------------------------------------------------------
function redisplayTurnScroller() {
  if (totalTurnCount < 2) {
    elementOuterTurnScrollWell.style.visibility = "hidden";
  } else {
    makeNoteOfScrollWellWidth();
    var gripWidthInPixels;
    var turnWidthInPixels;
    if (totalTurnCount < 30) {
      gripWidthInPixels = scrollWellWidth / totalTurnCount;  // if there have been 5 turns, this turn takes 20% of the scroll bar
      turnWidthInPixels = gripWidthInPixels;
    } else {
      gripWidthInPixels = scrollWellWidth / 30;
      turnWidthInPixels = (scrollWellWidth - gripWidthInPixels) / (totalTurnCount - 1);
    }
    var widthOfPreviousTurns = (selectedTurn.turnNumber - 1) * turnWidthInPixels; // how much of the bar do all previous turns take up
    elementTurnScrollGrip.style.marginLeft = widthOfPreviousTurns + "px"; 
    elementTurnScrollGrip.style.width = gripWidthInPixels + "px";

    if (totalTurnCount < 5) {
      elementTurnScrollGrip.value = "turn " + selectedTurn.turnNumber;
    } else {
      if (totalTurnCount < 10) {
        elementTurnScrollGrip.value = selectedTurn.turnNumber;
      } else {
        elementTurnScrollGrip.value = "";
      }
    }
    elementOuterTurnScrollWell.style.visibility = "visible";
  }
}

// -------------------------------------------------------------------
// Updates all the HTML elements on the display page to reflect the 
// values in the global variables.
// -------------------------------------------------------------------
function redisplayPage() {
  var menuBarHeight = getMenuItemById("menuItemPlayers").offsetHeight;
  elementMenuBar.style.height = menuBarHeight + "px";
  elementMenuBarBlankSpace.style.height = menuBarHeight + "px";
  
  var turnNumberText = "turn " + selectedTurn.turnNumber;
  var playerNumber = 0;
  if ((selectedTurn.numberOfPlayers > 0) && (selectedTurn.turnNumber > 0)) {
    playerNumber = (((selectedTurn.turnNumber-1)+selectedTurn.numberOfTurnsSkipped)%(selectedTurn.numberOfPlayers))+1;
    turnNumberText += " (player " + playerNumber + ")";
  }
  elementCurrentRoll.innerHTML = selectedTurn.diceRollThisTurn;
  elementTurnCounter.innerHTML = turnNumberText;
  
  redisplayTurnScroller();
  
  var maxBar = INITIAL_BAR_GRAPH_WIDTH_IN_TURNS;
  var dataHistogram = new Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
  for (var i=1; i<=selectedTurn.turnNumber; i++) {
    var diceRoll = turnArray[i].diceRollThisTurn;
    dataHistogram[diceRoll] += 1;
  }
  for (var i=2; i<=12; i++) {
    bar[i].innerHTML = dataHistogram[i];
    maxBar = Math.max(maxBar, dataHistogram[i]);
    if (i == selectedTurn.diceRollThisTurn) {
      bar[i].style.backgroundColor = COLOR_RED;
    } else {
      if (i%2 == 0) {
        bar[i].style.backgroundColor = COLOR_BLACK;
      } else {
        bar[i].style.backgroundColor = COLOR_DARK_GRAY;
      }
    }
  }
  for (var i=2; i<=12; i++) {
    bar[i].style.width = (dataHistogram[i]/maxBar)*100 + "%";
    bar[i].style.visibility = ((dataHistogram[i] == 0) ? "hidden" : "visible"); 
  }
}

// -------------------------------------------------------------------
// Take an HTML document <div> element for a main menu item, and 
// change the border styling so that it looks inset, outset, or plain.
// -------------------------------------------------------------------
function setBorderStyleForMenuItem(menuItem, borderStyle) {
  if (menuItem != null) {
    switch (borderStyle) {
      case "outset":
        menuItem.style.borderStyle = "outset";
        menuItem.style.borderWidth = MENU_BORDER_WIDTH;
        menuItem.style.borderColor = "#ffffff #000000 #000000 #ffffff";
        break;
      case "inset":
        menuItem.style.borderStyle = "inset";
        menuItem.style.borderWidth = MENU_BORDER_WIDTH;
        menuItem.style.borderColor = "#000000 #ffffff #ffffff #000000";
        break;
      case "plain":
        menuItem.style.borderStyle = "solid";
        menuItem.style.borderWidth = MENU_BORDER_WIDTH;
        menuItem.style.borderColor = "#cccccc";
        break;
    }
  }
}

// -------------------------------------------------------------------
// Return the HTML document <div> element for a main menu item 
// (class="mainMenuItem") with a given id.
// -------------------------------------------------------------------
function getMenuItemById(name) {
  return document.getElementById(name);
}

// -------------------------------------------------------------------
// Return the HTML document <div> element for a sub menu (class="subMenu") 
// with a given "name" (where "name" just means the name we're using in 
// this code, rather than an HTML attribute called "name")
// -------------------------------------------------------------------
function getSubMenuByName(name) {
  switch (name) {
    case "menuItemDiceMachine":
      return document.getElementById("subMenuDiceMachine");
      break;
    case "menuItemPlayers":
      return document.getElementById("subMenuPlayers");
      break;
  }
  // return document.getElementById("subMenu" + name);
}

// -------------------------------------------------------------------
// Called when the mouse moves over a main menu item.  Update the 
// border to give the user feedback.
// -------------------------------------------------------------------
function mouseOverMenuItem(eventObject) {
  var divId = this.id;
  setBorderStyleForMenuItem(getMenuItemById(divId), "outset");
}

// -------------------------------------------------------------------
// Called when the mouse moves away from a main menu item.  Update the 
// border to give the user feedback.
// -------------------------------------------------------------------
function mouseOutMenuItem(eventObject) {
  var divId = this.id;
  if (idOfOpenSubMenu != divId) {
    setBorderStyleForMenuItem(getMenuItemById(divId), "plain");
  }
}

// -------------------------------------------------------------------
// Called when the user clicks on one of the main menu items.
// If this item's sub-menu is already open, then close it.  Otherwise, 
// if this item's sub-menu is closed, then open it (after first closing
// whatever other sub-menu is currently open).
// -------------------------------------------------------------------
function mouseDownMenuItem(eventObject) {
  var divId = this.id;
  var menuItemIsOpen = (idOfOpenSubMenu == divId);
  if (menuItemIsOpen) {
    closeSubMenu(divId);
  } else {
    closeSubMenu(idOfOpenSubMenu);
    openSubMenu(divId);
  }
}

// -------------------------------------------------------------------
// Given the name of one of the two sub-menus, close the sub-menu.
// -------------------------------------------------------------------
function closeSubMenu(idOfMenu) {
  if (idOfMenu != null) {
    if (idOfOpenSubMenu == idOfMenu) {
      var menuItem = getMenuItemById(idOfMenu);
      var subMenu = getSubMenuByName(idOfMenu);
      subMenu.style.visibility = "hidden";
      subMenu.style.display = "none";
      setBorderStyleForMenuItem(menuItem, "plain");
      idOfOpenSubMenu = null;
    }
  } 
}

// -------------------------------------------------------------------
// Given the name of one of the two sub-menus, open up the sub-menu.
// -------------------------------------------------------------------
function openSubMenu(idOfMenu) {
  var menuItem = getMenuItemById(idOfMenu);
  var subMenu = getSubMenuByName(idOfMenu);

  // set-up the subMenu to open just below the menuItem it comes from
  var subMenuTop = getOffsetTopFromElement(menuItem) + menuItem.offsetHeight + 1;
  var subMenuLeft = getOffsetLeftFromElement(menuItem);
  subMenu.style.top = subMenuTop + "px"; 
  subMenu.style.left = subMenuLeft + "px"; 

  subMenu.style.visibility = "visible";
  subMenu.style.display = "block";
  setBorderStyleForMenuItem(menuItem, "inset");
  idOfOpenSubMenu = idOfMenu;
}

// -------------------------------------------------------------------
// Called when the user selects any of the menu items in either of
// the sub-menus.  Figure out which menu item was chosen, and then
// do the corresponding action.
// -------------------------------------------------------------------
function mouseDownSubMenuItem(name) {
  closeSubMenu(idOfOpenSubMenu);
  switch (name) {
    case "About DiceMachine...":
      alert("Dice Machine\n\nThis is a program that rolls a pair of 6-sided dice and graphs the distribution of the rolls. " + 
            "This DiceMachine is designed to be useful when playing the board game Settlers of Catan, although you could use it with other board games too." +
            "The original DiceMachine program was written in Java by Nick Parlante around the turn of the century (between 2000 and 2003)." +
            "This JavaScript version of the DiceMachine was based on Nick's Java version, and was written by Brian Skinner in 2004." + 
            "For more information about the Java version, goto: http://www-cs-students.stanford.edu/~nick/dice/");
      break;
    case "Undo last roll":
      undoLastRoll();
      redisplayPage();
      break;
    case "Clear All":
      clearRollHistory();
      redisplayPage();
      break;
    case "Roll 100 times":
      rollDice100(100);
      break;
    case "Quit":
      var response = confirm("Do you really want to quit?");
      if (response) {
        location.href = "http://www.google.com/";
      }
      break;
    case "Never mind":
      setNumberOfPlayers(0);
      break;
    case "2":
      setNumberOfPlayers(2);
      break;
    case "3":
      setNumberOfPlayers(3);
      break;
    case "4":
      setNumberOfPlayers(4);
      break;
    case "5":
      setNumberOfPlayers(5);
      break;
    case "6":
      setNumberOfPlayers(6);
      break;
    case "7":
      setNumberOfPlayers(7);
      break;
    case "8":
      setNumberOfPlayers(8);
      break;
    case "Skip":
      currentTurn.numberOfTurnsSkipped++;
      redisplayPage();
      break;
    default:
      alert(name);
      break;
  }
}

// -------------------------------------------------------------------
// PENDING
// -------------------------------------------------------------------
function buildSubMenus() {
  var playersSubMenu = new SubMenu("subMenuPlayers");
  playersSubMenu.addSubMenuItem("Never mind", true, true);
  playersSubMenu.addSubMenuItem("2", true);
  playersSubMenu.addSubMenuItem("3", true);
  playersSubMenu.addSubMenuItem("4", true);
  playersSubMenu.addSubMenuItem("5", true);
  playersSubMenu.addSubMenuItem("6", true);
  playersSubMenu.addSubMenuItem("7", true);
  playersSubMenu.addSubMenuItem("8", true);
  playersSubMenu.addSubMenuItem("Skip");
  playersSubMenu.appendToDocument();  

  var diceMachineSubMenu = new SubMenu("subMenuDiceMachine");
  diceMachineSubMenu.addSubMenuItem("About DiceMachine...");
  diceMachineSubMenu.addSubMenuItem("Undo last roll");
  diceMachineSubMenu.addSubMenuItem("Clear All");
  diceMachineSubMenu.addSubMenuItem("Roll 100 times");
  diceMachineSubMenu.addSubMenuItem("Quit");
  diceMachineSubMenu.appendToDocument();  
}


// ===================================================================
// MENU FUNCTIONS
//   |
//   |
//   V

// -------------------------------------------------------------------
// Constructor for SubMenu object. Creates a new SubMenu and 
// initializes it.
// -------------------------------------------------------------------
function SubMenu(idAttribute) {
  
  // Properties
  this.idAttribute = idAttribute;
  this.classAttribute = "subMenu";
  this.subMenuItems = new Array();
  this.subMenuItems = new Array();
  
  // Methods
  this.addSubMenuItem = addSubMenuItem;
  this.appendToDocument = appendToDocument;
  
}

// -------------------------------------------------------------------
// Adds a subMenuItem to this SubMenu object
// -------------------------------------------------------------------
function addSubMenuItem(stringValue, isSelectable, isSelected) {
  var subMenuItem = new Object;
  subMenuItem.label = stringValue;
  subMenuItem.selectable = (isSelectable ? true : false);
  subMenuItem.selected = (isSelected ? true : false);
  this.subMenuItems.push(subMenuItem);
}

// -------------------------------------------------------------------
// Builds the HTML to represent this SubMenu object.
// -------------------------------------------------------------------
function appendToDocument() {
  var outerDiv = document.createElement('div');
  var arrayOfStrings = new Array();
  arrayOfStrings.push("<div id=\"" + this.idAttribute + "\" class=\"" + this.classAttribute + "\">\n");
  arrayOfStrings.push("  <table class=\"menuTable\" cellspacing=\"0\">\n");
  for (var i=0; i<this.subMenuItems.length; i++) {
    var ifSelected = (this.subMenuItems[i].selected ? "=" : "");
    arrayOfStrings.push("    <tr id=\"" + this.idAttribute + "_subMenuItem" + i + "\" class=\"subMenuItem\" menuLabel=\"" + this.subMenuItems[i].label + "\" selectable=\"" + this.subMenuItems[i].selectable + "\"><td>" + ifSelected + "</td><td><a>" + this.subMenuItems[i].label + "</a></td></tr>\n");
  }
  arrayOfStrings.push("  </table>\n");
  arrayOfStrings.push("</div>\n");
  var finalString = arrayOfStrings.join("");
  document.body.appendChild(outerDiv);
  outerDiv.innerHTML = finalString;
  
  for (var i=0; i<this.subMenuItems.length; i++) {
    var subMenuElement = document.getElementById(this.idAttribute + "_subMenuItem" + i);
    subMenuElement.onmousedown = mouseDownOnSubMenuItem;
    subMenuElement.onmouseover = mouseOverOnSubMenuItem;
    subMenuElement.onmouseout = mouseOutOnSubMenuItem;
    if (this.subMenuItems[i].selected) {
      selectedCell = subMenuElement.firstChild;
    }
  }

}

// -------------------------------------------------------------------
// Call when user clicks on a generated subMenuItem.
// -------------------------------------------------------------------
function mouseDownOnSubMenuItem(eventObject) {
  var isSelectable = this.getAttribute("selectable");
  if (isSelectable == "true") {
    if (selectedCell != null) selectedCell.innerHTML = "";
    var firstCell = this.firstChild;
    firstCell.innerHTML = "=";
    selectedCell = firstCell;
  }
  this.style.background = "#cccccc";
  this.style.color = "#000000";
  mouseDownSubMenuItem(this.getAttribute("menuLabel"));
}

// -------------------------------------------------------------------
// Call when user hovers over a generated subMenuItem.
// -------------------------------------------------------------------
function mouseOverOnSubMenuItem(eventObject) {
  this.style.background = "#000d77";
  this.style.color = "#ffffff";
}

// -------------------------------------------------------------------
// Call when user stops hovering over a generated subMenuItem.
// -------------------------------------------------------------------
function mouseOutOnSubMenuItem(eventObject) {
  this.style.background = "#cccccc";
  this.style.color = "#000000";
}

// ===================================================================
// RE-USABLE HELPER FUNCTIONS
//   |
//   |
//   V


// -------------------------------------------------------------------
// Given a keyDown event object, returns the ascii value of the key
// that was pressed.  Should work for IE, Mozilla, and _some_ other 
// browsers.
// -------------------------------------------------------------------
function getAsciiValueOfKeyFromEvent(eventObject) {
  var asciiKeyCode = null;
  if (eventObject.keyCode) {
    asciiKeyCode = eventObject.keyCode;
  } else {
    if (eventObject.which) { 
      asciiKeyCode = eventObject.which; 
    }
  }
  // var character = String.fromCharCode(code);
  // alert('Character was ' + character);
  return asciiKeyCode;
}

// -------------------------------------------------------------------
// Given a mouseMoved event object, returns the (x) part of the (x, y)
// mouse coordinates relative to the document.
// -------------------------------------------------------------------
function getPageXFromEvent(eventObject) {
  var xCoordinate = 0;
  if (!eventObject) var eventObject = window.event;
  if (eventObject.pageX) {
    xCoordinate = eventObject.pageX;
  } else {
    if (eventObject.clientX) {
      xCoordinate = eventObject.clientX + document.body.scrollLeft;
    }
  }
  return xCoordinate;
}

// -------------------------------------------------------------------
// Given a mouseMoved event object, returns the (y) part of the (x, y)
// mouse coordinates relative to the document.
// -------------------------------------------------------------------
function getPageYFromEvent(eventObject) {
  var yCoordinate = 0;
  if (!eventObject) var eventObject = window.event;
  if (eventObject.pageY) {
    yCoordinate = eventObject.pageY;
  } else {
    if (eventObject.clientY) {
      yCoordinate = eventObject.clientY + document.body.scrollTop;
    }
  }
  return yCoordinate;
}

// -------------------------------------------------------------------
// Given an HTML element, find the real left offset for the element,
// meaning the distance in pixels from the left edge of the page.
// -------------------------------------------------------------------
function getOffsetLeftFromElement(htmlElement) {
  var cumulativeOffset = 0;
  if (htmlElement.offsetParent) {
    while (htmlElement.offsetParent) {
      cumulativeOffset += htmlElement.offsetLeft
      htmlElement = htmlElement.offsetParent;
    }
  } else {
    if (htmlElement.x) cumulativeOffset += htmlElement.x;
  }
  return cumulativeOffset;
}

// -------------------------------------------------------------------
// Given an HTML element, find the real top offset for the element,
// meaning the distance in pixels from the top edge of the page.
// -------------------------------------------------------------------
function getOffsetTopFromElement(htmlElement) {
  var cumulativeOffset = 0;
  if (htmlElement.offsetParent) {
    while (htmlElement.offsetParent) {
      cumulativeOffset += htmlElement.offsetTop
      htmlElement = htmlElement.offsetParent;
    }
  } else {
    if (htmlElement.y) cumulativeOffset += htmlElement.y;
  }
  return cumulativeOffset;
}

// -------------------------------------------------------------------
// Finds all the HTML <a> link elements that include the attribute
// rel="external", and sets them up so that when they are clicked on
// the linked page opens in a new window.
// -------------------------------------------------------------------
function externalLinks() {
  if (!document.getElementsByTagName) return;
  var anchors = document.getElementsByTagName("a");
  for (var i=0; i<anchors.length; i++) {
    var anchor = anchors[i];
    if (anchor.getAttribute("href") && anchor.getAttribute("rel") == "external")
      anchor.target = "_blank";
  }
}

// -------------------------------------------------------------------
// Given an object, get all the values of all the object's properties,
// and display them in the debug <textarea> at the bottom of the page.
// -------------------------------------------------------------------
function displayObjectInDebugTextarea(someObject) {
  var outputText = "";
  for (var i in someObject) {
    outputText += i + " == " + someObject[i] + "\n";
  }
  displayDebugText(outputText);
}

// -------------------------------------------------------------------
// Given a text string, display the text in the debug <textarea> at
// the bottom of the page.
// -------------------------------------------------------------------
function displayDebugText(someText) {
  var elementDebugOutput = document.getElementById("debugOutput");
  elementDebugOutput.value += someText + "\n\n============================\n\n";
  elementDebugOutput.style.visibility = "visible";
}

// ===================================================================
// REGISTER FOR WINDOW EVENTS
//   |
//   |
//   V

window.onload = onLoad;
window.onresize = onWindowResize;
window.onerror = displayObjectInDebugTextarea;

// THE END
// ===================================================================

