$(document).ready(function(){ initGrids(puzzles); drawButtons(); });

// static preference for all grids
var gridSize = 1;
var grids = new Array();
var activeGrid = -1;

// static constants
var ALL_SETS = new Array();
var OFF_GRID = new Object();
var killNum = new Array();

var isIE = /MSIE/.test(navigator.userAgent);

function initGrids(puzzles) {
    ALL_SETS.push(makeColumnSets());
    ALL_SETS.push(makeRowSets());
    ALL_SETS.push(makeBlockSets());

    OFF_GRID.x = -1;
    OFF_GRID.y = -1;

    var x = 0;
    for (var i=0; i<puzzles.length; i++) {
        $('.grid_'+i).show();
        var grid = new FishyGrid(i);
        grid.initGrid(puzzles[i]);
        $('.grid_'+i).hide();

        grids.push(grid);

        x = i;
    }
    if (typeof(tab) != 'undefined') {
        changeChannel(tab-1);
    }
    if (activeGrid < 0) {
        // show medium grid by default.
        if (puzzles.length > 1) x--;
        changeChannel(x);
    }

    $(document).click(function(e){ clickAway(e); });

}

function changeChannel(x) {
    if (x != activeGrid && x >= 0 && x < grids.length) {
        // loop through all, hide inactive, show active
        for (var i=0; i<grids.length; i++) {
            if (i == x) {
                $('.grid_'+x).show();
                $('#grid_tab_'+x).addClass('grid_tab_selected');
            } else {
                $('.grid_'+i).hide();
                $('#grid_tab_'+i).removeClass('grid_tab_selected');
            }
        }
        activeGrid = x;
        if (getActiveGrid().autoHelp) {
            $('#hint_link').show();
            $('#hint_explain').show().html('');
        } else {
            $('#hint_link').hide();
            $('#hint_explain').hide();
        }
    }
}

function getActiveGrid() {
    return grids[activeGrid];
}

function makeColumnSets() {
    var sets = new Array();
    for (var col=0; col<9; col++) {
        var col_set = new Array();
        for (var row=0; row<9; row++) {
            col_set.push(row*9 + col);
        }
        sets.push(col_set);
    }
    return sets;
}

function makeRowSets() {
    var sets = new Array();
    for (var row=0; row<9; row++) {
        var row_set = new Array();
        for (var col=0; col<9; col++) {
            row_set.push(row*9 + col);
        }
        sets.push(row_set);
    }
    return sets;
}

function makeBlockSets() {
    var sets = new Array();
    for (var bX=0; bX<3; bX++) {
        for (var bY=0; bY<3; bY++) {
            var topLeft = (bY*3)*9 + bX*3;
            var block_set = new Array();
            for (var row=0; row<3; row++) {
                for (var col=0; col<3; col++) {
                    block_set.push(topLeft + row*9 + col);
                }
            }
            sets.push(block_set);
        }
    }
    return sets;
}


/*** FishyGrid class ***/

function FishyGrid(gridID) {

  this.gridID = gridID;

  this.noMods;
  this.puzzleInit = new Array(81);
  this.puzzleState = new Array(81);
  this.pencilMarks = new Object();
  this.autoHelp = false;
  this.cursorXY;
  this.pencilmarkRC;

  this.keyStamp = 0;
  this.lastStamp = 0;

  // this obj. supports multiple levels of undo/redo
  this.lastAction = new Object();
}

FishyGrid.prototype.gridref = function(jqRef) {
    return $('.grid_'+this.gridID+' '+jqRef);
}

FishyGrid.prototype.cellref = function(rc,infix) {
    var prefix = '#g'+this.gridID+'_';
    if (infix) prefix += infix + '_';
    if (rc.indexOf('g') == 0) prefix = '#';
    return $(prefix+rc);
}

FishyGrid.prototype.initGrid = function(puzzle) {
    var input = $('.cursor_input');
    input.bind('keyup mouseup change',function(objEvent){
            setCursorSquare( objEvent, new Date().getTime(), $(this).val() );
        });
    if (navigator.userAgent.match(/Safari/i) || $.browser.msie) {
        input.keydown(function(event){handleInputKey(event, new Date().getTime());});
        $(document).keydown(function(e){ handleKeypress(e); });
    } else {
        input.keypress(function(event){handleInputKey(event, new Date().getTime());});
        $(document).keypress(function(e){ handleKeypress(e); });
    }

    this.hideCursor();
    //$('.cursor').hide();

    this.noMods = puzzle;
    if (this.noMods == null) {
        this.noMods = '020900010109050008007001450001403002090010030300807900015200300200080105060005020';
    }

    for (var i=0; i<81; i++) {
        var rc = toRC(i);
        // leave pointers to the right grid
        this.cellref(rc)[0].grid = this;
        this.cellref(rc,'pm')[0].grid = this;

        this.cellref(rc).click(function(){ return this.grid.moveCursorToSquare($(this).attr('id')); });
        this.cellref(rc,'pm').hover(
          function(){ $(this).addClass('pm_hilite'); this.grid.hideCursor(); this.grid.pencilmarkRC = $(this).attr('id'); },function(){ $(this).removeClass('pm_hilite'); this.grid.pencilmarkRC = '';
              this.grid.restoreCursor(); }
        ).click(
          function(){ this.grid.commitSoleCandidate($(this).attr('id')); return false; }
        );
    }

    this.importPuzzle(this.noMods);

    if (this.autoHelp) {
      $('#hint_link').show();
    }
}

FishyGrid.prototype.importPuzzle = function(puzzle) {
    for (var i=0; i<81; i++) {
        var n = puzzle.charAt(i);
        this.puzzleInit[i] = n;

        var rc = toRC(i);
        this.cellref(rc,'n').html(n == 0 ? '' : n);
    }

    this.pencilMarks.user = new Array(81);
    for (var i=0; i<81; i++) {
        this.pencilMarks.user[i] = 0;
    }

    this.genPencilMarks();
    this.displayPencilMarks();
}

function drawButtons() {
    $('.btn').each(function(){
            var b = $(this);
            var tt = b.text() || b.val();
            if ($(':submit,:button',this)) {
                b = $('<a>').insertAfter(this).addClass(this.className).attr('id',this.id);
                $(this).remove();
            }
            b.text('').css({cursor:'pointer'}).prepend('<i></i>').append($('<span>').text(tt).append('<i></i><span></span>'));
            b.attr('defcolor', b.css('background-color'));
            b.hover(function(){$(this).css({'background-color':'lightblue','color':'black'});},function(){$(this).css({'background-color':$(this).attr('defcolor'),'color':'white'});});
        });
    $('#btn_autohelp').click(function(){ toggleAutoMarks(); return false; });
    $('#btn_undo').click(function(){ undoMultiple(); return false; });
    $('#btn_redo').click(function(){ redoMultiple(); return false; });
    $('#btn_restart').click(function(){ startOver(); return false; });
    $('#btn_bigger').click(function(){ enlargeGrid(); return false; });
    $('#btn_smaller').click(function(){ shrinkGrid(); return false; });
    $('#btn_hint').click(function(){ grokHint(getActiveGrid()); return false; });

}

function toggleAutoMarks() { getActiveGrid().toggleAutoMarks(); }
function undoMultiple()    { getActiveGrid().undoMultiple(); }
function redoMultiple()    { getActiveGrid().redoMultiple(); }
function startOver()       { getActiveGrid().startOver(); }

function toRC(idx) {
    var r = parseInt(idx / 9);
    var c = idx % 9;
    r++; c++;
    return 'r'+r+'c'+c;
}

function xyToRC(xy) {
    if (typeof(xy) == 'undefined' || xy == null) return null;
    return 'r'+(xy.y+1)+'c'+(xy.x+1);
}

function toXY(idx) {
    var xy = new Object();
    xy.x = idx % 9;
    xy.y = parseInt(idx / 9);
    return xy;
}

function rcToXY(rc) {
    if (typeof(rc) == 'undefined' || rc == null) return OFF_GRID;
    var prefix_len = 0;
    if (rc.indexOf('g') == 0) prefix_len = 3;
    var r = rc.charAt(1+prefix_len);
    var c = rc.charAt(3+prefix_len);
    var xy = new Object();
    xy.x = c-1;
    xy.y = r-1;
    return xy;
}

FishyGrid.prototype.moveCursorToSquare = function(rc) {
    var xy = rcToXY(rc);
    if (!this.canMod(xy)) return true;
    this.commitPreviousEntry(xy);
    this.cursorXY = xy;

    var cursor = $('.cursor').first();

    var offset = this.cellref(rc).offset();
    var xx = parseInt(offset.left);
    var yy = parseInt(offset.top);
    var outerWidth = this.cellref(rc).css('width');
    var innerWidth = cursor.width();
    if (innerWidth < 1) innerWidth = 30;
    var margin = Math.round( (parseInt(outerWidth) - parseInt(innerWidth))/2 - 1 );

    var cursorKeyMove = true;
    if (! cursor.is(':visible') || cursor.css("opacity") == '0') {
        cursorKeyMove = false;
        cursor.fadeTo(0,0.7);
    }

    cursor.offset({top:(yy+margin),left:(xx+margin)});

    var input = $('.cursor_input');
    var n = this.getEntryN(this.cursorXY);
    if (n == 0) n = '';
    input.focus().val('0').val(n).select();

    if (!cursorKeyMove) {
        this.lastStamp = 0;
        this.keyStamp = 0;
    }

    return false;
}

FishyGrid.prototype.shiftCursor = function(moveX,moveY)
{
    var xy = this.cursorXY;
    if (!xy || xy == OFF_GRID) return; //{ xy = this.saveCursorXY; }
    var newXY = new Object();
    newXY.x = xy.x;
    newXY.y = xy.y;
    var targetX = xy.x;
    var targetY = xy.y;
    for (var i=0; i<8; i++) {
        targetX += moveX;
        targetY += moveY;

        // loop around pacman-style, vertical
        if (targetY < 0) targetY = 8;
        if (targetY > 8) targetY = 0;

        // loop-around horizontal
        if (targetX < 0) targetX = 8;
        if (targetX > 8) targetX = 0;

        newXY.x = targetX;
        newXY.y = targetY;
        if (this.canMod(newXY)) {
            this.moveCursorToSquare(xyToRC(newXY));
            break;
        } else {
            //alert("can't mod "+xyToRC(newXY));
        }
    }
}

FishyGrid.prototype.tabCursor = function(whichway)
{
    var xy = this.cursorXY;
    if (xy == OFF_GRID) { xy = this.saveCursorXY; }
    var newXY = new Object();
    newXY.x = xy.x;
    newXY.y = xy.y;
    var targetX = xy.x;
    var targetY = xy.y;
    for (var i=0; i<15; i++) {
        if (whichway < 0) {
            // shift-tab: move backwards
            targetX--;
            // loop around, previous row
            if (targetX < 0) {
                targetX = 8;
                targetY--;
                if (targetY < 0) targetY = 8;
            }
        } else {
            targetX++;
            // loop around, next row
            if (targetX > 8) {
                targetX = 0;
                targetY++;
                if (targetY > 8) targetY = 0;
            }
        }
        newXY.x = targetX;
        newXY.y = targetY;
        if (this.canMod(newXY)) {
            this.moveCursorToSquare(xyToRC(newXY));
            break;
        }
    }
}

function handleInputKey(event,stamp) {
    if (!event) { event = window.event; }
    stamp = event.timeStamp;

    var grid = getActiveGrid();

    // some events fire multiple times, just process once!
    if (grid.keyStamp == stamp || grid.keyStamp == -1) return;

    // BACKSPACE and DEL
    if (event.keyCode == '8' || event.keyCode == '46') {
        grid.keyStamp = stamp;
        grid.setSquare(grid.cursorXY, 0);
    } else if (event.keyCode == '38') {
        // UP
        grid.keyStamp = stamp;
        grid.shiftCursor(0,-1);
    } else if (event.keyCode == '40') {
        // DOWN
        grid.keyStamp = stamp;
        grid.shiftCursor(0,1);
    } else if (event.keyCode == '37') {
        // LEFT
        grid.keyStamp = stamp;
        grid.shiftCursor(-1,0);
    } else if (event.keyCode == '39') {
        // RIGHT
        grid.keyStamp = stamp;
        grid.shiftCursor(1,0);
    } else if (event.keyCode == '9') {
        // TAB
        grid.keyStamp = stamp;
        event.preventDefault();
        grid.tabCursor( event.shiftKey ? -1 : 1 );
    } else if (event.keyCode == '27') {
        // ESC
        grid.keyStamp = stamp;
        event.preventDefault();
    }
}

function handleKeypress(event) {
    if (!event) event = window.event;

    var grid = getActiveGrid();

    // some events fire multiple times, just process once!
    var stamp = event.timeStamp;
    if (grid.keyStamp == stamp || grid.keyStamp == -1) return;

    var charCode = event.which;
    if (charCode >= 97 && charCode <= 105) charCode -= 48;
    var n = String.fromCharCode(charCode);

    if (n.match(/^[1-9]$/)) {
        grid.keyStamp = stamp;
        if (grid.pencilmarkRC == null || grid.pencilmarkRC == '') return;
        var rc = rcToXY(grid.pencilmarkRC.substring(6));
        grid.toggleUserMark(rc, n);
    } else if (event.keyCode == 27) {
        grid.keyStamp = stamp;
        // ESC
        if (!$('.cursor').is(':visible'))
            grid.undoLastEdit();
    }
    /*
    if (isArrowOrTab(event.keyCode) && grid.isReadyForCursorKey()) {
        if (event.keyCode == '38') {
            // UP
            grid.shiftCursor(0,-1);
        } else if (event.keyCode == '40') {
            // DOWN
            grid.shiftCursor(0,1);
        } else if (event.keyCode == '37') {
            // LEFT
            grid.shiftCursor(-1,0);
            event.preventDefault();
        } else if (event.keyCode == '39') {
            // RIGHT
            grid.shiftCursor(1,0);
        } else if (event.keyCode == '9') {
            // TAB
            event.preventDefault();
            grid.tabCursor( event.shiftKey ? -1 : 1 );
        }
    }*/
}

FishyGrid.prototype.isReadyForCursorKey = function() {
    // 1. determine cursor location (or bail out)
    if (typeof this.cursorXY != 'object' || this.cursorXY.x == OFF_GRID.x) {
//        if (typeof this.saveCursorXY != 'object' || this.saveCursorXY.x == OFF_GRID.x) {
            return false;
//        }
    }
    // 2. make sure input was hidden (or bail out)
//    var cursor = this.gridref('#cursor');
//    if (cursor.is(':visible')) return false;
    // 3. show input
//    cursor.show();
    return true;
}

function setCursorSquare( e, stamp, n ) {
    var grid = getActiveGrid();
    grid.setCursorSquare(e, stamp, n);
}

FishyGrid.prototype.setCursorSquare = function( e, stamp, n ) {
    // already processed this event?
    if (this.lastStamp == stamp || this.lastStamp == -1) return;

    this.lastStamp = stamp;
    if (isArrowOrTab(e.keyCode)) {
        // entered a number?  or just moving around?
        // if moving around, don't hide cursor!
        // in fact, re-select
        $('.cursor_input').select();
        return;
    } else if (e.keyCode == '27') {
        if (this.acceptingInput()) {
            // ESC, don't commit anything
            this.hideCursor();
        } else {
            this.undoLastEdit();
        }
        return;
    }

    if (!this.acceptingInput()) return;

    // FIXME maybe grab n from e not n?
    if ('0123456789'.indexOf(n) > 0) {
        this.setSquare(this.cursorXY, n);
    } else if (e.keyCode >= 16 && e.keyCode <= 18 || e.keyCode == 224) {
        // modifier keys CTRL, SHIFT, OPT, CMD
        return;
    }
    this.hideCursor();
}

function isArrowOrTab(keyCode) {
    if (keyCode == 9 || (keyCode >= 37 && keyCode <= 40)) return true;
    return false;
}

FishyGrid.prototype.setSquare = function(col_row, n, isUndo) {
    if (n == null) n = 0;

    if (this.canMod(col_row) && n >= 0 && n <= 9) {
        var sq = col_row.y * 9 + col_row.x;

        if (this.puzzleState[sq] == n) return;

        if (!isUndo) {
            this.lastAction.sq = sq;
            this.lastAction.xy = col_row;
            this.lastAction.was = this.puzzleState[sq];
            this.logActionForUndo(col_row,this.lastAction.was,n);
        }

        this.puzzleState[sq] = n;

        var bigNum = this.cellref(toRC(sq),'n');

        if (n == 0) {
            bigNum.html('');
            //            revealPencilMarksAt(col_row);
        } else {
            bigNum.html(n);
            this.paintGreen(bigNum);
            this.hidePencilMarksAt(col_row);
        }
        if (this.autoHelp) {
            this.genPencilMarks();
            this.showObviousProblems();
        } else {
            // should we be doing this even if this is an undo?
            /// updateUserMarks(col_row,n);
        }
        this.clearHintColoring();
        this.displayPencilMarks();
        //trace(cellX);
    }
}

FishyGrid.prototype.commitSoleCandidate = function(pmRC) {
    var rc = pmRC.substring(6);
    var xy = rcToXY(rc);
    var n = this.getSoleRemainingCandidate(xy);
    if (n > 0) {
        this.commitPreviousEntry(xy);
        this.setSquare(xy,n);
        this.paintBlue( xy );
        this.cursorXY = xy;
        $('.cursor_input').focus();
        this.hideCursor();
        return;
    }
    // otherwise...
    this.moveCursorToSquare(rc);
}

FishyGrid.prototype.hidePencilMarksAt = function(col_row) {
    this.cellref(xyToRC(col_row),'pm').html('');
}

FishyGrid.prototype.paintGreen = function(bignumref) {
    bignumref.css('color','#6699ff');
}

FishyGrid.prototype.paintBlue = function( xy ) {
    var bignumref = this.cellref(xyToRC(xy),'n');
    bignumref.css('color','blue');
}

// app differentiates between hitting escape v. clicking undo button
FishyGrid.prototype.undoLastEdit = function() {
    this.setSquare(this.lastAction.xy, this.lastAction.was, true);
    if (this.lastAction.history != null && this.lastAction.history.length > 0) {
        this.lastAction.history.pop();
    }
    this.lastAction.xy = OFF_GRID;
}

FishyGrid.prototype.undoMultiple = function() {
    if (this.lastAction.history == null || this.lastAction.history.length == 0) {
        return;
    }
    var undoAction = this.lastAction.history.pop();
    if (this.lastAction.redoStack == null) {
        this.lastAction.redoStack = new Array();
    }
    this.lastAction.redoStack.push(undoAction);

    // extra third arg means, don't push this change onto the undo stack
    this.setSquare(undoAction.xy, undoAction.before, true);
}

FishyGrid.prototype.redoMultiple = function() {
    if (this.lastAction.redoStack == null || this.lastAction.redoStack.length == 0) {
        return;
    }
    var redoAction = this.lastAction.redoStack.pop();
    this.setSquare(redoAction.xy, redoAction.after, true);
    this.cursorXY = redoAction.xy;

    // commit previous action (paint dark blue)
    if (this.lastAction.history != null && this.lastAction.history.length > 0) {
        var prevAction = this.lastAction.history[this.lastAction.history.length-1];
        this.paintBlue(prevAction.xy);
    }

    // return this action to the undo stack
    this.lastAction.history.push(redoAction);
}

FishyGrid.prototype.logActionForUndo = function(col_row, before, after) {
    var action = new Object();
    action.xy = col_row;
    action.before = (before == null ? 0 : before);
    action.after = after;
    if (this.lastAction.history == null) {
        this.lastAction.history = new Array();
    }
    this.lastAction.history.push(action);
    this.lastAction.redoStack = null;
}

// this shows/hides the blue ring for alerts
FishyGrid.prototype.alertRing = function(col_row,show) {
    if (show) {
        this.cellref(xyToRC(col_row)).addClass('alert_ring');
    } else {
        this.cellref(xyToRC(col_row)).removeClass('alert_ring');
    }
}

FishyGrid.prototype.clearAlertRings = function()
{
    var xy = new Object();
    for (var x=0; x<9; x++) {
        xy.x = x;
        for (var y=0; y<9; y++) {
            xy.y = y;
            this.alertRing(xy,false);
        }
    }
}

FishyGrid.prototype.showObviousProblems = function()
{
    this.clearAlertRings();

    for (var i=0; i<9; i++) {
        for (var j=0; j<3; j++) {
            var set = ALL_SETS[j][i];
            var dupeSig = this.getDupeSig(set);
            if (dupeSig > 0) {
                for (var k=0; k<9; k++) {
                    var n = this.getNumAt(set[k]);
                    if ((dupeSig & (1<<n)) > 0) {
                        this.alertRing(toXY(set[k]),true);
                    }
                }
            }
        }
    }
}

FishyGrid.prototype.getNumAt = function(idx)
{
    var n = this.puzzleInit[idx];
    if (n > 0) return n;

    // check current state
    n = this.puzzleState[idx];
    return n;
}

FishyGrid.prototype.getDupeSig = function(set)
{
    var sig = 0;
    var dupeSig = 0;
    var n;

    for (var i=0; i<9; i++) {
        n = this.getNumAt(set[i]);
        if (n > 0) {
            if ((sig & (1<<n)) > 0) {
                dupeSig |= 1<<n;
            }
            sig |= 1<<n;
        }
    }

    return dupeSig;
}

FishyGrid.prototype.displayPencilMarks = function() {
    var xy = new Object();
    for (var i=0; i<81; i++) {
        xy.x = i % 9;
        xy.y = parseInt(i/9);
        this.updatePencilMarksWidget(xy);
    }
}

FishyGrid.prototype.updatePencilMarksWidget = function(xy) {
    var marks = this.getPencilMarksAt(xy);
    var html = '';
    if (marks != null && marks.length > 0) {
        // if (!this.autoHelp) this.revealPencilMarksAt(xy);
        var userCandidateCount = 0;
        for (var i=0; i<marks.length; i++) {
            if (marks[i] <= 9) {
                html += '<span>'+marks[i]+'</span>';
                userCandidateCount++;
            } else {
                var n = marks[i]-1;
                n = n%9; n++;
                html += '<span class="whiteout">'+n+'</span>';
            }
        }
        if (userCandidateCount == 1) {
            html = '<span class="lone_candidate">'+html+'</span>';
        }
    }
    if (!this.autoHelp) {
        // make clickable
        if (html == '' && this.canMod(xy) && this.getEntryN(xy) == 0) {
            html = '&nbsp; &nbsp;';
        }
        // hide marks if number entered
        if (this.canMod(xy) && this.getEntryN(xy) > 0) {
            html = '';
        }
    }
    this.cellref(xyToRC(xy),'pm').html(html);
}

FishyGrid.prototype.getPencilMarksAt = function(col_row) {
    var sq = col_row.y * 9 + col_row.x;
    var sig = this.pencilMarks.sigs[sq];
    var xOut = this.pencilMarks.user[sq];
    if (this.autoHelp) {
        if (sig == 0) return null;

        var marks = new Array();
        var idx = 0;
        var mod = 0;
        for (var n=1; n<=9; n++) {
            mod = 0;
            if ((1<<n & sig) > 0) {
                if ((1<<n & xOut) > 0) {
                    mod = 9;
                }
                marks[idx] = n + mod;
                idx++;
            }
        }
        return marks;
    } else {
        var marks = new Array();
        for (var n=1; n<=9; n++) {
            if ((1<<n & xOut) > 0) {
                marks.push(n);
            }
        }
        return marks;
    }
}

FishyGrid.prototype.canMod = function(col_row) {
    if (typeof(col_row) == 'undefined' || col_row == null) return false;

    if (col_row.x < 0) return false;
    if (col_row.x > 8) return false;
    if (col_row.y < 0) return false;
    if (col_row.y > 8) return false;
    // is it a given in the puzzle def? then, not allowed to mod
    var idx = col_row.y * 9 + col_row.x;
    var sqValue = this.noMods.charAt(idx);
    var n = parseInt(sqValue);
    if (n > 0 && n < 10) return false;
    // otherwise, mod away!

    return true;
}

// some handy constants
// Careful!  These parens are necessary to avoid order-of-operation bugs
killNum[0] = 0x1FF<<1;              // 1111111110
killNum[1] = (0x1FF<<1) - (1<<1);   // 1111111100
killNum[2] = (0x1FF<<1) - (1<<2);   // 1111111010
killNum[3] = (0x1FF<<1) - (1<<3);   // 1111110110
killNum[4] = (0x1FF<<1) - (1<<4);   // 1111101110
killNum[5] = (0x1FF<<1) - (1<<5);   // 1111011110
killNum[6] = (0x1FF<<1) - (1<<6);   // 1110111110
killNum[7] = (0x1FF<<1) - (1<<7);   // 1101111110
killNum[8] = (0x1FF<<1) - (1<<8);   // 1011111110
killNum[9] = (0x1FF<<1) - (1<<9);   // 0111111110

FishyGrid.prototype.genPencilMarks = function() {
    var sigs = new Array(81);
    for (var i=0; i<81; i++) {
        if (!this.puzzleState[i]) {
            this.puzzleState[i] = 0;
        }
        if (this.puzzleInit[i] > 0 || this.puzzleState[i] > 0) {
            sigs[i] = 0;
        } else {
            sigs[i] = 0x1FF << 1;
        }
    }
    var okSig;
    var idx;
    for (var row=0; row<9; row++) {
        // which pencilmark numbers are ok in this row?
        okSig = killNum[0];
        for (var col=0; col<9; col++) {
            idx = row*9 + col;
            okSig &= killNum[this.puzzleInit[idx]];
            okSig &= killNum[this.puzzleState[idx]];
        }
        // reduce pencilmarks in this row to just
        // the ones which don't appear in the row
        for (var col=0; col<9; col++) {
            sigs[row*9+col] &= okSig;
        }
    }
    for (var col=0; col<9; col++) {
        // which pencilmark numbers are ok in this row?
        okSig = killNum[0];
        for (var row=0; row<9; row++) {
            idx = row*9 + col;
            okSig &= killNum[this.puzzleInit[idx]];
            okSig &= killNum[this.puzzleState[idx]];
        }
        // reduce pencilmarks in this col to just
        // the ones which don't appear in the col
        for (var row=0; row<9; row++) {
            sigs[row*9+col] &= okSig;
        }
    }
    for (var blkrow=0; blkrow<3; blkrow++) {
        for (var blkcol=0; blkcol<3; blkcol++) {
            // which pencilmark numbers are ok in this block?
            okSig = killNum[0];
            for (var mem=0; mem<9; mem++) {
                idx = (blkrow*3 + Math.floor(mem/3))*9 + blkcol*3 + mem%3;
                okSig &= killNum[this.puzzleInit[idx]];
                okSig &= killNum[this.puzzleState[idx]];
            }
            // reduce pencilmarks in this block to just
            // those which don't already appear in the block
            for (var mem=0; mem<9; mem++) {
                idx = (blkrow*3 + Math.floor(mem/3))*9 + blkcol*3 + mem%3;
                sigs[idx] &= okSig;
            }
        }
    }
    this.pencilMarks.sigs = sigs;
}

FishyGrid.prototype.toggleUserMark = function(col_row, n) {
    var sq = col_row.y * 9 + col_row.x;
    var nBit = 1 << n;

    // test for mark presence
    if (!this.canMod(col_row) || (this.autoHelp && this.pencilMarks.sigs[sq] & nBit == 0)) {
        // this pencilmark is not avail to edit
        return;
    }

    var markState = this.pencilMarks.user[sq] & nBit;
    if (markState > 0) {
        // turn off
        this.pencilMarks.user[sq] -= nBit;
    } else {
        // turn on
        this.pencilMarks.user[sq] += nBit;
    }

    this.updatePencilMarksWidget(col_row);
}

FishyGrid.prototype.elimPencilMarks = function(sq,sig,k) {
    var col_row = toXY(sq);
    if (!this.canMod(col_row)) { return; }

    // this is probably wrong when autohelp is off
    var marksAvail = this.pencilMarks.sigs[sq];
    var xOut = this.pencilMarks.user[sq];
    var marksOn = (marksAvail | xOut) - xOut;

    if ((marksOn & sig) > 0) {
        // color this square to show it was changed?
        // or, blink the changing digits a couple times?
        this.pencilMarks.user[sq] = xOut | (marksOn & sig);
        this.colorCandidateHint(sq,k);

        this.updatePencilMarksWidget(col_row);
    }
}


FishyGrid.prototype.getSoleRemainingCandidate = function(xy) {
    var marks = this.getPencilMarksAt(xy);
    var userCandidateCount = 0;
    var n = 0;
    for (var i=0; i<marks.length; i++) {
        if (marks[i] <= 9) {
            userCandidateCount++;
            n = marks[i];
        }
    }
    if (userCandidateCount == 1) {
        return n;
    } else {
        return 0;
    }
}

FishyGrid.prototype.commitPreviousEntry = function(col_row) {
    var oldXY = this.cursorXY;
    if (!oldXY) { return; }

    if (oldXY.x != col_row.x || oldXY.y != col_row.y) {
        // paint old square blue to signify final answer
        if (this.getEntryN(oldXY) > 0) {
            this.paintBlue( oldXY );
        }
    }
}

FishyGrid.prototype.getEntryN = function(col_row) {
    var n = this.puzzleState[col_row.y * 9 + col_row.x];
    if (n) return n;
    return 0;
}

FishyGrid.prototype.acceptingInput = function() {
    var cursor = $(".cursor");
    var cursorOpacity = cursor.css("opacity");
    if (cursorOpacity == "0") {
        return false;
    } else {
        return cursor.is(":visible");
    }
}

FishyGrid.prototype.hideCursor = function() {
    if (this.acceptingInput()) {
        $('.cursor').fadeTo(0,0).offset({top:-100,left:-100}); //hide();
        this.saveCursorXY = this.cursorXY;
//        this.cursorXY = OFF_GRID;
    } else {
        this.saveCursorXY = null;
    }
}

FishyGrid.prototype.restoreCursor = function() {
    if (typeof(this.saveCursorXY) != 'undefined' && this.saveCursorXY != null) {
        if (this.saveCursorXY != OFF_GRID) {
            this.cursorXY = this.saveCursorXY;
            this.moveCursorToSquare(xyToRC(this.cursorXY));
        }
    }
}

FishyGrid.prototype.startOver = function() {
    this.pencilMarks.user = cleanSigs(this.pencilMarks.user);
    this.puzzleState = cleanSigs(this.puzzleState);

    // clear redo/undo stacks
    this.lastAction.history = null;
    this.lastAction.redoStack = null;

    // clean up rings
    this.clearAlertRings();

    this.hideCursor();

    this.importPuzzle(this.noMods);
    if (this.autoHelp) this.genPencilMarks();
    this.displayPencilMarks();
}

// FIXME do NOT forget pencilmarks!! just toggle from one mode to the other
// ie, need to maintain two sets of marks...
FishyGrid.prototype.toggleAutoMarks = function()
{
    if (this.autoHelp) {
        this.autoHelp = false;
        this.swapPencilMarks();
        this.clearAlertRings();
        this.displayPencilMarks();
        $('#hint_link').hide();
        $('#hint_explain').hide();
    } else {
        this.autoHelp = true;
        this.swapPencilMarks();
        this.genPencilMarks();
        this.showObviousProblems();
        this.displayPencilMarks();
        $('#hint_link').show();
        $('#hint_explain').show();
    }
}

FishyGrid.prototype.swapPencilMarks = function()
{
    var tmp = this.savePencilMarks;
    this.savePencilMarks = this.pencilMarks;
    if (typeof(tmp) == 'undefined' || tmp == null) {
        this.pencilMarks = new Object();
        this.pencilMarks.user = new Array(81);
        for (var i=0; i<81; i++) this.pencilMarks.user[i] = 0;
    } else {
        this.pencilMarks = tmp;
    }
}

function cleanSigs(sigs)
{
    var cleanSigs;
    if (sigs == null) {
        cleanSigs = new Array(81);
    } else {
        cleanSigs = sigs;
    }

    for (var i=0; i<81; i++) {
        cleanSigs[i] = 0;
    }
    return cleanSigs;
}

function clickAway(e) {
    var grid = getActiveGrid();
    grid.commitPreviousEntry(OFF_GRID);
    grid.hideCursor();
    grid.cursorXY = OFF_GRID;
}

function enlargeGrid() {
    if (gridSize == 2) return;
    $('.bignum').css({'width':'54px','height':'54px','font-size':'24px','padding':'9px 0'});
    $('.square').css({'width':'54px','height':'54px'});
    $('.pencilmarks').css({'width':'54px','font-size':'11px'});
    $('#buttons').css({'width':'501px'});
    $('.left_col').css({'display':'none'});
    $('#swordfish').css({'display':'none'});
    //    $('.grid_table').css({'margin-top':'10px'});
    $('#btn_smaller').css({'display':'inline'});
    $('#btn_bigger').css({'display':'none'});

    var grid = getActiveGrid();
    if (grid.acceptingInput()) {
        grid.moveCursorToSquare(xyToRC(grid.cursorXY));
    }

    $('#overlay').css({'top':'12','left':'20'});
    //    $('#grid_overlay').css({'width':'400','height':'400'});
    gridSize = 2;
}

function shrinkGrid() {
    if (gridSize == 1) return;
    $('.bignum').css({'width':'40px','height':'40px','font-size':'20px','padding':'6px 0'});
    $('.square').css({'width':'40px','height':'40px'});
    $('.pencilmarks').css({'width':'40px','font-size':'9px'});
    $('#buttons').css({'width':'375px'});
    $('.left_col').css({'display':''});
    $('#swordfish').css({'display':''});
    //    $('.grid_table').css({'margin-top':'0px'});
    $('#btn_smaller').css({'display':'none'});
    $('#btn_bigger').css({'display':''});

    var grid = getActiveGrid();
    if (grid.acceptingInput()) {
        grid.moveCursorToSquare(xyToRC(grid.cursorXY));
    }

    $('#overlay').css({'top':'70','left':'135'});
    //    $('#grid_overlay').css({'width':'500','height':'500'});
    gridSize = 1;
}

FishyGrid.prototype.exportPuzzle = function() {
    var puzzleStr = '';
    for (var i=0; i<81; i++) {
        puzzleStr += this.getNumAt(i);
    }
    return puzzleStr;
}

FishyGrid.prototype.exportMarkSigs = function() {
    if (!this.autoHelp) {
        this.genPencilMarks();
        return this.pencilMarks.sigs;
    }
    // otherwise...
    var sigs = new Array(81);
    for (var i=0; i<81; i++) {
        // merge auto-gen pencilmarks and user updates
        sigs[i] = this.pencilMarks.sigs[i] - (this.pencilMarks.user[i] & this.pencilMarks.sigs[i]);
    }
    return sigs.join(",");
}

FishyGrid.prototype.colorPrincipals = function(principals) {
    for (var i=0; i<principals.length-1; i++) {
        var squares = principals[i].split(",");
        for (var j=0; j<squares.length; j++) {
            var rc = toRC(squares[j]);
            this.cellref(rc).addClass('principal_'+i);
        }
    }
}

FishyGrid.prototype.colorCandidateHint = function(sq,group) {
    var rc = toRC(sq);
    this.cellref(rc).addClass('reduced_'+group);
}

FishyGrid.prototype.colorHint = function(sq) {
    var rc = toRC(sq);
    this.cellref(rc).addClass('hint');
}

FishyGrid.prototype.clearHintColoring = function() {
    for (var i=0; i<81; i++) {
        var rc = toRC(i);
        this.cellref(rc).removeClass('hint principal_0 principal_1 reduced_0 reduced_1');
    }
}

