// Sudoku solving and hinting ui in Javascript.
// Thanksgiving 2008 version 11-28-2008
// Copyright 2008 David Bau, all rights reserved.

function sudokusetup() {
  if (window.addEventListener) {
    // The W3C/Firefox way
    window.addEventListener("keydown", dokey, false);
  } else if (document.attachEvent) {
    // The IE way
    document.attachEvent("onkeydown", dokey);
  } else {
    // The legacy way
    document.onkeydown = dokey;
  }
  readlocationhash();
  if ("onhashchange" in window) {
    window.onhashchange = pollhashchange;
  } else {
    setInterval(pollhashchange, 250);
  }
}

var lastknownhash = document.location.hash;

function pollhashchange() {
  if (lastknownhash != document.location.hash) {
    readlocationhash();
  }
}

function readlocationhash() {
  lastknownhash = document.location.hash;
  if (document.location.hash.match("easy$")) {
    makeeasy();
    return;
  } else if (document.location.hash.match("hard$|^$")) {
    makehard();
    return;
  }
  var m = /\d{81}/.exec(document.location.hash);
  var board = [];
  if (m) {
    var saved = m[0];
    for (var i = 0; i < 81; i++) {
      board.push(parseInt(saved.substring(i,i+1)) - 1);
      if (board[i] < 0) board[i] = null;
    }
    if (solvable(board)) {
      writeboard(board);
      boldfilled(board);
    } else {
      document.location.hash = "";
    }
  }
}

function locationstring(board) {
  var h = '';
  for (var i = 0; i < 81; i++) {
    if (board[i] === null) h += '0';
    else {
      h += (board[i] + 1);
    }
  }
  return h;
}

function writelocationhash(board) {
  var h = "#" + locationstring(board);
  lastknownhash = h;
  document.location.hash = h;
  lastknownhash = document.location.hash;
}

function sudokucss() {
  var IE6 = /MSIE 6/i.test(navigator.userAgent);
  text = "";
  text += "table.sudoku {" +
            "border-collapse: collapse; }\n";
  text += "table.sudoku-select {" +
            "border-collapse: collapse; " +
            (IE6 ? "" : "border: 1px solid transparent; ") + 
            "padding: 0px; " +
            "line-height: 100%; " +
            "text-align: center; vertical-align: middle; " +
            "padding: 0px; }\n";
  text += "td.sudoku-cell {" +
            "width: 45px; height: 45px; " +
            "text-align: center; vertical-align: middle; " +
            "line-height: 0; " +
            "border: 1px solid black; }\n";
  text += "td.sudoku-number {" +
            "width: 39px; height: 35px; " +
            "text-align: center; vertical-align: middle; " +
            "cursor: hand; " +
            "padding: 0px; margin: 0px; " + 
            "line-height: 0; " + 
            "font: 19pt 'arial', sans-serif;}\n";
  text += "td.sudoku-number-bold {" +
            "width: 39px; height: 35px; " +
            "text-align: center; vertical-align: middle; " +
            "cursor: hand; " +
            "padding: 0px; margin: 0px; " + 
            "line-height: 0; " + 
            "font-weight: bold; " + 
            "font: 20pt 'arial black', sans-serif;}\n";
  text += "td.sudoku-border {" +
            "background: black; height: 1px; width: 1px; " +
            "border: 1px solid black;}\n";
  text += "img.sudoku-border {" +
            "background: black; height: 1px; width: 1px; }\n";
  return text;
}

function sudokuhtml() {
  var text = "<table class=sudoku id=grid cellpadding=1px>\n";
  text += "<tr><td colspan=13 class=sudoku-border>" +
          "<img class=sudoku-border></td></tr>\n";
  for (var y = 0; y < 9; y++) {
    text += "<tr>"
    text += "<td class=sudoku-border></td>"
    for (var x = 0; x < 9; x++) {
      var c = y * 9 + x;
      text += "<td class=sudoku-cell id=sc" + c +
              " onselectstart='return false;'" +
              " onmousedown='return doclick(event, " + c + ");'>" +
              "<table class=sudoku-select " +
              " id=st" + c + " align=center><tr>" +
              "<td class=sudoku-number id=sn" + c +
              " style='border: none'>" +
              "&nbsp;</td></tr></table>" +
              "</td>";
      if (x % 3 == 2) text += "<td class=sudoku-border></td>";
    }
    text += "</tr>\n";
    if (y % 3 == 2) {
      text += "<tr><td colspan=13 class=sudoku-border>" +
              "<img class=sudoku-border></td></tr>\n";
    }
  }
  text += "<tr><td colspan=9 id=caption></td></tr>\n";
  text += "</table>\n";
  return text;
}

function sudokubuttons(cm, sm, me, mh) {
  if (typeof(cm) == "undefined") cm = "Clear";
  if (typeof(sm) == "undefined") sm = "Solve";
  if (typeof(me) == "undefined") me = "Easy";
  if (typeof(mh) == "undefined") mh = "Hard";
  var text = "";
  if (cm !== null) {
    text += "<input type=button value=\"" + cm + "\" onclick=clearit()" +
            " id=sclear>";
  }
  if (sm !== null) {
    text += "<input type=button value=\"" + sm + "\" onclick=solveit()" +
            " id=ssolve>";
  }
  if (me !== null) {
    text += "<input type=button value=\"" + me + "\" onclick=makeeasy()" +
            " id=seasy>";
  }
  if (mh !== null) {
    text += "<input type=button value=\"" + mh + "\" onclick=makehard()" +
            " id=shard>";
  }
  return text;
}

var complimit = 20000;

function cellelt(pos) {
  return document.getElementById("sc" + pos);
}

function numelt(pos) {
  return document.getElementById("sn" + pos);
}

function selectelt(pos) {
  return document.getElementById("st" + pos);
}

function readpos(pos) {
  var v = parseInt(numelt(pos).innerHTML);
  if (!v) return null;
  return v - 1;
}

function focusboard() {
  document.getElementById("ssolve").blur();
  document.getElementById("sclear").blur();
  var g = document.getElementById("grid");
  if (g.focus) g.focus();
}

function emptyboard() {
  var result = [];
  for (var pos = 0; pos < 81; pos++) {
    result.push(null);
  }
  return result;
}

function readboard() {
  var result = []
  for (var pos = 0; pos < 81; pos++) {
    result.push(readpos(pos));
  }
  return result;
}

var timepos = -1;
var timestart = 0;
var slowtime = 250;
var timedelay = slowtime;

function workpos(pos) {
  timepos = pos;
  timestart = (new Date).getTime();
  if (timedelay >= slowtime) {
    numelt(pos).innerHTML = '.';
  }
}

function flashpos(pos, msg) {
  numelt(pos).innerHTML = '' + msg;
}

function writepos(pos, v) {
  if (pos == timepos) {
    timedelay = (timedelay * 3 + ((new Date).getTime() - timestart)) / 4;
  }
  var e = numelt(pos);
  if (v === null || v < 0) e.innerHTML = '&nbsp;';
  else e.innerHTML = '' + (v + 1);
}

function colorpos(pos, c) {
  cellelt(pos).style.background = c;
}

selected = null;

function selectpos(pos) {
  if (pos === selected) return;
  var e;
  if (pos !== null) {
    e = selectelt(pos);
    e.style.border = "1px dashed blue";
  }
  if (selected !== null) {
    e = selectelt(selected);
    e.style.border = "none";
  }
  selected = pos;
}

function unselectpos(pos) {
  var e = numelt(pos);
  e.style.border = 'none';
}

function writeboard(b) {
  var choices = hint(b, false);
  var easy = b.slice();
  var inspect = b.slice();
  if (choices.length && choices[0].length == 1) {
    for (var i in choices) {
      easy[choices[i][0][0]] = choices[i][0][1];
    }
  }
  var deduced = easy.slice();
  deduce(deduced);
  var hard = deduced.slice();
  var tricky = hint(deduced, true);
  if (tricky.length && tricky[0].length == 1) {
    for (var i in tricky) {
      hard[tricky[i][0][0]] = tricky[i][0][1];
    }
  }
  for (var pos = 0; pos < 81; pos++) {
    writepos(pos, b[pos]);
    var color = 'white';
    if (b[pos] === null) {
      if (easy[pos] !== null) color = '#99FF33';
      else if (deduced[pos] !== null) color = '#FFFF66';
      else if (hard[pos] !== null) color = '#FFCC66';
      else if (inspect[pos] !== null) color = '#CCFFFF';
    }
    colorpos(pos, color);
  }
  writelocationhash(b);
  for (update in boardupdate) {
    boardupdate[update](b);
  }
}

var updatecounter = 0;
var boardupdate = {};
function registerboardupdate(f) {
  if (typeof(f) != 'function') return;
  updatecounter += 1;
  boardupdate[updatecounter] = f;
  return updatecounter;
}

function defer(f) {
  setTimeout(f, 0);
}

function doclick(event, pos) {
  var num = readpos(pos);
  if (event.ctrlKey) {
    boldnum(pos, !isboldnum(pos));
    if (num != null) return;
  }
  workpos(pos);
  selectpos(pos);
  focusboard();
  defer(function() {
    var tboard = readboard();
    if (num === null) num = -1;
    tboard[pos] = null;
    var bits = allowed(tboard, pos);
    while (num != 8) {
      num += 1;
      if (((1 << num) & bits) != 0) {
        tboard[pos] = num;
        var s = solvable(tboard, complimit);
        if (s != 0) {
          writeboard(tboard);
          return;
        }
      }
    }
    tboard[pos] = null;
    writeboard(tboard);
  });
  return false;
}

function tryset(pos, v) {
  var oldval = readpos(pos);
  if (v === null || v == oldval) {
    writepos(pos, v);
    writeboard(readboard());
    return;
  }
  flashpos(pos, v + 1);
  defer(function() {
    var tboard = readboard();
    tboard[pos] = null;
    var bits = allowed(tboard, pos);
    var okval = oldval;
    if (((1 << v) & bits) != 0) {
      tboard[pos] = v;
      var s = solvable(tboard, complimit);
      if (s != 0) okval = v;
    }
    tboard[pos] = okval;
    writeboard(tboard);
  });
}

function isboldnum(n) {
  return numelt(n).className == 'sudoku-number-bold';
}

function boldnum(n, b) {
  numelt(n).className = b ? 'sudoku-number-bold' : 'sudoku-number';
}

function boldfilled(b) {
  for (var pos = 0; pos < 81; pos++) {
    boldnum(pos, null !== b[pos]);
  }
}

function dokey(e) {
  var keycode = window.event ? window.event.keyCode : e.which;
  if (keycode >= 37 && keycode <= 40) {
    if (selected == null) {
      x = 4; y = 4;
    } else {
      x = selected % 9; y = Math.floor(selected / 9);
      if (keycode == 37) x += 8;
      if (keycode == 39) x += 1;
      if (keycode == 38) y += 8;
      if (keycode == 40) y += 1;
      x = x % 9; y = y % 9;
    }
    selectpos(x + y * 9);
    focusboard();
    if (e.preventDefault) e.preventDefault();
    return false;
  }
  if (selected == null) return;
  if (keycode > 48 && keycode < 58) {
    tryset(selected, keycode - 49);
  } else if ((readpos(selected) !== null && keycode == 32) ||
             keycode == 48 || keycode == 8 || keycode == 127) {
    tryset(selected, null);
    if (e.preventDefault) e.preventDefault();
  } else if (keycode == 32) {
    var tboard = readboard();
    var s = solution(tboard, complimit);
    if (s !== null) {
      tboard[selected] = s[selected];
    }
    writeboard(tboard);
    if (e.preventDefault) e.preventDefault();
  }
  if (keycode == 'B'.charCodeAt(0)) {
    boldnum(selected, !isboldnum(selected));
  }
  return false;
}

function solveit() {
  var tboard = readboard();
  var s = solution(tboard, complimit);
  writeboard(s);
  focusboard();
}

function clearit() {
  var board = emptyboard();
  writeboard(board);
  boldfilled(board);
  focusboard();
}

function makeeasy() {
  var board = makepuzzle(readboard(), false);
  writeboard(board);
  boldfilled(board);
  focusboard();
}

function makehard() {
  var board = makepuzzle(readboard(), true);
  writeboard(board);
  boldfilled(board);
  focusboard();
}
