//
// kenken.js
//
// jQuery script for the KenKen web interface.
//
// Author: Johannes Marbach
//
// Copyright (c) 2008, 2009, rapidrabbit GbR.
// All rights reserved.
//

// Note: To speed things up we use static size arrays for the row and
// column status and the clustermap. Therefore we need to be careful
// not to extend loops beyond the current dimension.

// Global variables
var leveldir = 'levels/';
var number_of_levels = 10;
var dimension;
var clusters;
var rows = new Array(new Array(), new Array(), new Array(), new Array(), new Array(), new Array(), new Array(), new Array(), new Array());
var columns = new Array(new Array(), new Array(), new Array(), new Array(), new Array(), new Array(), new Array(), new Array(), new Array());
var clustermap = new Array(new Array(), new Array(), new Array(), new Array(), new Array(), new Array(), new Array(), new Array(), new Array());
var mark_rows_columns = false;
var mark_clusters = false;
var row_column_criterions = false;
var cluster_criterions = false;
var mins, secs, timer_id = null;
var log_script = 'http://www.rapidrabbit.de/jj/cgi-bin/log.cgi';
var log_data = new Object();
var user_id = null;
var level = null;
var file = null;
var cross_domain_post_loaded = false;

// Bind events (after the DOM is loaded)
$(document).ready(function() {
    // Generate and save user id in cookie
    user_id = get_cookie('user_id');
    if (! user_id)
    {
        user_id = Math.random();
        document.cookie = 'user_id=' + user_id + ';expires=01/01/3000 00:00:00';
    }
    
    // Just play: Choose a random level and load file
    $('#just-play-button').bind('click', function() {
        level = Math.floor(Math.random()*number_of_levels+1);
        $('#level-selection').val(level);
        getkenken(level);
    });
    $('#just-play-button').hover(function() {
        $('#just-play-button').addClass('button-hover');
    }, function() {
        $('#just-play-button').removeClass('button-hover');
    });
    
    // Play: Get selected level and load file
    $('#play-button').bind('click', function() {
        level = $('#level-selection').val();
        getkenken(level);
    });
    $('#play-button').hover(function() {
        $('#play-button').addClass('button-hover');
    }, function() {
        $('#play-button').removeClass('button-hover');
    });
    
    // Blank: Reset everything
    $('#blank-button').bind('click', function() {
        // Clear all cells
        $('.cell-value').empty();
        
        // Remove solution image
        $('.overlayer').empty();
        
        // Reset row, column and cluster markers
        if (mark_rows_columns)
        {
            $('.row-marker').addClass('row-marker-set');
            $('.column-marker').addClass('column-marker-set');
        }
        if (mark_clusters)
            $('.cell-bottom').addClass('cell-wrong');
        
        // Reset arrays
        init_rows_columns();
        init_clusters();
        
        // Restart timer
        stop_timer(timer_id);
        timer_id = start_timer();
        
        log('restart');
    });
    $('#blank-button').hover(function() {
        $('#blank-button').addClass('button-hover');
    }, function() {
        $('#blank-button').removeClass('button-hover');
    });
    
    // 'Mark wrong rows/columns' option
    $('#mark-rows-columns-button').bind('click', toggle_mark_rows_columns);
    $('#mark-rows-columns-button').hover(function() {
        $('#mark-rows-columns-button').addClass('option-button-hover');
    }, function() {
        $('#mark-rows-columns-button').removeClass('option-button-hover');
    });
    
    // 'Mark wrong clusters' option
    $('#mark-clusters-button').bind('click', toggle_mark_clusters);
    $('#mark-clusters-button').hover(function() {
        $('#mark-clusters-button').addClass('option-button-hover');
    }, function() {
        $('#mark-clusters-button').removeClass('option-button-hover');
    });
    
    // Alert IE 6 users
    if (navigator.userAgent.indexOf('MSIE 6') != -1)
    {
        var msg = 'Dear user,\n\n'
        msg += 'You are using Microsoft Internet Explorer 6. To get a full\n';
        msg += 'taste of this game please upgrade to Internet Explorer 7 or\n';
        msg += 'get a standard compliant and secure browser like Firefox\n';
        msg += 'or Opera.';
        alert(msg);
    }
    
    // Implement keyboard navigation (not for IE6)
    if (navigator.userAgent.indexOf('MSIE 6') == -1)
    {
        $('#m0-0').addClass('cell-selected');
        $(document).keydown(function(event)
        {
            // Get keycode
            var keycode = event.charCode ? event.charCode :
                          event.keyCode ? event.keyCode : 0;                        
            
            // Get current selected id and coordinates
            var id = $('.cell-selected').attr('id');
            var coords = id.split('-');
            coords[0] = coords[0].substring(1);
            
            if (keycode >= 37 && keycode <= 40) // Arrow key
            {
                // Move "cursor"
                switch(keycode)
                {
                    case 37: // Go left
                        if (coords[1] > 0)
                        {
                            $('#' + id).removeClass('cell-selected');
                            id = 'm' + coords[0] + '-' + --coords[1];
                            $('#' + id).addClass('cell-selected');
                        }
                        break;
                    case 38: // Go up
                        if (coords[0] > 0)
                        {
                            $('#' + id).removeClass('cell-selected');
                            id = 'm' + --coords[0] + '-' + coords[1];
                            $('#' + id).addClass('cell-selected');
                        }
                        break;
                    case 39: // Go right
                        if (coords[1] < dimension - 1)
                        {
                            $('#' + id).removeClass('cell-selected');
                            id = 'm' + coords[0] + '-' + ++coords[1];
                            $('#' + id).addClass('cell-selected');
                        }
                        break;
                    case 40: // Go down
                        if (coords[0] < dimension - 1)
                        {
                            $('#' + id).removeClass('cell-selected');
                            id = 'm' + ++coords[0] + '-' + coords[1];
                            $('#' + id).addClass('cell-selected');
                        }
                        break;
                }
            }
            else // Other keys
            {
                if (keycode >= 49 && keycode <= 57 ||
                    keycode >= 97 && keycode <= 105) // Number
                {
                    var new_val = keycode <= 57 ? keycode - 48 : keycode - 96;
                    if (new_val <= dimension)
                    {
                        var id = '#c' + coords[0] + '-' + coords[1];
                        var old_val = parseInt($(id).text());
                        $(id).html(new_val);
                        
                        // Perform checks for updated cell
                        check_row_column(old_val, new_val, coords[0], coords[1]);
                        check_cluster(old_val, new_val, coords[0], coords[1]);
                        
                        // Check for solution
                        check_solution()
                    }
                }
                else if (keycode == 8 || keycode == 46) // Backspace / Delete
                {
                    var id = '#c' + coords[0] + '-' + coords[1];
                    var old_val = parseInt($(id).text());
                    $(id).html(0);
                    
                    // Perform checks for updated cell
                    check_row_column(old_val, 0, coords[0], coords[1]);
                    check_cluster(old_val, 0, coords[0], coords[1]);
                }
            }
        });
    }
});

// Load a game
function getkenken(level)
{
    var listfile = leveldir + 'level' + level + '.xml';
    
    $.get(listfile, function(data) {
        // Pick a random file
        var count = $(data).find('number').text();
        var find_string = 'level[@id=' + Math.floor(Math.random()*count) +']';
        file = $(data).find(find_string).attr('file');
        file = leveldir + file;
        
        // Load game info from .xml
        file_xml = file.substring(0, file.lastIndexOf('.') + 1) + 'xml';
        $.get(file_xml, function(data) {
            dimension = $(data).find('dimension').text();
            clusters = new Array();
            $(data).find('cluster').each(function() {
                // Here's how we define the cluster array:
                // [0]   Operation
                // [1]   Result
                // [2]   Current result
                // [3]   Length
                // [4]   Current length
                // [5]   Solution status
                // [6:]  Cell coordinates
                cluster = new Array();
                op_obj = $(this).find('operation');
                cluster[0] = op_obj.attr('type');
                cluster[1] = op_obj.attr('result');
                if (cluster[0] == '*')
                    cluster[2] = 1;
                else
                    cluster[2] = 0;
                cluster[3] = cluster[4] = 0;
                cluster[5] = false;
                var i, j;
                $(this).find('point').each(function() {
                    i = $(this).attr('y');
                    j = $(this).attr('x')
                    cluster.push(i);
                    cluster.push(j);
                    clustermap[i][j] = clusters.length;
                    cluster[3]++;
                });
                clusters.push(cluster);
            });
            
            // Load game .html
            $('#kenken').load(file, function() {
                // Load middle layer directly into top cells for IE 6
                if (navigator.userAgent.indexOf('MSIE 6') != -1)
                {
                    var top_id, mid_id, html;
                    for (var i=0; i<dimension; i++)
                    {
                        for (var j=0; j<dimension; j++)
                        {
                            mid_id = '#m' + i + '-' + j;
                            if ($(mid_id).text().length > 0)
                            {
                                top_id = '#' + i + '-' + j;
                                html = '<div class="result">' + $(mid_id).text() + '</div>';
                                $(top_id).append(html);
                            }
                        }
                    }
                }
                
                // Hide the middle and bottom layer for IE 6 and lower
                if (navigator.userAgent.indexOf('MSIE 6') != -1)
                {
                    $('.bottom-layer').addClass('hidden');
                    $('.middle-layer').addClass('hidden');
                }
                
                // Toggle buttons visible (Mark wrong clusters is disabled for IE 6 and lower)
                $('#blank-button').removeClass('hidden');
                $('#mark-rows-columns-button').removeClass('hidden');
                if (navigator.userAgent.indexOf('MSIE 6') == -1)
                    $('#mark-clusters-button').removeClass('hidden');
                
                // Initialize row and column arrays
                init_rows_columns();
                
                // Initially mark rows, columns and clusters, if option set
                if (mark_rows_columns)
                {
                    $('.row-marker').addClass('row-marker-set');
                    $('.column-marker').addClass('column-marker-set');
                }
                if (mark_clusters)
                {
                    $('.cell-bottom').addClass('cell-wrong');
                }
                
                // Bind hover event
                $('.cell-click').hover(function() {
                    $('.cell-selected').removeClass('cell-selected'); // Remove "keyboard-cursor"
                    id = '#m' + $(this).attr('id');
                    $(id).addClass('cell-selected');
                }, function() {
                    id = '#m' + $(this).attr('id');
                    $(id).removeClass('cell-selected');
                });
                
                // Preselect first field
                $('#m0-0').addClass('cell-selected');
                
                // Bind click event
                $('.cell-click').bind('click', function() {
                    // Update value on click
                    var i = $(this).attr('id').charAt(0);
                    var j = $(this).attr('id').charAt(2);
                    var id = '#c' + i + '-' + j;
                    var val_old = parseInt($(id).text());
                    var val_new;
                    if (! val_old)
                        val_new = 1;
                    else if (val_old == dimension)
                        val_new = 0;
                    else
                        val_new = val_old + 1;
                    $(id).html(val_new);
                    
                    // Perform checks for updated cell
                    check_row_column(val_old, val_new, i, j);
                    check_cluster(val_old, val_new, i, j);
                    
                    // Check for solution
                    check_solution()
                });
                
                // (Re)start timer
                if (timer_id)
                    stop_timer(timer_id);
                timer_id = start_timer();
                
                if (! cross_domain_post_loaded)
                {
                    $.getScript('jquery.cross-domain-post.js', function()
                    {
                        cross_domain_post_loaded = true;
                        log('start');
                    });
                }
                else log('start');
            });
        });
    });
}

// Initialize the row and column arrays
function init_rows_columns()
{
    for (var i=0; i<dimension; i++)
    {
        for (var j=0; j<dimension; j++)
            rows[i][j] = columns[i][j] = 0;
        rows[i][j] = columns[i][j] = false;
    }
}

// Initialize the cluster array
function init_clusters()
{
    for (var i=0; i<clusters.length; i++)
    {
        clusters[i][5] = false
        if (clusters[i][0] == '*')
            clusters[i][2] = 1;
        else
            clusters[i][2] = 0;
        clusters[i][4] = 0;
    }
}

// Check and mark row and column on cell update
function check_row_column(val_old, val_new, i, j)
{
    if (val_new)
    {
        rows[i][val_new-1]++;
        columns[j][val_new-1]++;
    }
    if (val_old)
    {
        rows[i][val_old-1]--;
        columns[j][val_old-1]--;
    }
    
    // Check row
    rows[i][dimension] = true;
    for (var k=0; k<dimension; k++)
        if (rows[i][k] != 1)
        {
            rows[i][dimension] = false;
            break;
        }
    
    // Check column
    columns[j][dimension] = true;
    for (var k=0; k<dimension; k++)
        if (columns[j][k] != 1)
        {
            columns[j][dimension] = false;
            break;
        }
    
    // Mark row and column
    if (mark_rows_columns)
    {
        var id;
        
        id = '#r' + i;
        if (rows[i][dimension])
            $(id).removeClass('row-marker-set');
        else
            $(id).addClass('row-marker-set');
        
        id = '#c' + j;
        if (columns[j][dimension])
            $(id).removeClass('column-marker-set');
        else
            $(id).addClass('column-marker-set');
    }
}

// Check and mark cluster on cell update
function check_cluster(val_old, val_new, i, j)
{
    var idx = clustermap[i][j];
    var op = clusters[idx][0];
    
    // Update current cluster length
    if (! val_new)
        clusters[idx][4]--;
    if (! val_old)
        clusters[idx][4]++;
    
    // Check cluster
    switch (op)
    {
        case '+':
            if (val_old)
                clusters[idx][2] -= val_old;
            if (val_new)
                clusters[idx][2] += val_new;
            
            if (clusters[idx][1] != clusters[idx][2] || clusters[idx][3] != clusters[idx][4])
                clusters[idx][5] = false;
            else
                clusters[idx][5] = true;
            
            break;
        case '-':
            if (! val_new)
            {
                clusters[idx][5] = false;
                break;
            }
            
            var id, val;
            
            if (clusters[idx][6] != i || clusters[idx][7] != j)
                id = '#c' + clusters[idx][6] + '-' + clusters[idx][7];
            else
                id = '#c' + clusters[idx][8] + '-' + clusters[idx][9];
            val = $(id).text();
            if (! val)
                break;
            
            else if (val >= val_new)
                clusters[idx][2] = val - val_new;
            else
                clusters[idx][2] = val_new - val;
            
            if (clusters[idx][1] != clusters[idx][2] || clusters[idx][3] != clusters[idx][4])
                clusters[idx][5] = false;
            else
                clusters[idx][5] = true;
            
            break;
        case '*':
            if (val_old)
                clusters[idx][2] /= val_old;
            if (val_new)
                clusters[idx][2] *= val_new;
            
            if (clusters[idx][1] != clusters[idx][2] || clusters[idx][3] != clusters[idx][4])
                clusters[idx][5] = false;
            else
                clusters[idx][5] = true;
            
            break;
        case '/':
            if (! val_new)
            {
                clusters[idx][5] = false;
                break;
            }
            
            var id, val;
            
            if (clusters[idx][6] != i || clusters[idx][7] != j)
                id = '#c' + clusters[idx][6] + '-' + clusters[idx][7];
            else
                id = '#c' + clusters[idx][8] + '-' + clusters[idx][9];
            val = $(id).text();
            if (! val)
                break;
            
            if (val >= val_new)
                clusters[idx][2] = val / val_new;
            else
                clusters[idx][2] = val_new / val;
            
            if (clusters[idx][1] != clusters[idx][2] || clusters[idx][3] != clusters[idx][4])
                clusters[idx][5] = false;
            else
                clusters[idx][5] = true;
            
            break;
        default: // NOP, i.e. one-cell cluster
            clusters[idx][2] = val_new;
            
            if (clusters[idx][1] != clusters[idx][2])
                clusters[idx][5] = false;
            else
                clusters[idx][5] = true;
            
            break;
    }
    
    // Mark cluster
    if (mark_clusters)
    {
        var id;
        
        if (clusters[idx][5])
            for (var k=6; k<clusters[idx].length; k++)
            {
                id = '#b' + clusters[idx][k] + '-' + clusters[idx][++k];
                $(id).removeClass('cell-wrong');
            }
        else
            for (var k=6; k<clusters[idx].length; k++)
            {
                id = '#b' + clusters[idx][k] + '-' + clusters[idx][++k];
                $(id).addClass('cell-wrong');
            }
    }
}

// Check for solution
function check_solution()
{
    row_column_criterions = true;
    for (var k=0; k<dimension; k++)
    {
        if (! rows[k][dimension] || ! columns[k][dimension])
        {
            row_column_criterions = false;
            break;
        }
    }
    
    if (row_column_criterions)
    {
        cluster_criterions = true;
        for (var k=0; k<clusters.length; k++)
        {
            if (! clusters[k][5])
            {
                cluster_criterions = false;
                break;
            }
        }
        
        if (row_column_criterions && cluster_criterions)
        {
            $('.cell-selected').removeClass('cell-selected');
            var img = '<img src="images/solved-' + dimension + '.png">';
            $('#overlayer').html(img);
            
            stop_timer(timer_id); // Stop timer
            
            log('finish');
        }
    }
}

// Toggle 'Mark wrong rows/columns' option
function toggle_mark_rows_columns()
{
    if (! mark_rows_columns)
    {
        mark_rows_columns = true;
        if ($('#0-0').length > 0) // Only mark if the puzzle is present
            mark_all_rows_columns();
        $('#mark-rows-columns-button').addClass('option-button-pressed');
    }
    else
    {
        mark_rows_columns = false;
        $('.row-marker').removeClass('row-marker-set');
        $('.column-marker').removeClass('column-marker-set');
        $('#mark-rows-columns-button').removeClass('option-button-pressed');
    }
}

// Toggle 'Mark wrong clusters' option
function toggle_mark_clusters()
{
    if (! mark_clusters)
    {
        mark_clusters = true;
        if ($('#0-0').length > 0) // Only mark if the puzzle is present
            mark_all_clusters();
        $('#mark-clusters-button').addClass('option-button-pressed');
    }
    else
    {
        mark_clusters = false;
        $('.cell-wrong').removeClass('cell-wrong');
        $('#mark-clusters-button').removeClass('option-button-pressed');
    }
}

// Mark all wrong rows and columns
function mark_all_rows_columns()
{
    var id;
    
    for (var i=0; i<dimension; i++)
    {
        id = '#r' + i;
        if (! rows[i][dimension])
            $(id).addClass('row-marker-set');
            
        id = '#c' + i;
        if (! columns[i][dimension])
            $(id).addClass('column-marker-set');
    }
}

// Mark all wrong clusters
function mark_all_clusters()
{
    var id;
    
    for (var i=0; i<clusters.length; i++)
        if (! clusters[i][5])
            for (var k=6; k<clusters[i].length; k++)
            {
                id = '#b' + clusters[i][k] + '-' + clusters[i][++k];
                $(id).addClass('cell-wrong');
            }
}

function start_timer()
{   
    mins = secs = 0;
    $('#timer').html('Time: 0s');
    
    return setInterval(function() {
        if (secs == 59)
            mins++;
        secs = (secs + 1) % 60;
        
        var time_string = 'Time: ';
        if (mins == 0)
            time_string += secs + 's';
        else
        {
            time_string += mins + ':';
            if (secs < 10)
                time_string += '0'
            time_string += secs + 'min';
        }
        $('#timer').html(time_string);
    }, 1000);
}

function stop_timer(timer_id)
{
    clearInterval(timer_id);
}

function get_cookie(name)
{
    if (document.cookie)
    {
        var start = document.cookie.indexOf(name + '=');
        if (start != -1)
        {
            start += name.length + 1;
            var end = document.cookie.indexOf(';', start);
            if (end == -1) end=document.cookie.length;
            return unescape(document.cookie.substring(start, end));
        }
    }
    return null
}

function log(action)
{
    log_data['user_id'] = user_id;
    log_data['level'] = level;
    log_data['file'] = file;
    log_data['action'] = action;
    $.cross_domain_post(log_script, log_data);
}

