//
// Wordsy 1.8 -- Copyright 2005-2009 Floating Sheep Studios
//                                   http://floatingsheep.com/
//

//// global state data ////
var pile    = [];    // the letters still in the pile, ordered
var answer  = [];    // the letters in a partial answer, ordered
var aletts  = 0;     // # of answer letters
var hist    = [];    // an array of previous correct answers for history
var hpos    = 0;     // position in the history array
var hsize   = 0;     // size of history array (this round)
var answers = {};    // a hash of valid answer words
var used    = {};    // a hash of which of those have been used
var acount  = 0;     // # of correct answer words possible
var round   = 1;     // round #
var score   = 0;     // score between all the rounds so far
var sixes   = 0;     // number of six-letter words in this round
var pstart  = 0;     // Date object when game was paused
var tstart;	     // Date object when this round was started
var tlimit;	     // the calculated time limit for a round
var ticking = false; // whether the timer is ticking
var deftout = null;  // holds timeout object for definitions display
var defword = null;  // which word was mostly requested for definition
var defcache= [];    // cache of definitions for words
var soundon = false; // sound currently enabled?
var soundld = false; // have sounds been loaded?
var lastdiff= 9999;  // last value of the timer
var randseed;	     // random seed
var seqpos;	     // sequence # in that list
var togglePause;     // pointer to which functionality pause has
var paused;          // is the game paused?
var IE;              // is this IE?
var inputlocked;     // is input locked?
var globallo;	     // current global lo score to beat

//// configuration ////
var tlmult  = 3;  // time limit multiplier: 2.75x the number of total points
var points  = [	    	      // point-mapping
  0,0,0,  // 0, 1, and 2-letter words
  1,	  // 3-letter
  2,	  // 4-letter
  4,	  // 5-letter
  8 	  // 6-letter
];
var ncols = [0, 0, 0, 4, 3, 2, 1]; // how many cols to use for each word size
var pps = 2;	    	      // points-per-second: 1/2 pt per second under
var dicturl = 'http://dictionary.reference.com/search?q=';
var defdelay = 500;	      // pause before displaying definition in ms
var dwidth   = 220;

//// functions ////
function readJSON(str) {
  var obj;
  eval('obj = ' + str);
  return obj;
}

function init() {
  // initial button state
  togglePause = pauseGame;

  // check browser
  IE = (navigator.userAgent.indexOf('MSIE') != -1);

  // sound setup
  $('playSound').style.visibility = 'visible';
  if (getCookie('sound')) {
    soundManager.onerror = function () { 
      $('playSound').style.visibility = 'hidden'; 
    };
    soundManager.onload = toggleSound;
  }

  // sequence setup
  var seedseq = getCookie('seedseq');
  if (seedseq) {
    var arr  = seedseq.split('-');
    randseed = arr[0];
    seqpos   = arr[1];
  } else {
    randseed = Math.floor(4294967296 * Math.random());
    seqpos   = 0;
    updateSeedSeq();
  }

  // setup resize handler
  handleResize();
  window.onresize = handleResize;

  // setup keyboard handler and mouse onclicks
  lockInput();
  enableKeyHandler();
  for (var i = 0; i < pile.length; i++) {
    $('p_' + i).onclick = function () { 
      if (!inputlocked) useFromPile(this.id.substr(2)); 
    };
  }

  // focus on the button
  $('theButton').focus();
}

function enableKeyHandler() {
  document.onkeydown = handleKey;
}

function disableKeyHandler() {
  document.onkeydown = null;
}

function updateSeedSeq() {
  expires = new Date();
  expires.setDate(expires.getDate() + 90);
  setCookie('seedseq', randseed + '-' + seqpos, expires);
}

function addPoints(pts) {
  score += pts;
  $('score').innerHTML = score;
}

function lockInput() {
  inputlocked = true;
  // $('debug').innerHTML = 'inputlocked = true';
}

function allowInput() {
  inputlocked = false;
  // $('debug').innerHTML = 'inputlocked = false';
}

function handleResize() {
  $('definition').style.maxHeight = 
    Math.max(100, document.body.clientHeight - 600) + 'px';
}

function resumeGame() {
  Element.hide('pauseFrame');

  // figure out how long we've been paused
  var now  = new Date();
  var diff = now - pstart;

  // push the start time forward by that much
  tstart.setSeconds(tstart.getSeconds() + Math.round(diff / 1000));

  ticking = true;
  updateTimer();
  $('theButton').innerHTML = 'Pause';

  // and resume
  togglePause = pauseGame;
  paused = false;
  allowInput();
}

function pauseGame() {
  // pause
  lockInput();
  paused = true;
  ticking = false;
  togglePause = resumeGame;

  // replace the screen w/ work-safe fakery
  Element.show('pauseFrame');

  pstart = new Date();
  $('theButton').innerHTML = 'Resume';
}

function newRound() {
  var butt = $('theButton');
  butt.blur();
  butt.onclick = function () { togglePause(); return false; };
  butt.innerHTML = 'Pause';

  var now = new Date();
  new Ajax.Request('ajax/round.cgi/' + randseed + '/' + seqpos, {
    method:      'get',
    aynchronous: false,
    onSuccess:   function (req) {
      seqpos++;
      updateSeedSeq();
      $('round').innerHTML = round;
      notify('Starting Round ' + round++);
      var data = readJSON(req.responseText);
      loadAnswers(data.words);
      fillPile(data.letters);
      startTimer();
      togglePause = pauseGame;
      paused = false;
      allowInput();
    }
  });
}

function startOver() {
  addPoints(-score);
  round = 1;
  newRound();
}

function endRound(msg, snd) {
  ticking = false;
  setAnswer('');

  var tstop = new Date();
  var diff  = tstop - tstart;

  var got_sixes = true;

  var rscore = 0;

  // hilight unused words and figure score
  for (var word in answers) {
    // words in the hash are preceded with ' '
    word = word.substr(1);

    if (used[' ' + word]) {
      rscore += points[word.length];
    } else {
      if (word.length == 6) got_sixes = false;
      revealAnswer(word, true);
    }
  }

  if (diff < tlimit) {
    var bonus = Math.round((tlimit - diff) * pps / 1000);
    rscore += bonus;
    addPoints(bonus);
  } else {
    $('timer').innerHTML = '0:00';
  }

  playSound(snd);
  endStr = 'Round Over: <span class="small">' + msg + ' Score: ' + rscore
	 + '</span>';
  // alert(endStr);
  notify(endStr);

  // set the button up correctly
  var butt = $('theButton');
  if (got_sixes) {
    delayButton(butt, 'Round ' + round, 'newRound', 2000);
  } else {
    notify('Game Over.  Final Score: ' + score);
    disableButton(butt);
    setTimeout(checkHiScore, 1000);
  }
}

function checkHiScore() {
  var d = new Date();
  var nick = getCookie('nick');

  new Ajax.Request('ajax/loscore.cgi/' + d.getTime(), {
    method:     'get',
    parameters: 'nick=' + escape(nick),
    onSuccess:  function (req) {
      var los  = readJSON(req.responseText);
      var lo   = parseInt(los[0]);
      globallo = parseInt(los[1]);
      if (!lo || score > lo) {
	playSound('rimshot');
        disableButton($('theButton'));
	$('theButton').innerHTML = 'Start Over';
	showDialog();
      } else {
	playSound('grumble');
	delayButton($('theButton'), 'Start Over', 'startOver', 1000);
      }
    }
  });
}

function sendHiScore() {
  enableKeyHandler();
  var nick = $('nick').value;

  if (nick.search(/\S/) != -1)
    var expires = new Date();
    expires.setDate(expires.getDate() + 90);
    setCookie('nick', nick, expires);

    new Ajax.Request('ajax/score.cgi', {
      method:     'get',
      parameters: 'nick=' + escape(nick) + '&score=' + score + 
		 '&round=' + (round-1),
      onSuccess:  function (req) { 

	if (score > globallo) {
	  popup('scores.mhtml?id=' + parseInt(req.responseText));
	} else {
	  popup('scores.mhtml?nick=' + escape(nick));
	}

	hideDialog();
      }
    });

  return false;
}

function delayButton(obj, str, action, delay) {
  obj.innerHTML = str;
  disableButton(obj);
  setTimeout(function () { 
    enableButton(obj, action);
  }, delay);
}

function disableButton(obj) {
  obj.className    = 'inactiveButton';
  obj.onclick      = function () { return false; };
  obj.style.cursor = 'default';
}

function enableButton(obj, action) {
  obj.onclick      = function () { eval(action+"()");return false; };
  obj.className    = 'button';
  obj.style.cursor = 'pointer';
}

function startTimer() {
  // calculate the time limit
  var pts = 0;
  for (var word in answers)
    pts += points[word.length - 1];
  tlimit = Math.round(pts * tlmult) * 1000;

  // reset values
  tstart   = new Date();
  ticking  = true;
  lastdiff = 9999;

  // show first time limit
  updateTimer();
}

function updateTimer() {
  if (!ticking) return;

  var now  = new Date();
  var diff = now - tstart;

  if (diff >= tlimit) {
    lockInput();
    return endRound("Time's Up!", 'doorclose');
  }

  diff = Math.round((tlimit - diff) / 1000);

  var m = Math.floor(diff / 60)
  var s = diff % 60;
  if (s < 10) s = '0' + s;

  // don't refresh unless we have to
  if (diff < lastdiff) {
    $('timer').innerHTML = m + ':' + s;
    lastdiff = diff;
  }

  setTimeout(updateTimer, 900);
}

function handleKey(e) {
  if (!e) var e = window.event;

  // don't handle any ctrl chars
  if (e.ctrlKey || e.altKey || e.metaKey) return true;

  var code;
  if (e.keyCode) code = e.keyCode;
  else if (e.which) code = e.which;
  var chr = String.fromCharCode(code);

  // normalize letters to lower-case
  if (code > 64 && code < 91) {
    code += 32;
    chr   = chr.toLowerCase();
  }

  if (code > 96 && code < 123) {  	  // a - z
    if (!inputlocked) letterTyped(chr);
  } else if (code == 8 || code == 46) {	  // backspace / delete
    if (!inputlocked) removeAnswerChr();
  } else if (code == 38) {		  // up arrow
    if (!inputlocked) historyUp();
  } else if (code == 40) {		  // down arrow
    if (!inputlocked) historyDown();
  } else if (code == 37 || code == 39) {  // left/right arrow
    if (!inputlocked) shufflePile();
  } else if (code == 13) {  		  // enter
    if (!inputlocked) checkAnswer();
  } else if (chr == ' ') {		  // space
    if (!inputlocked || paused) togglePause();
  } else if (code == 27) {		  // escape
    if (!inputlocked) setAnswer('');
  } else {				  // everything else
    return true;
  }

  return false;
}

function indexOf(arr, thing) {
  for (var i = 0; i < arr.length; i++) {
    if (thing == arr[i]) return i;
  }
  return null;
}

function shufflePile() {
  var newpile = [];
  while (pile.length) {
    var c = pile.splice(Math.floor(Math.random() * pile.length), 1);
    newpile.push(c[0]);
  }
  pile = newpile;
  for (var i = 0; i < pile.length; i++) {
    var c = pile[i];
    $('p_' + i).innerHTML = c || '&nbsp;';
  }
}

function checkAnswer() {
  lockInput();

  var word = answer.slice(0,aletts).join('');

  if (word.length > 0) {
    var uword = word.toUpperCase();

    if (used[' ' + word]) {
      playSound('doink');
      notify('Already used: ' + uword);
      setAnswer('');
    } else if (answers[' ' + word]) {
      playSound('drip');
      gotWord(word);
      hpos = hsize;
      msg = 'Good word: ' + uword;

      if (word.length == 6) {
	if (!--sixes) {
	  msg += '; <span class="small">You are go for Round ' + round + 
		 '.</span>';
	  playSound('zelda');
	}
      }

      notify(msg);

      // check if we're done
      if (acount == hsize) {
	endRound('You got all the words!', 'chimes');
      }
    } else {
      notify('Invalid word: ' + uword);
      playSound('ahem');
      setAnswer('');
    }
  }

  allowInput();
}

function gotWord(word) {
  // put it in the grid
  revealAnswer(word);

  // mark it used
  used[' ' + word] = true;

  // clear the answer
  setAnswer('');

  // add it to the history
  hist[hpos++] = word;
  hsize++;

  // update count
  updateWordsLeft();

  // update your score
  addPoints(points[word.length]);
}

function updateWordsLeft() {
  $('wordsLeft').innerHTML = acount - hsize;
}

function revealAnswer(word, missed) {
  var g = $('g_' + word);
  for (var i = 0; i < word.length; i++) {
    var lett = g.childNodes.item(i);
    lett.innerHTML = word.charAt(i);
    if (missed) lett.className = 'missed';
    lett.style.cursor = 'pointer';
  }
  g.onclick      = function () { window.open(dicturl + word, 'dict'); };
  g.onmouseover  = function () { showDefinition(word); };
  g.onmouseout   = function () { hideDefinition(word); };
  g.style.cursor = 'pointer';
}

function reallyShowDefinition() {
  var word = defword;

  var defdom = $('definition');
  if (defcache[word]) {
    defdom.innerHTML = defcache[word];
    defdom.style.display = 'block';
    return;
  }

  defdom.innerHTML = 'Loading definition for: ' + word + '...';
  defdom.style.display = 'block';
  new Ajax.Request('ajax/define.cgi/' + word, {
    method:    'get',
    onSuccess: function (req) {
      if (defword == word) {
	defdom.innerHTML = defcache[word] = req.responseText;
	defdom.style.display = 'block';
      }
    }
  });
}

function showDefinition(word) {
  clearTimeout(deftout);
  defword = word;
  deftout = setTimeout(reallyShowDefinition, defdelay);
}

function hideDefinition(word) {
  if (defword != word) return;

  clearTimeout(deftout);
  deftout = defword = null;

  // hide the definition field
  $('definition').style.display = 'none';
  // Effect.SlideUp($('definition').parentNode);
}

function notify(str) {
  $('notices').innerHTML = str;
}

function letterTyped(chr) {
  // find the letter in the pile
  var p = indexOf(pile, chr);
  if (p == null) return;

  useFromPile(p);
}

function useFromPile(p) {
  if (!pile[p]) return;

  var chr = pile[p];

  // remove it from the pile
  pile[p] = null;
  $('p_' + p).innerHTML = '&nbsp;';

  // add it to the answer
  answer[aletts] = chr;
  $('a_' + aletts).innerHTML = chr;
  aletts++;
}

function removeAnswerChr() {
  if (aletts == 0) return;

  var chr = answer[--aletts];
  answer[aletts] = null;
  $('a_' + aletts).innerHTML = '&nbsp;';

  var p = indexOf(pile, null);
  pile[p] = chr;
  $('p_' + p).innerHTML = chr;
}

function historyUp() {
  if (hpos > 0)
    setAnswer(hist[--hpos]);
}

function historyDown() {
  if (hpos < (hsize - 1))
    setAnswer(hist[++hpos]);
}

function setAnswer(word) {
  // do this the cheap and easy way for now
  while (aletts) removeAnswerChr();
  for (var i = 0; i < word.length; i++)
    letterTyped(word.charAt(i));
}

function emptyGrid() {
  // stupid IE
  $('gridContainer').innerHTML = '<table border="0" cellpadding="0" cellspacing="0" style="text-align:center"><tbody id="grid"></tbody></table>';
}

function loadAnswers(words) {
  answers = {};
  used    = {};
  hsize   = 0;
  sixes   = 0;
  touts   = {};
  defcache= {};

  for (var i = 0; i < words.length; i++) {
    var word = words[i];
    answers[' ' + word] = true;
    if (word.length == 6) sixes++;
  }
  acount = words.length;
  emptyGrid();
  fillGrid(words);

  updateWordsLeft();
}

function htmlEscape(str) {
  return str.
    replace(/&/g, '&amp;').
    replace(/</g, '&lt;').
    replace(/>/g, '&gt;').
    replace(/"/g, '&quot;');
}

function DOM2HTML(node) {
  switch (node.nodeType) {
    case 1: // element
      var tag = node.tagName.toLowerCase();
      // a|p|b|i|img|br|ul|li|dl|dt|dd|div|span|table|td|tr
      var attrs = ['classname', node.className, 'dir', node.dir, 
		   'id', node.id, 'lang', node.lang, 'title', node.title];
      var extra = [];
      switch (tag) {
	case 'a':
	  extra = ['name', node.name, 'href', node.href];
	  break;
	case 'div':
	case 'p':
	  extra = ['align', node.align];
	  break;
	case 'dl':
	  extra = ['compact', node.compact];
	  break;
	case 'img':
	  extra = ['align', node.align, 'alt', node.alt, 
		   'border', node.border, 'height', node.height, 
		   'hspace', node.hspace,
		   'lowSrc', node.lowsrc, 'name', node.name, 
		   'src', node.src, 'vspace', node.vspace, 
		   'width', node.width];
	  break;
	case 'li':
	  extra = ['type', node.type, 'value', node.value];
	  break;
	case 'ul':
	  extra = ['compact', node.compact, 'type', node.type];
	  break;
	case 'ol':
	  extra = ['compact', node.compact, 'start', node.start,  
		   'type', node.type];
	  break;
	case 'table':
	  extra = ['align', node.align, 'bgcolor', node.bgColor, 
		   'border', node.border, 'cellpadding', node.cellPadding, 
		   'cellspacing', node.cellSpacing, 'width', node.width];
	  break;
	case 'tr':
	  extra = ['align', node.align, 'bgcolor', node.bgColor, 
		   'valign', node.vAlign];
	  break;
	case 'td':
	  extra = ['abbr', node.abbr, 'align', node.align, 
		   'bgcolor', node.bgColor, 'colspan', node.colSpan, 
		   'height', node.height, 'nowrap', node.noWrap,
		   'rowspan', node.rowSpan, 'valign', node.vAlign, 
		   'width', node.width];
	  break;
      }

      attrs = attrs.concat(extra);
      var html = '<' + tag;
      for (var i = 0; i < attrs.length; i += 2) {
	var attr = attrs[i];
	var val  = attrs[i+1];
	if (val != null && val.length) 
	  html += ' ' + attr + '="' + htmlEscape(val) + '"';
      }
      html += '>';

      var kids = node.childNodes;
      for (i = 0; i < kids.length; i++) {
	html += DOM2HTML(kids.item(i));
      }
      html += '</' + tag + '>';

      return html;
    case 3: // text
      return htmlEscape(node.nodeValue);
    case 4: // cdata
    case 8: // comment
      return '';
    default:
      alert('unhandled nodeType: ' + node.nodeType);
  }
}

function fillGrid(words) {
  var grid = $('grid');

  // count number of letters in each word
  var count = [-1, -1, -1, 0, 0, 0, 0];

  // to hold dom objects
  var cols  = [];

  words.each(function (word) {
    var len = word.length;

    // make a new row for this word
    var wordTR = document.createElement('tr');
    wordTR.id  = 'g_' + word;

    // fill in the letters
    for (var j = 0; j < word.length; j++) {
      var td = document.createElement('td');
      td.className = 'hit';
      td.innerHTML = '&nbsp;';
      wordTR.appendChild(td);
    }

    // now the fun formatting begins
    if (ncols[len] > 0) {
      var col = count[len]++ % ncols[len];

      // we don't even have a table row to put the whole letter class in
      if (!cols[len]) {
	var tr = document.createElement('tr');
	grid.appendChild(tr);
	var td = document.createElement('td');
	td.colSpan = 6;
	tr.appendChild(td);
	var t = document.createElement('table');
	t.cellPadding = t.border = 0;
	t.cellSpacing = 5;
	td.appendChild(t);
	cols[len] = document.createElement('tr');
	t.appendChild(cols[len]);
      }

      // we need a column to put the row in
      var colBody;
      var cNodes = cols[len].childNodes;
      if (cNodes.length > col && cNodes.item(col)) {
	colBody = cNodes.item(col).firstChild.firstChild;
      } else {
	td = document.createElement('td');
	td.vAlign = 'top';
	cols[len].appendChild(td);
	t  = document.createElement('table');
	t.cellPadding = t.border = 0;
	t.cellSpacing = 1;
	td.appendChild(t);
	colBody = document.createElement('tbody');
	t.appendChild(colBody);
      }

      colBody.appendChild(wordTR);
    } else {
      grid.appendChild(wordTR);
    }
  });

  // stupid IE hack
  var gc = $('gridContainer');
  var h  = gc.innerHTML;
  gc.innerHTML = h;
}

function fillPile(letters) {
  pile = [];
  for (var i = 0; i < letters.length; i++) {
    var chr = letters.charAt(i);
    pile[i]  = chr;
    $('p_' + i).innerHTML = chr;
  }
}

function emptyAnswer() {
  answer = [];
  aletts = 0;
  for (var i = 0; i < 6; i++)
    $('a_' + i).innerHTML = '&nbsp;';
}

function playSound(snd) {
  if (soundon) soundManager.play(snd);
}

function loadSounds() {
  ['ahem', 'drip', 'doorclose', 'grumble', 'zelda', 'chimes', 'rimshot', 
      'doink'].each(function (snd) {
    soundManager.createSound({
      id:       snd,
      url:      'sounds/' + snd + '.mp3',
      autoLoad: true,
      autoPlay: false,
      volume:   50
    });
  });
  soundld = true;
}

function toggleSound() {
  var butt = $('playSound');
  butt.blur();
  if (soundon) {
    soundon        = false;
    butt.innerHTML = 'Play Sounds';
    deleteCookie('sound');
  } else {
    if (!soundld) loadSounds();
    soundon        = true;
    butt.innerHTML = 'Mute Sounds';

    // set cookie noting sound is on for 90 days in the future
    expires = new Date();
    expires.setDate(expires.getDate() + 90);
    setCookie('sound', 1, expires);
  }
}

function popup(path) {
  window.open(path, 'popup', 'width=400,height=400,scrollbars,resizable');
}

function showDialog() {
  $('hiscore').innerHTML = score;
  $('nick').value = getCookie('nick');

  var d = $('dialog');
  d.style.width = dwidth + 'px';
  d.style.left  = Math.round((document.body.clientWidth - dwidth) / 2) + 'px';
  new Effect.MoveBy('dialog', 300, 0, {
    duration:    0.75,
    afterFinish: function () {
      $('nick').focus();
      disableKeyHandler();
    }
  });
}

function hideDialog() {
  var d = $('dialog');
  new Effect.Opacity(d, {
    from:        1.0,
    to:          0.0,
    duration:    0.5,
    afterFinish: function () {
      d.style.top  = '-105px';
      d.style.opacity = 1.0;
      enableButton($('theButton'), 'startOver');
    }
  });
}
