/* Sudoku assistent */

/* requires prior inclusion of prototype.js */

var hints_count = 0;
var check1_count = 0;
var check2_count = 0;
var last_field = [];
var s_history = [];
var s_history_pointer = -1;
var hilight_errors = true;
var short_date = "";
var won = false;
var lang = "de";

var marked_cells = [
    [0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0]
];

/* keep track of active element (e.g. the one with the focus.
   Taken from https://jalbum.net/forum/thread.jspa?messageID=24532 */

var activeElement = null;
function blurHandler(evt) {
    activeElement = null;
}
function focusHandler(evt) {
    var e = evt ? evt : window.event;
    if (!e) return;
    if (e.target)
        activeElement = e.target;
    else if(e.srcElement) activeElement = e.srcElement;
  
}
function loadHandler() {
    var i, j;
    
    for (i = 0; i < document.forms.length; i++)
        for (j = 0; j < document.forms[i].elements.length; j++) {
            document.forms[i].elements[j].onfocus   = focusHandler;
            document.forms[i].elements[j].onblur    = blurHandler;
            document.forms[i].elements[j].onkeydown = jump;
        }
}

/* http://www.faqts.com/knowledge_base/view.phtml/aid/1159/fid/130 */
function setCaretToStart (control) {
  if (control.createTextRange) {
    var range = control.createTextRange();
    range.collapse(true);
    range.select();
  }
  else if (control.setSelectionRange) {
    control.focus();
    control.setSelectionRange(0, 0);
  }
}
/* End of foreign code */

var timer_enabled = false;
var timer_running = false;
var time;

function check() {
    result = true;
    for (n = 0; n < 9 && result; n++){
        result = result && check_row(n);
        result = result && check_col(n);
/*      alert("check"); */
    }
    for (x = 0; x < 3 && result; x++){
        for (y = 0; y < 3 && result; y++){
            result = result && check_block(3*x, 3*y);
        }
    }
    return result;
}

function check_row(row) {
    test = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
    for (i = 0; i < 9; i++){
        test[get_val(i, row)]++;
    }
    for (i = 1; i < 10; i++){
        if (test[i] > 1){
            return false;
        }
    }
    return true;
}

function check_col(col) {
    test = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
    for (i = 0; i < 9; i++){
        test[get_val(col, i)]++;
    }
    for (i = 1; i < 10; i++){
        if (test[i] > 1){
            return false;
        }
    }
    return true;
}

function check_block(a_x, a_y){
    test = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
    for (x = 0; x < 3; x++){
        for (y = 0; y < 3; y++){
            test[get_val(a_x+x, a_y+y)] ++;
        }
    }
    for (i = 1; i < 10; i++){
        if (test[i] > 1){
            return false;
        }
    }
    return true;
}

function get_val(x, y){
    f = $F('s' + x + '' + y);
    if ('' == f){
        res = 0;
    } else {
        res =  parseInt(f);
    }
    if (res){
        return(res);
    } else {
        return 0;
    }
}

function validate() {
    var res = true;
    for (x = 0; x < 9; x++){
        for (y = 0; y < 9; y++){
            r = get_val(x, y);
            if (!r){
                r = 0;
            }
            if (r > 0 && solved[x][y] != r){
                if (hilight_errors){
                    get_item(x, y).style.backgroundColor = "#FF9999";
                    get_item(x, y).parentNode.style.backgroundColor = "#FF9999";

                } else {
                    return false;
                }
                res =  false;
            }
        }
    }
    return res;
}

function solve() {
    for (x = 0; x < 9; x++){
        for (y = 0; y < 9; y++){
            a = $("s" + x + '' + y);
            if (!a.value){
                a.style.color = "red";
                hints_count++;
            }
            a.value = solved[x][y];
        }
    }
}

function get_item(x, y) {
    return $('s' + x + '' + y);
}

function hint() {
    if (!has_zero()){
        return;
    }

    hints_count ++;
    $("time_penalty").innerHTML = time_pretty_print(time_penalty());

    while (true){
        var x = Math.floor(Math.random() * 9);
        var y = Math.floor(Math.random() * 9);
        if (!get_val(x, y)){
            get_item(x, y).value = solved[x][y];
            get_item(x, y).style.color = "blue";
            var commit = [0, solved[x][y], x, y, "blue"];
            if (s_history.length == s_history_pointer + 1){
                s_history.push(commit);
                s_history_pointer ++;

            } else {
                
                s_history = s_history.slice(0, s_history_pointer + 1);
                s_history.push(commit);
                s_history_pointer++;
            }
            reset_background_color();
            show_and_hide();

            return;
        }
    }
}

function has_zero() {
    for (x = 0; x < 9; x++){
        for (y = 0; y < 9; y++){
            if (!get_val(x, y)){
                return true;
            }
        }
    }
    return false;
}

function gui_check() {
    check1_count ++;
    $("time_penalty").innerHTML = time_pretty_print(time_penalty());
    if (check()){
        if (lang == "en"){
            my_alert("No violations of the Sudoku rules found");
        } else {
            my_alert("Keine Verletzungen der Sudoku-Regeln gefunden");
        }
    } else {
        if (lang == "en"){
            my_alert("I'm sorry, you made a mistake");
        } else {
            my_alert("Sie haben einen Fehler gemacht, zwei gleiche Zahlen"
                + " sind in einer Zeile, Spalte oder in einem Block"
                + " eingetragen :-/");
        }
    }
}

function gui_validate() {
    check2_count ++;
    $("time_penalty").innerHTML = time_pretty_print(time_penalty());
    if (validate()){
        if (lang == "en"){
            my_alert("You're completely right by now");
        } else {
            my_alert("Bisher liegen Sie komplett richtig!");
        }
    } else {
        if (lang == "en"){
            my_alert("At least one numbers differs from the" +
                    " correct solution");
        } else {
            my_alert("Mindestens eine Zahl weicht von der richtigen" 
                    + " Loesung ab");
        }
    }
}

function update() {
    store_diff();
    reset_background_color();
    show_and_hide();


    var tmp = hilight_errors;
    hilight_errors = false;

    if (!has_zero() && validate() && (!won)){
        if (lang == "en"){
            my_alert("Congratulations, you won! (Time: " + 
                time_pretty_print(time) + "; with penalty: "
                + time_pretty_print(time+time_penalty()) +
                ")");
        } else {
            my_alert("Glückwunsch, Sie haben gewonnen (Zeit: " +
                time_pretty_print(time) + ", mit Zuschlaegen: " +
                time_pretty_print(time+time_penalty())+ ")" );
        }
        won = true;
        timer_enabled = false;
        if (no_hiscore == 0){
            var url = "/online/hiscore.pl?date=" + short_date + "&score=" +
                (time + time_penalty()) + "&action=check&help=" +
                (hints_count + check1_count + check2_count);

            var ajax = new Ajax.Request (
                    url,
                    {method: 'get',
                    onComplete: prompt_for_hiscore}
                    );
        }

    }
    hilight_errors = tmp;
    clean_characters();
}

function prompt_for_hiscore(response) {
    var r = response.responseText;
    if (r.match("^yes")){
        if (lang == "en"){
            $("msg").innerHTML += " scroll down to add your name"
                + " to the hiscore list!";
        } else {
            $("msg").innerHTML += " Scrollen Sie nach unten um "
                + "sich in die Hiscores einzutragen.";
        }
        location.hash = "#sudoku_hiscore_enter";
    } else {
        if (lang == "en"){
            $("msg").innerHTML += " you can scroll down to submit your name to the hiscore list, however today you are not in the top 12 :(";

        } else {
            $("msg").innerHTML += " Sie können nach unten Scrollen um ihren Namen in die Hiscore-Liste einzufügen. Heute sind Sie aber nicht in den Top 12 :(";
        }
    }
    $("sudoku_hiscore_enter").style.visibility = "visible";
    $("sudoku_hiscore_enter").style.height = "100%";
    $("sudoku_hiscore_enter").style.display = "block";

    $("hi_name").disabled = false;
    $("hi_comment").disabled = false;
    $("hi_send").disabled = false;

    if (r.match("^yes:r")){
        /* code for registration here */
    }

}

function table_to_array() {
    var result = [[], [], [], [], [], [], [], [], []];
    for (var x = 0; x < 9; x++){
        for (var y = 0; y < 9; y++){
            result[x][y] = get_val(x, y);
        }
    }
    return result;
}


function history_forth() {
    // redo function
    if (s_history.length == s_history_pointer + 1){
        // nothing to redo
    //  alert("nothing to redo");
        return;
    }

    s_history_pointer++;
    // apply commit
    var commit = s_history[s_history_pointer];
    var new_val = commit[1];
    var x = commit[2];
    var y = commit[3];
    if (new_val == 0){
        get_item(x, y).value = "";
    } else {
        get_item(x, y).value = new_val;
        if (commit[4]){
            get_item(x, y).style.color = commit[4];
        }

    } 
    last_field = table_to_array();
    show_and_hide();
    reset_background_color();


}

function history_back() {
    // undo function
    // TODO: thorough testing ;)
    if (s_history_pointer < 0){
        // nothing to undo
        return;
    }

    time++;

    // apply commit
    var commit = s_history[s_history_pointer];
    var old_val = commit[0];
    var x = commit[2];
    var y = commit[3];
    //alert("x: " + x + ", y: " + y);
    if (!old_val){
        get_item(x, y).value = "";
    } else {
        get_item(x, y).value = old_val;
        if (commit[4]){
            get_item(x, y).style.color = commit[4];
        }
    }
    s_history_pointer--;
    last_field = table_to_array();
    show_and_hide();
    reset_background_color();
}

function reset_background_color() {
    for (var x = 0; x < 9; x++){
        for (var y = 0; y < 9; y++){
            if (! get_item(x, y).readOnly && !marked_cells[x][y]){
                get_item(x, y).style.backgroundColor = "white";
                get_item(x, y).parentNode.style.backgroundColor = "white";
            }
        }
    }
}

function show_and_hide() {
    if (s_history.length == s_history_pointer + 1){
        $("redo").disabled = true;
    } else {
        $("redo").disabled = false;
    }

    if (s_history_pointer == -1){
        $("undo").disabled = true;
    } else {
        $("undo").disabled = false;
    }
}

function store_diff() {
    var new_field = table_to_array();
    for (var x = 0; x < 9; x++){
        for (var y = 0; y < 9; y++){
            if (last_field[x][y] != new_field[x][y]){
                // store diffs
                // commit looks like this: 
                //[old val, new val, x, y, color].
                // (color = new color)
                var commit = [last_field[x][y],
                    new_field[x][y], x, y, ''];
                var c = get_item(x, y).style.color;
                if (c){
                    commit[4] = c;
                } else {
                    commit[4] = "black";
                }


                if (s_history.length = s_history_pointer + 1){
                    if (s_history.length == 0){
                        s_history = [commit];
                        s_history_pointer = 0;
                    } else {
                        s_history.push(commit);
                        s_history_pointer++;
                        if (s_history.length - s_history_pointer != 1){
                            my_alert("Fehler im undo/redo-commit");
                        }
                    }
                } else {
                    s_history = s_history.slice(0, s_history_pointer + 1);
                    s_history.push(commit);
                    s_history_pointer++;
                }


            }
        }
    }
    last_field = new_field;

}

function my_alert(msg) {
    $("msg").innerHTML = msg;
}

function cell_mark(x, y){
    var mark_color = '#FF8686';
    get_item(x, y).style.backgroundColor = mark_color;
    get_item(x, y).parentNode.style.backgroundColor = mark_color;
    marked_cells[x][y] = 1;
}

function cell_unmark(x, y){
    get_item(x, y).style.backgroundColor = 'white';
    get_item(x, y).parentNode.style.backgroundColor = 'white';
    marked_cells[x][y] = 0;
}

function clean_characters() {
    for (var x = 0; x < 9; x++){
        for (var y = 0; y < 9; y++){
            var v = get_item(x, y).value;
            /* now _that's_ an evil hack:
             * instead of checking for an event for letter keys 
             * (don't know how to do that...), check here if 
             * a letter was entered. D'oh! */
            if (v.match(/m/i)){
                cell_mark(x, y);
            } else if (v.match(/u/i)){
                cell_unmark(x, y);
            }

            v = v.replace(/[^1-9]/, '');
            get_item(x, y).value = v;

            if (v.length > 1){
                v = v.substr(1,1);
                get_item(x, y).value = v;
            }
            if (v && !v.match("^[1-9]$")){
                get_item(x, y).value = '';
            }
        }
    }
}


function timer_toggle() {
    timer_enabled = ! timer_enabled;
    if (timer_enabled){
        $("pause").enabled = true;  
        $("pause").value = "Pause";
        $("sudoku_table").style.visibility = "visible";
        if (! timer_running){
            window.setTimeout("timer_update()", 1000);
            timer_running = true;
        }
    } else {
        $("pause").enabled = true;  
        if (lang == "en"){
            $("pause").value = "Continue";
        } else {
            $("pause").value = "Weiter";
        }
        $("sudoku_table").style.visibility = "hidden";
    }

}


function sudoku_init() {
    timer_toggle();
    time = parseInt($F("time"));
    var hints = $F("hints").split(";");
    if (hints.length > 1){
        hints_count = parseInt(hints[0]);
        check1_count = parseInt(hints[1]);
        check2_count = parseInt(hints[2]);
    }
    loadHandler();
}

function save_persistent_variables() {
    var hints = [hints_count, check1_count, check2_count];
    $("hints").value = hints.join(";");
    $("time").value = time;
}
        
function timer_update() {
    if (timer_enabled){
        time ++;
    }

    $("timer").innerHTML = time_pretty_print(time);
    $("time_penalty").innerHTML = time_pretty_print(time_penalty());

    save_persistent_variables();
    if (timer_enabled){
        window.setTimeout("timer_update()", 1000);
    } else {
        timer_running = false;
    }
}

function time_penalty() {
    return 50 * (hints_count + check2_count) + 30 * check1_count;
}

function time_pretty_print(s) {
    var min = parseInt(s/ 60);
    var sec = s % 60;
    if (sec > 9){
        return min + ':' + sec;
    } else {
        return min + ':0' + sec;
    }
}

function hiscore_send() {
    if (! won) {
        my_alert("Sie muessen gewinnen um einen hiscore abschicken zu koennen");
        return;
    }
    var name = $F("hi_name");
    var comment = $("hi_comment");
    var score = time + time_penalty();
    var url = "/online/hiscore.pl?date=" + short_date +
        "&action=add";
    var post_data = Form.serialize($("hi_enter"));
    post_data = post_data 
        + ";time=" + time 
        + ";hints=" + hints_count 
        + ";score=" + score 
        + ";check1=" + check1_count 
        + ";check2=" + check2_count 
        + ';cheatlog=' + serialize_history();

    $("sudoku_hiscore_enter").style.visibility = "hidden";
    $("sudoku_hiscore_enter").style.height = "0px";
    $("sudoku_hiscore_enter").style.display = "none";
    var ajax = new Ajax.Updater (
            'hiscore_list',
            url,
            {
            method: "post",
            postBody: post_data
            });
}

function hiscore_resend() {
    var url = "/online/hiscore.pl?date=" + short_date +
        "&action=add";
    var post_data = Form.serialize($("hi_reenter"));
    var ajax = new Ajax.Updater (
            'hiscore_list',
            url,
            {
            method: "post",
            postBody: post_data
            });

}

function jump(event) {
    var e = event ? event : Try.these(
            function (){return event;}, 
            function (){return window.event;},
            function (){return document.event;});
    if (activeElement && e && e.keyCode){
        var k = e.keyCode;
        if (k < 37 || k > 40){
            return;
        }
        var a = activeElement.id;
        var y = parseInt(a.slice(1,2));
        var x = parseInt(a.slice(2,3));
        if (k == 37 && x > 0){
            /* move left */
            x--;
        } else if (k == 39 && x < 8) {
            /* move right */
            x++;
        } else if (k == 38 && y > 0){
            /* move up */
            y--;
        } else if (k == 40 && y < 8) {
            /* move down */
            y++;
        }
/*      my_alert(k);  */

        var new_id = 's' + y + '' + x;
        Field.focus(new_id);

        /* move cursor to start of input field */
        /* http://parentnode.org/javascript/working-with-the-cursor-position/
         */
        var obj = $(new_id);
        setCaretToStart(obj);

    }
}


function serialize_history(){
    var r = "";
    var commit;
    for(i=0; i < s_history.length;i++){
        commit = s_history[i];
        r += commit[0] + ':' + commit[1] + ':' + commit[2] + ':' + commit[3] + '|';
    }
    return r;

}

