////////////////////////////////////////////////////////////////////////
//
// Filename:    js/state.js
// Purpose:     Handle state parameters while problem solving
// Method:      Local state parameters and server round-trips
//              to update the database.
// Author:      J.van.der.Steen@gobase.org
// Date:        2005-12-16
//
////////////////////////////////////////////////////////////////////////

var debug = true;
var debug = false;

//
// Problem object
//
function Problem(name, value, punish, solved)
{
    //
    // Properties
    //
    this.name   = name;         // Reference name (GIUD)
    this.value  = value;        // Initial value of the problem
    this.solved = solved;       // Has this problem been solved?
    this.punish = punish;       // Punishment for every wrong answer
    this.pid    = -1;           // Problem ID
    this.eid    = -1;           // Exercise ID

    //
    // Methods
    //
    this.getName   = function () { return this.name; };
    this.getPID    = function () { return this.pid; };
    this.getEID    = function () { return this.eid; };
    this.getScore  = function () { return this.value; };
    this.getValue  = function () { return (this.solved) ? 0 : this.value; };
    this.getSolved = function () { return this.solved; };
    this.issolved  = function () { return this.solved; };
    this.getPunish = function () {
                     //
                     // Calculate the punishment which would be applied
                     //
                     if (this.solved) return 0;
                     if (this.value - this.punish < 2) {
                        return this.value - 2;
                     }
                     return this.punish;
                     };

    this.setValue  = function(value)
                     //
                     // Set problem value, return value
                     //
                     {
                         return this.value = value;
                     };
    this.setSolved = function(solved)
                    //
                    // Set problem status, return status
                    //
                    {
                        return this.solved = solved;
                    };
    this.setPunish = function(punish)
                    //
                    // Set problem punish, return punish
                    //
                    {
                        return this.punish = punish;
                    };
    this.setPID    = function(pid)
                     //
                     // Set problem ID, return ID
                     //
                     {
                         return this.pid = pid;
                     };
    this.setEID    = function(eid)
                     //
                     // Set exercise ID, return ID
                     //
                     {
                         return this.eid = eid;
                     };

    //
    // Update backend with attempts (either successful or failure)
    //
    this.failure   = function ()
                     //
                     // Update administration, return points remaining
                     //
                     {
                         if (!this.issolved()) {
                             this.value -= this.punish;
                             if (this.value < 2) {
                                 //
                                 // A problem is worth minimal 2 points
                                 //
                                 this.value = 2;
                             }
                         }
                         return this.getValue();
                     };
    this.success   = function ()
                     //
                     // Update administration, return scored points
                     //
                     {
                         if (!this.issolved()) {
                             this.solved = true;
                             return this.getScore();
                         }
                         return 0;
                     };
}

//
// Current user object
//
function User(uid, name)
{
    //
    // Properties
    //
    this.uid      = uid;                // Account id
    this.name     = name;               // Account username
    this.score    = 0;                  // total score
    this.problems = new Array();        // problems seen and solved
    this.todo     = null;               // unsolved problem
    this.next     = null;               // next unsolved problem

    //
    // Methods
    //
    this.getUID     = function () { return this.uid; };
    this.getName    = function () { return this.name; };
    this.hasProblem =
                    function (name)
                    {
                        return (this.problems[name]) ? true : false;
                    };
    this.addProblem =
                    function (name)
                    {
                        if (!this.problems[name]) {
                            this.problems[name] =
                                new Problem(name, 10, 2, false);
                        }
                        return this.problems[name];
                    };

    this.getScore   = function () { return this.score; };
    this.setScore   = function (points)
                    {
                        this.score = points;
                        return this.score;
                    };
    this.addScore   = function (points)
                    {
                        this.score += points;
                        return this.score;
                    };

    this.setNextProblem = function (name)
                    {
                        this.todo = name;
                    };
    this.getNextProblem = function () { return this.todo; };
    this.setFollProblem = function (name)
                    {
                        this.next = name;
                    };
    this.getFollProblem = function () { return this.next; };

    //
    // Get value of a problem for this user
    //
    this.getProblemValue =
                    function (name)
                    {
                        this.addProblem(name);
                        return this.problems[name].getValue();
                    };

    //
    // Get value of punish parameter of a problem for this user
    //
    this.getProblemPunish =
                    function (name)
                    {
                        this.addProblem(name);
                        return this.problems[name].getPunish();
                    };

    //
    // User failed to solve a problem, punish by decreasing its value.
    //
    this.failure    = function (name)
                    {
                        this.addProblem(name);
                        this.problems[name].failure();
                        //
                        // Update DBMS
                        //
                        exerciseUpdate( this.getUID()
                                      , this.getName()
                                      , this.problems[name]
                                      ) ;
                        return this.problems[name].getValue();
                    };

    //
    // User succeeded to solve a problem, update the score.
    //
    this.success    = function (name)
                    {
                        this.addProblem(name);
                        var scored = 0;
                        if (!this.problems[name].issolved()) {
                            scored = this.problems[name].success();
                            this.addScore(scored);
                            //
                            // Update DBMS
                            //
                            exerciseUpdate( this.getUID()
                                          , this.getName()
                                          , this.problems[name]
                                          ) ;
                        }
                        return scored;
                    };

}

var user = null;

function init(uid, username, problemID, problemN)
//
// We get called once the user logged into the problem section.
// We call the server to fetch his current state.
// The returned data is an JSON object which contains all problems
// solved and visited by this user.
//
{
    //
    // Fetch user state from server, initialize state
    //
    if (!user || user.getName() != username) {
        user = new User(uid, username);
    }

    if (debug) {
        var exerciseListEngine = new Ace.Engine("Listing", "_tracer");
    } else {
        var exerciseListEngine = new Ace.Engine(false, false);
    }

    //
    // Launch asynchrone request to fetch user's problems
    //
    var request = new Ace.Request( Ace.Method.Post
                                 , "/cursus/ajax/ExerciseList.phtml"
                                 , null
                                 , parList(
                                       { uid:       uid
                                       , username:  username
                                       , problemID: problemID
                                       , problemN:  problemN
                                       }
                                   )
                                 , exerciseListHandle
                                 , null
                                 , Ace.CallbackOption.StatusOK
                                 ) ;
    exerciseListEngine.invoke(request, null);
}

function exerciseListHandle(response, args)
//
// Handle an exerciseList response.
// Expect a JSON object containing a list of problems:
// {
//   problemSet: [
//     {
//       problemID: "name"       // Problem GUID
//             pid: 3            // Problem ID
//             eid: -1           // Exercise ID
//           value: 10           // Current value of the problem
//          punish:  2           // Punishment for wrong answers
//          solved: false        // Has this problem been solved?
//     }
//   ]
//   ,
//   env: {
//       uid: 1               // Account id
//       username: "username" // User
//       problemID: "2-10-2"  // Problem ID
//       problemN : "3"       // Problem # (1, 2, ..., N)
//   }
// }
//
{
    if (user) {
        eval( "var json = " + response.text + ";" );
        if (json.error) {
            myAlert("Error encountered: " + json.error);
            user.problems = new Array();
            return;
        }
        if (json.problemSet) {
            var problems = json.problemSet;
            for (var n = 0; n < problems.length; n++) {
                //
                // Add to user's problem collection
                //
                problem = user.addProblem(problems[n].problemID);
                problem.setValue  (problems[n].value);
                problem.setPunish (problems[n].punish);
                problem.setSolved (problems[n].solved);
                problem.setPID    (problems[n].pid);
                problem.setEID    (problems[n].eid);
            }
            //
            // Create index of problem set
            //
            problemIndex(problems, json.env);
        }
        //
        // We're all set, load the first problem into the problem widget
        //
        if (json.env) {
            var env = json.env;
            problemLoad(user, env.problemID, env.problemN);
            //
            // Initialize user's total score
            //
            user.setScore(parseInt(env.scored));
            //
            // Set next problem to solve
            //
            if (env.nextProblem) {
                user.setNextProblem(env.nextProblem);
            }
            if (env.follProblem) {
                user.setFollProblem(env.follProblem);
            }
        }
    }
}

function problemURL(problemID, n)
//
// Create URL to nth problem in the set identified by problemID
//
{
    var arg = parList(
                  { sec: problemID
                  ,   n: n-1
                  }
              );
    return "/cursus/Oefenen.phtml?" + arg;
}

function navigationIMG(name, title)
//
// Create img from supplied name and title
//
{
    return "<img alt='" + name + "'"
         + " src='/cursus/icn/" + name + ".gif'"
         + " title='" + title + "'"
         + " border='0' hspace='4'>";
}

function problemIndex(problems, env)
//
// Create index from supplied problem set, add indication of problem status
//
{
    var html = '';
    var c = "<img alt=c src='/cursus/icn/mark_selected.gif'>";
    var s = "<img alt=s src='/cursus/icn/mark_right.gif'>";
    var v = "<img alt=v src='/cursus/icn/mark_wrong.gif'>";
    var n = "<img alt=n src='/cursus/icn/mark_notdone.gif'>";

    //
    // Generate HTML code
    //
    html += "<center>\n";
    html += "<table cellpadding=0 cellspacing=0 border=0>\n";

    //
    // Scoreboard
    //
    html += "<tr>\n";
    html += "<td></td>\n";
    for (var k = 0; k < problems.length; k++) {
        var styling = 'width:20px;padding-bottom:4px;text-align:center;';
        html += "<td style='" + styling + "'>";
        if (k+1 == env.problemN) {
            html += c;                          // current
        } else {
            if (problems[k].solved) {
                html += s;                      // solved
            } else {
                if (problems[k].value < 10) {
                    html += v;                  // visited
                } else {
                    html += n;                  // new
                }
            }
        }
        html += "</td>\n";
    }
    html += "<td></td>\n";
    html += "</tr>\n";

    //
    // References
    //
    html += "<tr>\n";
    var prev = navigationIMG( "prev"
                            , "ga naar het vorige probleem"
                            ) ;
    if (env.problemN > 1) {
        prev = "<a href='"
             + problemURL(env.problemID, parseInt(env.problemN) - 1)
             + "'>" + prev + "</a>";
    }
    var frst = navigationIMG( "frst"
                            , "ga naar het eerste probleem"
                            ) ;
    if (env.problemN > 1) {
        frst = "<a href='"
             + problemURL(env.problemID, 1)
             + "'>" + frst + "</a>";
    }
    html += "<td>" + frst + prev + "</td>\n";
    for (var k = 1; k <= problems.length; k++) {
        var styling = 'width:20px;text-align:center;';
        if (k > 1) {
            styling += 'border-left:1px solid black;';
        }
        html += "<td style='" + styling + "'>";
        if (k == env.problemN) {
            html += "<strong>";
        } else {
            html += "<a href='" + problemURL(env.problemID, k) + "'>";
        }
        html += k;
        if (k == env.problemN) {
            html += "</strong>";
        } else {
            html += "</a>";
        }
        html += "</td>\n";
    }
    var next = navigationIMG( "next"
                            , "ga naar het volgende probleem"
                            ) ;
    if (env.problemN < problems.length) {
        next = "<a href='"
             + problemURL(env.problemID, parseInt(env.problemN) + 1)
             + "'>" + next + "</a>";
    }
    var last = navigationIMG( "last"
                            , "ga naar het laatste probleem"
                            ) ;
    if (env.problemN < problems.length) {
        last = "<a href='"
             + problemURL(env.problemID, problems.length)
             + "'>" + last + "</a>";
    }
    html += "<td>" + next + last + "</td>\n";
    html += "</tr>\n";

    html += "</table>\n";
    html += "</center>\n";

    //
    // Write HTML code
    //
    var idxWin = top.mainFrame.document.getElementById('index');
    idxWin.innerHTML = html;
}

function problemLoad(user, problemID, problemN)
//
// Load Nth problem of supplied set
//
{
    var arg = parList(
                  { username: user.getName()
                  , n: problemN
                  }
              );
    var url = 'oefenen/' + problemID + '/?' + arg;
    var diaWin = top.mainFrame.frames['diaWin'];
    diaWin.location = url;
}

function exerciseUpdate(uid, username, problem)
//
// Launch asynchrone request to update user's exercises
//
{
    if (debug) {
        var exerciseUpdateEngine = new Ace.Engine("Update", "_tracer");
    } else {
        var exerciseUpdateEngine = new Ace.Engine(false, false);
    }

    var request = new Ace.Request( Ace.Method.Post
                                 , "/cursus/ajax/ExerciseUpdate.phtml"
                                 , null
                                 , parList(
                                       {
                                               uid: uid
                                       ,  username: username
                                       , problemID: problem.getName()
                                       ,       eid: problem.getEID()
                                       ,     value: problem.getScore()
                                       ,    solved: problem.getSolved()
                                       ,       pid: problem.getPID()
                                       }
                                   )
                                 , exerciseUpdateHandle
                                 , null
                                 , Ace.CallbackOption.StatusOK
                                 ) ;
    exerciseUpdateEngine.invoke(request, null);
}

function exerciseUpdateHandle(response, args)
//
// Handle the update of an exercise response.
// Expect a JSON object with the following properties:
// {
//  problemID: name               // Problem GUID
//      value: <numeric value>    // Exercise Value
//     solved: <boolean value>    // Exercise solved status
//        eid: <numeric value>    // Exercise record id
// }
//
{
    eval( "var json = " + response.text + ";" );

    if (json.error) {
        myAlert("Error encountered: " + json.error);
        return;
    }
    if (json.exercise) {
        exercise = json.exercise;
        if (user) {
            problem = user.addProblem(exercise.problemID);
            problem.setValue  (exercise.value);
            problem.setSolved (exercise.solved);
            problem.setEID    (exercise.eid);
            if (json.env) {
                var env = json.env;
                //
                // Set next problem to solve
                //
                if (env.nextProblem) {
                    user.setNextProblem(env.nextProblem);
                }
                if (env.follProblem) {
                    user.setFollProblem(env.follProblem);
                }
                //
                // Notify exercise widget
                //
                var diaWin = top.mainFrame.frames['diaWin'];
                diaWin.nextProblem( user.getNextProblem()
                                  , user.getFollProblem()
                                  ) ;
                myAlert("Next problem " + env.nextProblem);
                myAlert("Foll problem " + env.follProblem);
            }
        }
        myAlert("Problem " + exercise.problemID + " has been updated");
    }
}

function parList(parameters)
//
// Library function
//
// Build parameter list from supplied object containing the parameters:
// { key1:val1, key2:val2, ... }
//
{
    var parString = "";
    for (key in parameters) {
        parString += '&' + key + '=' + encodeURI(parameters[key]);
    }
    if (parString.length > 0) {
        parString = parString.substring(1); // skip initial '&'
    }
    return parString;
}

function myAlert(text)
{
    if (debug) {
        alert(text);
    }
}

////////////////////////////////////////////////////////////////////////
//
// Unit tests
//
////////////////////////////////////////////////////////////////////////
if (false) {
    //
    // Create a user
    //
    var user = new User(0, 'Test gebruiker');

    //
    // Construct virtual problem: <problem section>:<problem number>
    var name = '2-1-2:3';

    alert('Initial value: ' + user.getProblemValue(name));
    user.failure(name);
    alert('Current value: ' + user.getProblemValue(name));
    user.failure(name);
    alert('Current value: ' + user.getProblemValue(name));
    alert('You scored: '    + user.success(name));
    alert('Total score: '   + user.getScore());
}

