/*
 * ui.js
 * 
 * Implementation of the Yukendo webgame user interface.
 * 
 * Author: Johannes Marbach
 * 
 * Copyright (c) 2008, 2009, rapidrabbit GbR.
 * All rights reserved.
 *
 */

preload_images(); // Preload images

// Globals
var dimension = null;          // Current puzzle dimension
var numbers = new Array();     // Current number population
var map = new Array();         // Maps cells to clusters
for (var i = 0; i < 10; ++i) { // Maximum dimension limited to 10!!
    numbers[i] = new Array();
    map[i] = new Array();
}
var rows = new Array();        // Row states
var columns = new Array();     // Column states
var powers = new Array(1, 2, 4, 8, 16, 32, 64, 128, 256, 512);
var clusters = new Array       // Array of clusters
var mark_clusters = false;     // Mark wrong clusters
var mark_rows_columns = false; // Mark wrong rows / columns
var cursor = new Cursor();     // Cursor object
var timer = new Timer();       // Timer object
var paused = false;            // Game paused?

$(document).ready(function() {
    // Hide game area, additional controls and timer
    $('#game-area').hide();
    $('#blank-button').hide();
    $('#mark-rows-columns-button').hide();
    $('#mark-clusters-button').hide();
    $('#pause-button').hide();
    $('#timer').hide();
    if (navigator.appName == 'Microsoft Internet Explorer') { // IE hack
        $('td.frame-left').next().children().each(function() {
            $(this).hide();
        });
    }
    
    // Drop clicks on level select
    $('#level-select').click(function() {return false;});
    
    // Catch changes of level select
    $('#level-select').change(function() {
        $(this).blur();
        $('#play-button').click();
    });
    
    // Catch clicks on play button
    $('#play-button').click(function() {
        // Show additional controls (only important on first run)
        $('#blank-button').fadeIn('slow');
        $('#mark-rows-columns-button').fadeIn('slow');
        $('#mark-clusters-button').fadeIn('slow');
        $('#pause-button').fadeIn('slow');
        
        // Reset pause button
        $('#pause-button').removeClass('pressed');
        paused = false;
        
        // Fade / Load puzzle
        $('#tutorial').fadeOut('slow', function() {
        $('#timer').fadeOut('slow');
        $('#game-area').fadeOut('slow', function() {
            load_puzzle($('#level-select').val());
        });
        if (navigator.appName == 'Microsoft Internet Explorer') {
            // IE hack
                $('td.frame-left').next().children().each(function() {
                $(this).fadeOut('slow');
                });
            }
        });
    });
    
    // Catch clicks on play random button
    $('#play-random-button').click(function() {
        $('#level-select').val(1 + parseInt(Math.random() * 10));
        $('#play-button').click();
    });
    
    // Catch clicks on blank button
    $('#blank-button').click(function() {
        initialize(); // Reinitialize
    });
    
    // Catch clicks on pause / continue button
    $('#pause-button').click(function() {
        // Deny pause for finished puzzle
        if (cursor.i === null) return false;
        
        if (! paused) {
            // Update settings
            paused = true;
            $(this).addClass('pressed');
            
            timer.stop(); // Stop timer
            
            // Slide down
            $('#slider').animate({
                height: (dimension * 50) + 'px'
            }, 1000);
        } else {
            // Slide up
            $('#slider').animate({height: '0px'}, 1000, function() {
                timer.start(); // Continue timer
                
                paused = false; // Update settings
            });
            
            $(this).removeClass('pressed'); // Unpress button
        }
    });
    
    // Catch clicks on mark rows / columns button
    $('#mark-rows-columns-button').click(function() {
        if (! mark_rows_columns) {
            // Update settings
            mark_rows_columns = true;
            $(this).addClass('pressed');
            
            // Set markers
            for (var i = 0; i < dimension; ++i) {
                update_row_marker(i);
                update_column_marker(i);
            }
        } else {
            // Update settings
            mark_rows_columns = false;
            $(this).removeClass('pressed');
            
            $('.marker-set').removeClass('marker-set'); // Unset markers
        }
    });
    
    // Catch clicks on mark clusters button
    $('#mark-clusters-button').click(function() {
        // Update settings
        if (! mark_clusters) {
            mark_clusters = true;
            $(this).addClass('pressed');
        } else {
            mark_clusters = false;
            $(this).removeClass('pressed');
        }
        
        // Update display
        for (var i = 0; i < clusters.length; ++i)
            update_display_cluster(clusters[i]);
        
        // Update cursor
        if (cursor.i != null) cursor.enter(cursor.i, cursor.j);
    });
    
    // Implement keyboard control of cursor
    $(document).keydown(function(event) {
        // Get keycode
        var keycode = event.charCode ? event.charCode :
            event.keyCode ? event.keyCode : 0;
        
        if (keycode >= 37 && keycode <= 40) { // Arrow key
            if (paused) return false; // Drop events in paused mode
            
            // Calculate new coordinates
            var i = cursor.i;
            var j = cursor.j;
            
            switch (keycode) {
                case 37: // Go left
                    if (j > 0) --j;
                    break;
                case 38: // Go up
                    if (i > 0) --i;
                    break;
                case 39: // Go right
                    if (j < dimension - 1) ++j;
                    break;
                case 40: // Go down
                    if (i < dimension - 1) ++i;
                    break;
            }
            
            // Move cursor
            if (i != cursor.i || j != cursor.j) cursor.enter(i, j);
            
            return false; // Stop further event propagation
        } else {
            if (keycode >= 49 && keycode <= 57 ||
                keycode >= 97 && keycode <= 105) { // Number key
                if (paused) return false; // Drop events in paused mode
                
                // Get number from keycode
                while (keycode > 9) keycode -= 48;
                
                if (keycode <= dimension) // Insert number
                    update(keycode);
                
                return false; // Stop further event propagation
            } else if (keycode == 8 || keycode == 46) {
                if (paused) return false; // Drop events in paused mode
                
                // Backspace / Delete
                update(0); // Insert zero
                return false; // Stop further event propagation
            }
        }
    });
    // Startet ein Level 4 Spiel
    //$('#level-select').val(4);
    //$('#play-button').click();

});

// Loads a random puzzle from the specified level.
function load_puzzle(level) {
    // Load list of puzzles
    $.ajax({
        type: 'GET',
        url: '/yukendo/levels/level-' + level + '.xml',
        dataType: 'xml',
        success: function(data) {
            var root = $(data).find('puzzles'); // Get root node
            
            // Pick a random puzzle
            var n = parseInt(Math.random() * root.attr('number'));
            var file = '/yukendo/levels/level-' + level + '/' +
                root.children().eq(n).attr('file');
            
            // Load puzzle XML
            $.ajax({
                type: 'GET',
                url: file + '.xml',
                dataType: 'xml',
                success: function(data) {
                    // Get root node
                    var root = $(data).find('yukendo-puzzle');
                    
                    dimension = parseInt(root.attr('dimension'));
                    
                    // Reset clusters array
                    clusters.splice(0, clusters.length);
                    
                    // Parse cluster data
                    root.children().each(function() {
                        // Initialize cluster
                        var cluster =
                            new Cluster($(this).attr('operation'),
                            parseInt($(this).attr('result')));
                        
                        // Set cluster cells
                        $(this).children().each(function() {
                            var i = $(this).attr('y');
                            var j = $(this).attr('x');
                            cluster.cells.push(new Array(i, j));
                            
                            map[i][j] = clusters.length;
                        });
                        
                        clusters.push(cluster); // Extend clusters array
                    });
                    
                    // Load puzzle HTML
                    $('#puzzle').load(file + '.html', function() {
                        // Catch clicks on cells
                        $('.border-layer table tr td')
                            .click(function() {update();});
                        
                        // Implement mouse control of cursor movement
                        $('.border-layer table tr td')
                            .mouseenter(function() {
                            // Drop events in paused mode
                            if (paused) return false;
                            
                            var ij = get_ij($(this));
                            cursor.enter(ij[0], ij[1])
                        });
                        
                        initialize(); // Initialize
                    });
                }
            });
        }
    });
}

// Cluster class
function Cluster(operation, result) {
    // Set operation and result
    if (! operation) this.operation = null
    else this.operation = operation;
    this.result = result;
    
    this.cells = new Array(); // Initialize array of cells
}

// Cursor class
function Cursor() {
    this.i = this.j = null; // Initialize coordinates
    
    // Enter new cell
    this.enter = function(i, j) {
        this.leave(); // Unset cursor
        
        // Update coordinates
        this.i = i;
        this.j = j;
        
        // Get image basedir
        if (mark_clusters &&
            ! clusters[map[this.i][this.j]].state)
            var dir = 'wrong-cluster';
        else var dir = 'plain';
        
        // Set cursor
        get_cell('.operation-cursor-layer', this.i, this.j).css(
            'background-image','url(/yukendo/images/numbers/cursor-' + dir +
            '/' + numbers[this.i][this.j] + '.png)');
    };
    
    // Leave current cell
    this.leave = function() {
        // Unset cursor
        get_cell('.operation-cursor-layer', this.i, this.j).css(
            'background-image', 'none');
        
        this.i = this.j = null; // Update coordinates
    };
}

// Timer class
function Timer() {
    // Initialize time variables and id
    this.h = this.m = this.s = this.id = null;
    
    // Start timer
    this.start = function() {
        if (this.id) this.stop(); // Stop previous run
        
        if (! paused) this.h = this.m = this.s = 0 // Reset time
        
        this.update_display(); // Initialize display
        
        // Set timeout
        var obj = this;
        this.id = setInterval(function() {
            obj.update_time();
            obj.update_display();
        }, 1000);
    }
    
    // Update time
    this.update_time = function() {
        // Increment time
        if (this.s == 59) {
            this.s = 0;
            if (this.m == 59) {
                this.m = 0;
                ++this.h;
            } else ++this.m;
        } else ++this.s
    }
    
    // Update display
    this.update_display = function() {
        // Build string
        var str = ''
        if (this.h > 0) str += this.h + ':';
        if (this.m < 10) str += '0';
        str += this.m + ':';
        if (this.s < 10) str += '0';
        str += this.s;
        
        // Insert string
        $('#timer').html(str);
    }
    
    // Stop timer
    this.stop = function() {
        clearInterval(this.id);
    }
}

// Undertakes initializing actions.
function initialize() {
    // Initialize arrays
    for (var i = 0; i < dimension; ++i) {
        rows[i] = columns[i] = 0;
        for (var j = 0; j < dimension; ++j) numbers[i][j] = 0;
    }
    
    // Initialize clusters
    for (var i = 0; i < clusters.length; ++i) {
        clusters[i].current_result =
            (clusters[i].operation == '*') ? 1 : 0;
        clusters[i].current_length = 0;
        clusters[i].state = false;
    }
    
    // Initialize display
    if (mark_clusters) var dir = 'wrong-cluster';
    else var dir = 'plain';
    $('.value-layer table tr td').css('background-image',
        'url(/yukendo/images/numbers/' + dir + '/0.png)');
    
    // Initialize markers
    if (mark_rows_columns) {
        $('.row-marker-layer table tr td').addClass('marker-set');
        $('.column-marker-layer table tr td').addClass('marker-set');
    }
    
    cursor.enter(0, 0); // Initialize cursor
    
    timer.start(); // (Re)start timer
    
    // Dectivate over layer
    $('#over-layer').width(0);
    $('#over-layer').height(0);
    
    // Resize slider
    $('#slider').width(dimension * 50);
    $('#slider').height(0);
    
    // Fade-in game
    $('#game-area').fadeIn('slow');
    $('#timer').fadeIn('slow');
    if (navigator.appName == 'Microsoft Internet Explorer') { // IE hack
        $('td.frame-left').next().children().each(function() {
            $(this).fadeIn('slow');
        });
    }
}

// Returns a selector to the cell (i, j) in the specified layer.
function get_cell(layer, i, j) {
    return $(layer + ' table tr:eq(' + i + ') td:eq(' + j + ')');
}

// Extracts and returns (i, j) array from cell object.
function get_ij(obj) {
    ij = obj.attr('id').split('-');
    
    ij[0] = parseInt(ij[0]);
    ij[1] = parseInt(ij[1]);
    
    return ij;
}

// Updates currently selected cell.
function update(n) {
    update_internals(n); // Update internals for selected cell
    
    // Update display
    if (mark_clusters)
        update_display_cluster(clusters[map[cursor.i][cursor.j]]);
    else {
        get_cell('.value-layer', cursor.i, cursor.j).css(
            'background-image', 'url(/yukendo/images/numbers/plain/' +
            numbers[cursor.i][cursor.j] + '.png)');
    }
    
    // Update cursor
    cursor.enter(cursor.i, cursor.j);
    
    // Update markers
    if (mark_rows_columns) {
        update_row_marker(cursor.i);
        update_column_marker(cursor.j);
    }
    
    // Check global state
    if (latin_square()) {
        if (check_clusters()) {
            // Activate over layer
            $('#over-layer').width(dimension * 50);
            $('#over-layer').height(dimension * 50);
            
            cursor.leave(); // Unset cursor
            
            timer.stop(); // Stop timer
        }
    }
}

// Updates internal state of currently selected cell.
function update_internals(n) {
    // Save and increment value
    var o = numbers[cursor.i][cursor.j];
    if (typeof(n) == 'undefined') n = (o + 1) % (dimension + 1);
    numbers[cursor.i][cursor.j] = n
    
    // Update row
    rows[cursor.i] = 0;
    for (var k = 0; k < dimension; ++k) {
        if (numbers[cursor.i][k])
            rows[cursor.i] |= powers[numbers[cursor.i][k] - 1];
        else break;
    }
    
    // Update column
    columns[cursor.j] = 0;
    for (var k = 0; k < dimension; ++k) {
        if (numbers[k][cursor.j])
            columns[cursor.j] |= powers[numbers[k][cursor.j] - 1];
        else break;
    }
    
    // Get cluster from cell
    var cluster = clusters[map[cursor.i][cursor.j]];
    
    // Update cluster length
    if (! o) ++cluster.current_length;
    if (! n) --cluster.current_length;
    
    // Update cluster result
    switch(cluster.operation) {
        case '+':
            cluster.current_result += n - o;
            break;
        case '*':
            if (o) cluster.current_result /= o;
            if (n) cluster.current_result *= n;
            break;
        case null:
            cluster.current_result = n;
            break;
        default: // '-' and '/'
            var a = numbers[cluster.cells[0][0]][cluster.cells[0][1]];
            var b = numbers[cluster.cells[1][0]][cluster.cells[1][1]];
            
            if (cluster.operation == '-') {
                if (a && b)
                    cluster.current_result = a > b ? a - b : b - a;
                else cluster.current_result = 0;
            }
            else { // '/'
                if (a % b == 0) cluster.current_result = a / b;
                else if (b % a == 0) cluster.current_result = b / a;
                else cluster.current_result = 0;
            }
    }
    
    // Update cluster state
    if (cluster.result == cluster.current_result &&
        cluster.cells.length == cluster.current_length)
        cluster.state = true;
    else cluster.state = false;
}

// Updates display of a whole cluster.
function update_display_cluster(cluster) {
    // Get image basedir
    if (mark_clusters && ! cluster.state) var dir = 'wrong-cluster';
    else var dir = 'plain';
    
    // Update display of all cells within the cluster
    for (var k = 0; k < cluster.cells.length; ++k) {
        get_cell('.value-layer', cluster.cells[k][0],
            cluster.cells[k][1]).css('background-image',
            'url(/yukendo/images/numbers/' + dir + '/' +
            numbers[cluster.cells[k][0]][cluster.cells[k][1]] +
            '.png)');
    }
}

// Updates row marker of i-th row.
function update_row_marker(i) {
    var cell = $('.row-marker-layer table tr td:eq(' + i + ')');
    if (rows[i] != powers[dimension] - 1) cell.addClass('marker-set');
    else cell.removeClass('marker-set');
}

// Updates column marker of i-th row.
function update_column_marker(i) {
    var cell = $('.column-marker-layer table tr td:eq(' + i + ')');
    if (columns[i] != powers[dimension] - 1) cell.addClass('marker-set');
    else cell.removeClass('marker-set');
}

// Checks latin square criterions.
function latin_square() {
    for (var i = 0; i < dimension; ++i)
        if (rows[i] != powers[dimension] - 1 ||
            columns[i] != rows[i]) return false;
    return true;
}

// Checks all cluster criterions.
function check_clusters() {
    for (var i = 0; i < clusters.length; ++i)
        if (! clusters[i].state) return false;
    return true;
}

// Preloads all images.
function preload_images() {
    $.image_preload(
        // Row / Column marker
        '/yukendo/images/marker.png',
        // Hover effects
        '/yukendo/images/buttons/blank-hover_en.png',
        '/yukendo/images/buttons/mark-clusters-hover_en.png',
        '/yukendo/images/buttons/mark-rows-columns-hover_en.png',
        '/yukendo/images/buttons/play-hover_en.png',
        '/yukendo/images/buttons/play-random-hover_en.png',
        // Borders
        '/yukendo/images/borders/inner-n-outer-ew.png',
        '/yukendo/images/borders/inner-n-outer-sew.png',
        '/yukendo/images/borders/inner-n-outer-sw.png',
        '/yukendo/images/borders/inner-n-outer-w.png',
        '/yukendo/images/borders/inner-nw-outer-c.png',
        '/yukendo/images/borders/inner-nw-outer-e.png',
        '/yukendo/images/borders/inner-nw-outer-ec.png',
        '/yukendo/images/borders/inner-nw-outer-s.png',
        '/yukendo/images/borders/inner-nw-outer-sc.png',
        '/yukendo/images/borders/inner-nw-outer-se.png',
        '/yukendo/images/borders/inner-nw-outer-sec.png',
        '/yukendo/images/borders/inner-nw.png',
        '/yukendo/images/borders/inner-w-outer-n.png',
        '/yukendo/images/borders/inner-w-outer-ne.png',
        '/yukendo/images/borders/inner-w-outer-ns.png',
        '/yukendo/images/borders/inner-w-outer-nse.png',
        '/yukendo/images/borders/outer-new.png',
        '/yukendo/images/borders/outer-nsew.png',
        '/yukendo/images/borders/outer-nsw.png',
        '/yukendo/images/borders/outer-nw.png');
    
    // Numbers
    var dirs = new Array('cursor-plain', 'cursor-wrong-cluster',
        'plain', 'wrong-cluster');
    for (var i = 0; i < dirs.length; ++i)
        for (var j = 0; j < 10; ++j)
            $.image_preload('/yukendo/images/numbers/' + dirs[i] + '/' + j +
                '.png');
}

