Index: common/db/db.php
===================================================================
--- common/db/db.php	(revision 1105)
+++ common/db/db.php	(working copy)
@@ -245,6 +245,13 @@
     }
 }
 
+// Escape an array of strings.
+function db_escape_array($array) {
+    $ret = implode(',', array_map('db_quote', $array));
+
+    return $ret;
+}
+
 // Executes query, fetches only FIRST result row
 function db_fetch($query) {
     $result = db_query($query, true);
Index: common/db/round.php
===================================================================
--- common/db/round.php	(revision 1105)
+++ common/db/round.php	(working copy)
@@ -54,6 +54,33 @@
     }
 }
 
+function round_recompute_score($round_id) {
+    $query = "SELECT * FROM `ia_score_user_round`
+        WHERE `round_id` = ".db_quote($round_id);
+    $rows = db_fetch_all($query);
+    $users = array();
+    foreach ($rows as $row) {
+      $users[] = $row['user_id'];
+    }
+
+    $query = "SELECT SUM(`score`) AS score, user_id
+            FROM ia_score_user_round_task
+            WHERE `user_id` IN ('".implode("', '", $users)."') AND
+                  `round_id` = ".db_quote($round_id)."
+            GROUP BY `user_id`";
+    $rows = db_fetch_all($query);
+    foreach($rows as $row) {
+        $user_id = $row['user_id'];
+        $score = $row['score'];
+
+        $query = "UPDATE `ia_score_user_round`
+                    SET `score` = ".db_quote($score)."
+                    WHERE `user_id` = ".db_quote($user_id)." &&
+                          `round_id` = ".db_quote($round_id);
+        db_query($query);
+    }
+}
+
 // Update a round.
 function round_update($round) {
     log_assert_valid(round_validate($round));
@@ -76,7 +103,7 @@
 // FIXME: cache tasks.
 //
 // if user_id is non-null a join is done on $score
-function round_get_tasks($round_id, $first = 0, $count = null, $user_id = null, $score_name = null, $filter = null) {
+function round_get_tasks($round_id, $first = 0, $count = null, $user_id = null, $fetch_scores = false, $filter = null) {
     if ($count === null) {
         $count = 666013;
     }
@@ -89,49 +116,45 @@
               "task.`type` AS `type`,
                task.`open_source` AS `open_source`,
                task.`open_tests` AS `open_tests`";
-
-    if ($score_name === null || $user_id === null) {
+    if ($user_id === null || $fetch_scores === false) {
         $query = sprintf("SELECT $fields
                           FROM ia_round_task as round_task
                           LEFT JOIN ia_task as task ON task.id = round_task.task_id
                           WHERE `round_task`.`round_id` = '%s'
                           ORDER BY task.`order` LIMIT %d, %d",
-                         db_escape($round_id), db_escape($first), db_escape($count));
+                          db_escape($round_id), db_escape($first), db_escape($count));
     } else {
         $filter_clause = db_get_task_filter_clause($filter, 'score');
         log_assert(is_whole_number($user_id));
-        $query = sprintf("SELECT $fields, score.score as score
+        $query = sprintf("SELECT $fields, score.`score` AS `score`
                           FROM ia_round_task as round_task
                           LEFT JOIN ia_task as task ON task.id = round_task.task_id
-                          LEFT JOIN ia_score as score ON
-                                score.user_id = %s AND
-                                score.name = '%s' AND
-                                score.round_id = '%s' AND
-                                score.task_id = round_task.task_id
+                          LEFT JOIN ia_score_user_round_task as score ON
+                                score.round_id = round_task.round_id AND
+                                score.task_id = round_task.task_id AND
+                                score.user_id = '%s'
                           WHERE `round_task`.`round_id` = '%s'
-                              AND %s
+                            AND %s
                           ORDER BY task.`order` LIMIT %d, %d",
-                         db_escape($user_id), db_escape($score_name), db_escape($round_id),
+                         db_escape($user_id),
                          db_escape($round_id), db_escape($filter_clause),
                          db_escape($first), db_escape($count));
     }
     return db_fetch_all($query);
 }
 
-function round_get_task_count($round_id, $user_id, $scores, $filter)
+function round_get_task_count($round_id, $user_id, $filter)
 {
-    if ($user_id && $filter && $scores) {
-        $filter_clause = db_get_task_filter_clause($filter, 'ia_score');
+    if ($user_id && $filter) {
+        $filter_clause = db_get_task_filter_clause($filter, 'ia_score_user_round_task');
         $query = sprintf("SELECT COUNT(*) FROM ia_round_task " .
-                         "LEFT JOIN ia_score " .
-                         "ON ia_round_task.round_id = ia_score.round_id " .
-                         "AND ia_round_task.task_id = ia_score.task_id " .
-                         "AND ia_score.user_id = %s " .
-                         "AND ia_score.name = '%s' " .
+                         "LEFT JOIN ia_score_user_round_task " .
+                         "ON ia_round_task.round_id = ia_score_user_round_task.round_id " .
+                         "AND ia_round_task.task_id = ia_score_user_round_task.task_id " .
+                         "AND ia_score_user_round_task.user_id = %s " .
                          "WHERE ia_round_task.round_id = '%s' " .
                          "AND %s",
                          db_escape($user_id),
-                         db_escape($scores),
                          db_escape($round_id),
                          db_escape($filter_clause)
                          );
Index: common/db/score.php
===================================================================
--- common/db/score.php	(revision 1105)
+++ common/db/score.php	(working copy)
@@ -5,36 +5,54 @@
 require_once(IA_ROOT_DIR."common/parameter.php");
 require_once(IA_ROOT_DIR."common/rating.php");
 
-// Escape an array of strings.
-function db_escape_array($array)
+// Updates a user's rating and deviation
+function score_update_rating($user_id, $round_id, $deviation, $rating)
 {
-    $ret = '';
-    foreach ($array as $element) {
-        if ($ret) {
-            $ret .= ", ";
-        }
-        $ret .= "'" . db_escape($element) . "'";
-    }
-    return $ret;
+    $query = "INSERT INTO `ia_rating` (`user_id`, `round_id`, `deviation`, `rating`)
+             VALUES (".implode(',',
+             array(db_quote($user_id),
+                 db_quote($round_id),
+                 db_quote($deviation),
+                 db_quote($rating)
+             )).") ON DUPLICATE KEY UPDATE ".
+                 "`rating` = ". $rating .",
+                 `deviation` = ".$deviation;
+    db_query($query);
+    return db_affected_rows();
 }
 
-// Update a score.
-// user/task/round can be null.
-function score_update($name, $user_id, $task_id, $round_id, $value)
-{
-    log_assert(is_score_name($name), "Bad score name '$name'");
-    log_assert(is_null($user_id) || is_user_id($user_id), "Bad user id '$user_id'");
-    log_assert(is_null($task_id) || is_task_id($task_id), "Bad task id '$task_id'");
-    log_assert(is_null($round_id) || is_round_id($round_id), "Bad round id '$round_id'");
-    $query = sprintf("
-            INSERT INTO ia_score (`name`, `score`, `user_id`, `task_id`, `round_id`)
-            VALUES ('%s', %s, %s, %s, %s) ON DUPLICATE KEY UPDATE `score`=%s",
-            db_escape($name), $value,
-            ($user_id === null ? 'NULL' : $user_id),
-            ($task_id === null ? 'NULL' : "'".db_escape($task_id)."'"),
-            ($round_id === null ? 'NULL' : "'".db_escape($round_id)."'"),
-            $value);
-    return db_query($query);
+// Updates user's task score
+function score_update($user_id, $task_id, $round_id, $value) {
+    log_assert(is_user_id($user_id), "Bad user id '$user_id'");
+    log_assert(is_task_id($task_id), "Bad task id '$task_id'");
+    log_assert(is_round_id($round_id), "Bad round id '$round_id'");
+
+    // Update user_id score for task_id at round_id
+    $query = "INSERT INTO `ia_score_user_round_task` (`user_id`, `round_id`, `task_id`, `score`)
+            VALUES (".implode(',',
+            array(db_quote($user_id),
+                db_quote($round_id),
+                db_quote($task_id),
+                db_quote($value)
+            )).") ON DUPLICATE KEY UPDATE
+                `score` = ".db_quote($value);
+    db_query($query);
+
+    // update score for round_id
+    $subquery = "( SELECT SUM(`score`) AS 'score' FROM `ia_score_user_round_task`
+            WHERE
+                `round_id` = ".db_quote($round_id)." &&
+                `user_id` = ".db_quote($user_id)."
+            GROUP BY `user_id` )";
+
+    $query = "INSERT INTO `ia_score_user_round` (`user_id`, `round_id`, `score`)
+            VALUES (".implode(',',
+            array(db_quote($user_id),
+                db_quote($round_id),
+                $subquery
+            )).") ON DUPLICATE KEY UPDATE
+                `score` = ".$subquery;
+    db_query($query);
 }
 
 // Builds a where clause for a score query.
@@ -85,103 +103,17 @@
     return $where;
 }
 
-// Build a query for a certain score.
-// Can be used as a subquery.
-function score_build_query($score, $user, $task, $round)
-{
-    $cond = score_build_where_clauses($user, $task, $round);
-    $cond[] = "(id = '".db_escape($score['name'])."')";
-    $query = "SELECT SUM(score) FROM ia_score WHERE " . implode(" AND ", $cond);
-}
-
-// Get scores.
-// $user, $task, $round can be null, string or an array.
-// If null it's ignored, otherwise only scores for those users/tasks/rounds
-// are counted.
-function score_get_range($score_name, $user, $task, $round, $groupby = "user_id", $start = 0, $count = 999999, $with_rankings = false)
-{
-    log_assert(is_score_name($score_name));
+// Count function to be used for score_get_rankings
+function score_get_count($user, $task, $round) {
     $where = score_build_where_clauses($user, $task, $round);
-    $where[] = sprintf("ia_score.`name` = '%s'", db_escape($score_name));
-    $query = sprintf("SELECT
-                ia_score.`name` AS `score_name`, `user_id`, `task_id`, `round_id`, SUM(`score`) AS score, 
-                ia_user.username AS user_name, ia_user.full_name AS user_full,
-                ia_user.rating_cache AS user_rating
-            FROM ia_score
-                LEFT JOIN ia_user ON ia_user.id = ia_score.user_id
-            WHERE %s GROUP BY %s
-            ORDER BY `score` DESC LIMIT %s, %s",
-            join($where, " AND "), $groupby, $start, $count);
-    $scores = db_fetch_all($query);
-
-    if ($with_rankings && count($scores)) {
-        $first_score = $scores[0]['score'];
-
-        // We need to count all users with a greater score than the first user
-        // in the requested range.
-        $where = score_build_where_clauses($user, $task, $round);
-        $where[] = sprintf("ia_score.`name` = '%s'", db_escape($score_name));
-        $query = sprintf("SELECT SUM(`score`) AS score 
-                          FROM ia_score
-                          WHERE %s GROUP BY %s
-                          HAVING score > %s",
-                         join($where, " AND "), $groupby, $first_score);
-        $users_before = db_num_rows(db_query($query));
-
-        // store rankings in result
-        $scores[0]['ranking'] = $users_before + 1;
-        $equal_scores = $start - $users_before + 1;
-        for ($i = 1; $i < count($scores); ++$i) {
-            $last_row = $scores[$i - 1];
-            $row =& $scores[$i];
-            if ($row['score'] == $last_row['score']) {
-                $row['ranking'] = $last_row['ranking'];
-                $equal_scores = $equal_scores + 1;
-            }
-            else {
-                $row['ranking'] = $last_row['ranking'] + $equal_scores;
-                $equal_scores = 1;
-            }
-        }
-    }
-
-    return $scores;
-}
-
-// Count function for score_get_range
-function score_get_count($score_name, $user, $task, $round, $groupby) {
-    log_assert(is_score_name($score_name));
-    $where = score_build_where_clauses($user, $task, $round);
-    $where[] = sprintf("ia_score.`name` = '%s'", db_escape($score_name));
-    if ($user !== null) {
-        $join = "LEFT JOIN ia_user ON ia_user.id = ia_score.user_id";
-    } else {
-        $join = "";
-    }
     $query = sprintf("SELECT COUNT(DISTINCT user_id) AS `cnt`
-            FROM ia_score $join
+            FROM ia_score_user_round
             WHERE %s",
-            join($where, " AND "), $groupby);
+            join($where, " AND "));
     $res = db_fetch($query);
     return $res['cnt'];
 }
 
-// Get a score value.
-// Returns 0 or null (if missing).
-function score_get_value($score_name, $user_id, $task_id, $round_id)
-{
-    log_assert(is_score_name($score_name));
-    log_assert(is_whole_number($user_id));
-    log_assert(is_task_id($task_id));
-    log_assert(is_user_id($round_id));
-
-    $query = sprintf("SELECT score FROM ia_score
-                WHERE name = '%s', task_id='%s', round_id='%s', user_id = %s",
-                $score_name, $task_id, $round_id, $user_id);
-    $res = db_fetch($query);
-    return getattr($res, 'score', null);
-}
-
 // Return rating history for given user_id (not username).
 // Output array format is:
 //  array(
@@ -203,30 +135,17 @@
     $history = rating_rounds();
 
     // get user scores
-    $query = sprintf("SELECT * FROM `ia_score`
+    $query = sprintf("SELECT * FROM `ia_rating`
                       LEFT JOIN ia_round ON round_id = ia_round.id
-                      WHERE `name` IN ('deviation', 'rating')
-                            AND ia_score.user_id = '%s'
+                      WHERE ia_rating.user_id = '%s'
                             AND ia_round.state = 'complete'
-                     ",
-                     db_escape($user_id));
+                     ", db_escape($user_id));
     $rows = db_fetch_all($query);
 
-    // process user scores
     foreach ($rows as $row) {
         $round_id = $row['round_id'];
-        log_assert(isset($history[$round_id]));
-
-        switch ($row['name']) {
-            case 'rating':
-                $history[$round_id]['rating'] = $row['score'];
-                break;
-            case 'deviation':
-                $history[$round_id]['deviation'] = $row['score'];
-                break;
-            default:
-                log_error("Query returned invalid rating scores");
-        }
+        $history[$round_id]['rating'] = $row['rating'];
+        $history[$round_id]['deviation'] = $row['deviation'];
     }
 
     // filter out rows which user has not participated in
@@ -254,8 +173,7 @@
 //      ...
 //  );
 function rating_rounds() {
-    $query = "
-        SELECT
+    $query = "SELECT
                object_id AS round_id, `value` AS `timestamp`,
                ia_round.page_name AS round_page_name,
                ia_round.title AS round_title
@@ -328,44 +246,29 @@
 function rating_last_scores() {
     // FIXME: horrible query
     $query = "SELECT
-        ia_score.name AS `name`, ia_score.score AS score,
-               ia_score.user_id, ia_score.round_id,
+        ia_rating.rating AS `rating`, ia_rating.deviation AS deviation,
+               ia_rating.user_id, ia_rating.round_id,
                pv.`value` AS `timestamp`, ia_user.username
-        FROM ia_score
+        FROM ia_rating
         LEFT JOIN ia_parameter_value AS pv
-            ON pv.object_type = 'round' AND pv.object_id = ia_score.round_id
+            ON pv.object_type = 'round' AND pv.object_id = ia_rating.round_id
             AND pv.parameter_id = 'rating_timestamp'
-        LEFT JOIN ia_user ON ia_user.id = ia_score.user_id
-        WHERE ia_score.name IN ('rating', 'deviation')
-        ORDER BY `timestamp` DESC, ia_score.round_id DESC
+        LEFT JOIN ia_user ON ia_user.id = ia_rating.user_id
+        ORDER BY `timestamp` DESC, ia_rating.round_id DESC
     ";
     $rows = db_fetch_all($query);
 
-    // FIXME: We should filter out rounds having rating_update off
-    // but these should not have any ratings stored in database anyway...
-
-    // parse rows 
     $users = array();
     foreach ($rows as $row) {
         $username = $row['username'];
-        $field = $row['name'];
         if (isset($users[$username])) {
-            if (isset($users[$username][$field])) {
-                // FIXME: This is currently a hack.
-                // Query shouldn't return more then one rating for each user
-                continue;
-            }
-            else {
-                $users[$username][$field] = $row['score'];
-            }
+            continue;
         }
-        else {
-            $users[$username] = array(
-                $field => $row['score'],
-                'timestamp' => parameter_decode('rating_timestamp',
-                                                $row['timestamp'])
-            );
-        }
+
+        $users[$username] = array(
+                'rating' => $row['rating'],
+                'deviation' => $row['deviation'],
+                'timestamp' => $row['timestamp']);
     }
 
     return $users;
@@ -428,7 +331,7 @@
 
 
         $rows[0]['position'] = $users_before + 1;
-        $equal_scores = $start - $users_before + 1;          
+        $equal_scores = $start - $users_before + 1;
         for ($i = 1; $i < count($rows); ++$i) {
             $last_row = $rows[$i - 1];
             $row =& $rows[$i];
@@ -458,8 +361,128 @@
 
 // Clears ALL user ratings & rating history
 function rating_clear() {
-    db_query("DELETE FROM ia_score WHERE `name` IN ('rating', 'deviation')");
+    db_query("DELETE FROM ia_rating");
     db_query("UPDATE ia_user SET rating_cache = NULL");
 }
 
+// Computes rankings for $rounds
+// returns entries from start to count
+// if detail_task == true, extra columns for each task will be created
+// if detail_round == true, extra columns for each round will be created
+function score_get_rankings($rounds, $tasks, $start = 0, $count = 999999, $detail_task = false, $detail_round = false)
+{
+    $where = score_build_where_clauses(null, null, $rounds);
+
+    // Get the total score for all rounds
+    $query = "SELECT SUM(score) AS score, user_id, ia_user.username AS user_name, ia_user.full_name AS user_full,
+                    ia_user.rating_cache AS user_rating
+                FROM ia_score_user_round
+                LEFT JOIN ia_user ON ia_user.id = ia_score_user_round.user_id
+                WHERE ".implode('AND', $where)."
+                GROUP BY `user_id`
+                ORDER BY score DESC
+                LIMIT ".db_escape($start).", ".db_escape($count);
+
+    $rankings = db_fetch_all($query);
+    if (count($rankings) == 0) {
+        return $rankings;
+    }
+
+    $users = array();
+    foreach ($rankings as $ranking) {
+        array_push($users, $ranking['user_id']);
+    }
+
+    // Further queries concern only the users that are in this rankings page
+    $filter_users = score_build_where_clauses($users, null, null);
+    $where[] = $filter_users[0];
+
+    // Detailed scores are mapped in an array with the following form
+    // Array[user_id][object_id] = user score for object
+    // Object can be round or task
+
+    if ($detail_round == true) {
+        // Get scores for each round
+        $query = "SELECT round_id, user_id, score
+                FROM ia_score_user_round
+                WHERE ".implode('AND', $where);
+        $scores = db_fetch_all($query);
+        foreach ($scores as $score) {
+            $user_id = $score['user_id'];
+            $round_id = $score['round_id'];
+            $rscore = $score['score'];
+            $round_scores[$user_id][$round_id] = $rscore;
+        }
+    }
+
+    if ($detail_task == true) {
+        // Get scores for each task
+        $query = "SELECT task_id, user_id, score
+                FROM ia_score_user_round_task
+                WHERE ".implode('AND', $where);
+        $scores = db_fetch_all($query);
+        foreach ($scores as $score) {
+            $user_id = $score['user_id'];
+            $task_id = $score['task_id'];
+            $tscore = $score['score'];
+            $task_scores[$user_id][$task_id] = $tscore;
+        }
+    }
+
+    // Compute rank for the first entry
+    $top_score = $rankings[0]['score'];
+    $where = score_build_where_clauses(null, null, $rounds);
+    $query = "SELECT SUM(score) AS score
+                FROM ia_score_user_round
+                WHERE ".implode(' AND ', $where)."
+                GROUP BY user_id
+                HAVING score > ".db_quote($top_score);
+    $first_rank = db_num_rows(db_query($query)) + 1;
+
+    //create all entries
+    for ($i = 0; $i < count($rankings); $i++) {
+        $user_id = $rankings[$i]['user_id'];
+
+        //task columns
+        if ($detail_task == true) {
+            foreach ($tasks as $task) {
+                $task_id = $task['id'];
+                if (isset($task_scores[$user_id][$task_id])) {
+                    $score = $task_scores[$user_id][$task_id];
+                } else {
+                    $score = 0;
+                }
+                $rankings[$i][$task_id] = $score;
+            }
+        }
+
+        //round columns
+        if ($detail_round == true) {
+            foreach ($rounds as $round_id) {
+                if (isset($round_scores[$user_id][$round_id])) {
+                    $score = $round_scores[$user_id][$round_id];
+                } else {
+                    $score = 0;
+                }
+                $rankings[$i][$round_id] = $score;
+            }
+        }
+
+        if ($i == 0) {
+            $rankings[$i]['ranking'] = $first_rank;
+            continue;
+        }
+
+        // Users with the same score should be on the same rank
+        if ($rankings[$i]['score'] == $rankings[$i - 1]['score']) {
+            $rankings[$i]['ranking'] = $rankings[$i - 1]['ranking'];
+        } else {
+            $rankings[$i]['ranking'] = $start + $i + 1;
+        }
+    }
+
+    return $rankings;
+}
+
+
 ?>
Index: common/db/task.php
===================================================================
--- common/db/task.php	(revision 1105)
+++ common/db/task.php	(working copy)
@@ -65,14 +65,23 @@
     // Delete problem page
     textblock_delete($task["page_name"]);
 
+    // Delete all scores received on task
+    db_query("DELETE FROM `ia_score_user_round_task`
+              WHERE `task_id` = " . db_quote($task["id"]));
+
+    // Recompute round scores
+    $query = "SELECT `round_id` FROM `ia_round_task`
+                WHERE `task_id` = ".db_quote($task['id']);
+    $rounds = db_fetch_all($query);
+
+    foreach ($rounds as $round) {
+        round_recompute_score($round['round_id']);
+    }
+
     // Remove task from all rounds
     db_query("DELETE FROM `ia_round_task`
               WHERE `task_id` = " . db_quote($task["id"]));
 
-    // Delete all scores received on task
-    db_query("DELETE FROM `ia_score`
-              WHERE `task_id` = " . db_quote($task["id"]));
-
     // Delete task jobs
     $job_ids_fetched = db_fetch_all("
         SELECT `id`
Index: common/db/user.php
===================================================================
--- common/db/user.php	(revision 1105)
+++ common/db/user.php	(working copy)
@@ -217,10 +217,10 @@
         $where = '';
     }
     elseif ($solved) {
-        $where = 'AND ia_score.score = 100';
+        $where = 'AND ia_score_user_round_task.score = 100';
     }
     elseif ($failed) {
-        $where = 'AND ia_score.score < 100';
+        $where = 'AND ia_score_user_round_task.score < 100';
     }
     else {
         // This shouldn't happen
@@ -228,10 +228,10 @@
     }
 
     $query = sprintf("SELECT *
-        FROM ia_score
-        LEFT JOIN ia_task ON ia_task.id = ia_score.task_id
-        WHERE ia_score.`name` = 'score' AND ia_score.user_id = '%s'
-              AND ia_score.round_id = 'arhiva' AND NOT ia_task.id IS NULL %s
+        FROM ia_score_user_round_task
+        LEFT JOIN ia_task ON ia_task.id = ia_score_user_round_task.task_id
+        WHERE ia_score_user_round_task.user_id = '%s'
+              AND ia_score_user_round_task.round_id = 'arhiva' AND NOT ia_task.id IS NULL %s
         GROUP BY ia_task.id
         ORDER BY ia_task.`order`", $user_id, $where);
 
@@ -242,9 +242,9 @@
 function user_submitted_rounds($user_id) {
     // FIXME: Find a way to remove the hard-coded "<> 'arhiva'"
     $query = "SELECT *
-        FROM ia_score
-        LEFT JOIN ia_round ON ia_round.id = ia_score.round_id
-        WHERE ia_score.`name` = 'score' AND ia_score.user_id = '%s'
+        FROM ia_score_user_round_task
+        LEFT JOIN ia_round ON ia_round.id = ia_score_user_round_task.round_id
+        WHERE ia_score_user_round_task.user_id = '%s'
               AND NOT ia_round.id IS NULL AND ia_round.id <> 'arhiva'
         GROUP BY ia_round.id";
     $query = sprintf($query, $user_id);
Index: common/score.php
===================================================================
--- common/score.php	(revision 1105)
+++ common/score.php	(working copy)
@@ -18,7 +18,7 @@
     $rstart = db_date_parse($round['start_time']);
     $rduration = getattr($rparams, 'duration', 10000000) * 60 * 60;
     if ($time >= $rstart && $time <= $rstart + $rduration) {
-        score_update('score', $user_id, $task_id, $round['id'], $score);
+        score_update($user_id, $task_id, $round['id'], $score);
     }
 }
 
Index: scripts/db-strip
===================================================================
--- scripts/db-strip	(revision 1105)
+++ scripts/db-strip	(working copy)
@@ -94,6 +94,47 @@
         "OR (ia_score.task_id IS NOT NULL AND ia_task.id IS NULL) ".
         "OR (ia_score.round_id IS NOT NULL AND ia_round.id IS NULL)");
 
+db_query("DELETE ia_score_user_round_task FROM ia_score_user_round_task ".
+         "LEFT JOIN ia_user on ia_user.id = ia_score_user_round_task.user_id ".
+         "LEFT JOIN ia_task on ia_task.id = ia_score_user_round_task.task_id ".
+         "LEFT JOIN ia_round on ia_round.id = ia_score_user_round_task.round_id ".
+         "WHERE (ia_score_user_round_task.user_id IS NOT NULL AND ia_user.id IS NULL) ".
+         "OR (ia_score_user_round_task.task_id IS NOT NULL AND ia_task.id IS NULL) ".
+         "OR (ia_score_user_round_task.round_id IS NOT NULL AND ia_round.id IS NULL)");
+
+// Recompute ia_score_user_round
+db_query("DELETE FROM ia_score_user_round");
+$users = db_fetch_all("SELECT id FROM ia_user");
+$rounds = db_fetch_all("SELECT id FROM ia_round");
+
+foreach ($rounds as $round) {
+    foreach ($users as $user) {
+        $round_id = $round['id'];
+        $user_id = $user['id'];
+
+        $query = "SELECT SUM(`score`) AS 'score' FROM ia_score_user_round_task
+                  WHERE user_id = ".db_quote($user_id)." AND
+                         round_id = ".db_quote($round_id)."
+                  GROUP BY user_id";
+
+        $result = db_fetch_all($query);
+        if (isset($result[0]['score'])) {
+             $score = $result[0]['score'];
+             db_query("INSERT INTO ia_score_user_round (`user_id`, `round_id`, `score`)
+                     VALUES (".db_quote($user_id).", ".db_quote($round_id).", ".db_quote($score).")");
+        }
+    }
+}
+
+// Delete orphan rating entries
+db_query("DELETE ia_rating FROM ia_rating ".
+        "LEFT JOIN ia_user on ia_user.id = ia_rating.user_id ".
+        "LEFT JOIN ia_round on ia_round.id = ia_rating.round_id ".
+        "WHERE (ia_rating.user_id IS NOT NULL AND ia_user.id IS NULL) ".
+        "OR (ia_rating.round_id IS NOT NULL AND ia_round.id IS NULL)");
+
+
+
 // Delete orphan jobs.
 log_print("Deleting orphan jobs.");
 db_query("DELETE ia_job FROM ia_job ".
Index: scripts/migrate-ia-score.php
===================================================================
--- scripts/migrate-ia-score.php	(revision 0)
+++ scripts/migrate-ia-score.php	(revision 0)
@@ -0,0 +1,90 @@
+#! /usr/bin/env php
+<?php
+    require_once('utilities.php');
+    require_once(IA_ROOT_DIR . "common/db/db.php");
+    ini_set("memory_limit", "1024M");
+    db_connect();
+
+    $query = "CREATE TABLE `ia_score_user_round` (
+                user_id int,
+                round_id varchar(64) CHARACTER SET latin1 COLLATE latin1_general_ci,
+                score decimal(11, 4),
+                CONSTRAINT user_round UNIQUE (user_id, round_id)
+                )";
+    db_query($query);
+
+    $query = "CREATE TABLE `ia_score_user_round_task` (
+                user_id int,
+                round_id varchar(64) CHARACTER SET latin1 COLLATE latin1_general_ci,
+                task_id varchar(64) CHARACTER SET latin1 COLLATE latin1_general_ci,
+                score decimal(11, 4),
+                CONSTRAINT user_round_task UNIQUE (user_id, round_id, task_id)
+                )";
+    db_query($query);
+
+    $query = "CREATE TABLE `ia_rating` (
+                user_id int,
+                round_id varchar(64) CHARACTER SET latin1 COLLATE latin1_general_ci,
+                deviation decimal(11, 4),
+                rating decimal(11, 4),
+                CONSTRAINT user_round UNIQUE (user_id, round_id)
+                )";
+    db_query($query);
+
+    $query = "SELECT * FROM `ia_score`";
+    $result = db_fetch_all($query);
+
+    foreach ($result as $score) {
+        if ($score['name'] == 'deviation' || $score['name'] == 'rating') {
+            $where = '`user_id` = '. db_quote($score['user_id']) ." && `round_id` = " . db_quote($score['round_id']);
+
+            $query = "SELECT * FROM `ia_rating` WHERE ".$where;
+            $result = db_fetch_all($query);
+            if (!$result) {
+                 $query = "INSERT INTO `ia_rating`(`user_id`, `round_id`, `deviation`, `rating`)
+                            VALUES (".db_quote($score['user_id']).", ".db_quote($score['round_id']).", '0', '0')";
+                 db_query($query);
+            }
+ 
+            $query = "UPDATE `ia_rating` SET `".$score['name']."` = ".db_quote($score['score'])." WHERE ".$where;
+            db_query($query);
+        } else if ($score['name'] == 'score') {
+            $query = "INSERT INTO `ia_score_user_round_task` (`user_id`, `round_id`, `task_id`, `score`)
+                        VALUES (".implode(',',
+                            array(
+                                db_quote($score['user_id']),
+                                db_quote($score['round_id']), 
+                                db_quote($score['task_id']),
+                                db_quote($score['score']))
+                            ).")";
+            db_query($query);
+
+            $query = "SELECT `score` FROM `ia_score_user_round` WHERE
+                        `user_id` = ".db_quote($score['user_id'])." &&
+                        `round_id` = ".db_quote($score['round_id']);
+            $result = db_fetch_all($query);
+
+            if (!$result) {
+                $query = "INSERT INTO `ia_score_user_round` (`user_id`, `round_id`, `score`)
+                        VALUES (".implode(',',
+                            array(
+                                db_quote($score['user_id']),
+                                db_quote($score['round_id']),
+                                db_quote($score['score'])
+                                )
+                            ).")";
+            } else {
+                $query = "UPDATE `ia_score_user_round` SET `score` = `score` + ".$score['score']." WHERE
+                         `user_id` = ".db_quote($score['user_id'])." &&
+                        `round_id` = ".db_quote($score['round_id']);
+            }
+            db_query($query);
+        } else echo implode(' | ', $score)." dumped\n";
+    }
+
+    $query = "SELECT * FROM `ia_round`";
+    $rounds = db_fetch_all($query);
+
+//    $query = "DROP TABLE `ia_score`";
+//    db_query($query)
+?>

Property changes on: scripts/migrate-ia-score.php
___________________________________________________________________
Added: svn:executable
   + *

Index: scripts/round-statistics
===================================================================
--- scripts/round-statistics	(revision 1105)
+++ scripts/round-statistics	(working copy)
@@ -60,9 +60,8 @@
 // score greater than 0 on at least one task
 $query = sprintf('
 SELECT COUNT(DISTINCT(`user_id`))
-    FROM `ia_score`
-    WHERE `name` = "score" AND
-          `score` > 0 AND
+    FROM `ia_score_user_round`
+    WHERE `score` > 0 AND
           `round_id` LIKE %s', db_quote($round_like));
 
 $result = db_fetch($query);
@@ -80,9 +79,8 @@
     SELECT `user_id`, SUM(`grouped_by_tasks`.`score`) AS `score`
     FROM (
         SELECT `user_id`, `task_id`, `score`
-        FROM `ia_score`
-        WHERE `name` = "score" AND
-              `round_id` LIKE %s
+        FROM `ia_score_user_round_task`
+        WHERE `round_id` LIKE %s
         GROUP BY `user_id`, `task_id`
         ) AS `grouped_by_tasks`
     GROUP BY `user_id`
@@ -100,9 +98,8 @@
     SELECT `user_id`, SUM(`grouped_by_tasks`.`score`) AS `score`
     FROM (
         SELECT `user_id`, `task_id`, `score`
-        FROM `ia_score`
-        WHERE `name` = "score" AND
-              `round_id` LIKE %s AND
+        FROM `ia_score_user_round_task`
+        WHERE `round_id` LIKE %s AND
               `score` > 0
         GROUP BY `user_id`, `task_id`
         ) AS `grouped_by_tasks`
Index: scripts/task-difficulty
===================================================================
--- scripts/task-difficulty	(revision 1105)
+++ scripts/task-difficulty	(working copy)
@@ -62,9 +62,9 @@
 foreach ($tasks as $task) {
     $query = <<<SQL
         SELECT COUNT(*)
-        FROM ia_score
+        FROM ia_score_user_round_task
         LEFT JOIN ia_user ON ia_user.id = user_id
-        WHERE rating_cache > 0 AND ia_score.name='score' AND round_id='arhiva'
+        WHERE rating_cache > 0 AND round_id='arhiva'
                 AND task_id='%s'
 SQL;
     $sample_space = (int)db_query_value(sprintf($query, db_escape($task)));
@@ -79,9 +79,9 @@
     $query = <<<SQL
         SELECT AVG(rating_cache) FROM (
             SELECT rating_cache
-            FROM ia_score
+            FROM ia_score_user_round_task
             LEFT JOIN ia_user ON ia_user.id = user_id
-            WHERE rating_cache > 0 AND ia_score.name='score' AND round_id='arhiva'
+            WHERE rating_cache > 0 AND round_id='arhiva'
                     AND task_id='%s'
             ORDER BY rating_cache
             LIMIT %d
Index: scripts/update-ratings
===================================================================
--- scripts/update-ratings	(revision 1105)
+++ scripts/update-ratings	(working copy)
@@ -79,7 +79,7 @@
 
 // read round scores
 log_print("Reading round scores...");
-$round_rows = score_get_range("score", null, null, $round_id, "user_id", 0, $usercount);
+$round_rows = score_get_rankings($round_id, null, 0, $usercount);
 log_print(count($round_rows) . " users were scored in this round.");
 foreach ($round_rows as $row) {
     log_assert(isset($row['user_name']) && isset($row['score']));
@@ -97,10 +97,8 @@
     $username = $row['username'];
     $user_id = $row['user_id'];
     // for this round (history)
-    score_update("rating", $user_id, null, $round_id,
-                 $users[$username]['rating']);
-    score_update("deviation", $user_id, null, $round_id,
-                 $users[$username]['deviation']);
+    score_update_rating($user_id, $round_id, $users[$username]['deviation'], $users[$username]['rating']);
+
     // cache rating
     $user = $ucheck[$username];
     $user['rating_cache'] = $users[$username]['rating'];
Index: www/macros/macro_rankings.php
===================================================================
--- www/macros/macro_rankings.php	(revision 1105)
+++ www/macros/macro_rankings.php	(working copy)
@@ -9,17 +9,36 @@
 // pre-defined set of contest rounds.
 //
 // Arguments:
-//     rounds   (required) a | (pipe) separated list of round names.
+//     rounds   (required) a | (pipe) separated list of round_id : round_name.
+//              Round name is the name which will appear in the column dedicated
+//              to that round in case detail_round == true
+//              If detail_round == false you can leave just the round_id (see examples)
 //     count    (optional) how many to display at once, defaults to infinity
+//     detail_task   (optional) true/false print score columns for each task
+//     detail_round  (optional) true/false  print score columns for each round
+// Examples:
+//      Macro: Rankings(rounds="preONI2007/1/a | preONI2007/2/a" count = "10")
+//      Columns: | pos | name | score |
 //
-// Examples:
-//      Rankings(rounds="preONI2007/1/a | preONI2007/2/a")
-//      Rankings(rounds="preONI2007/1/a | preONI2007/2/a" count="10")
+//      Rankings(rounds="preONI2007/1/a : round 1 | preONI2007/2/a : round 2" detail_round = "true")
+//      Columns: | pos | name | round 1 | round 2 | total |
+//
+//      Rankings(rounds="preONI2007/1/a : round 1 | preONI2007/2/a : round 2" detail_round = "true" detail_task = "true")
+//      Columns: | pos | name | task | task | task | round 1 | task | task | task | round 2 | total |
 function macro_rankings($args) {
     $args['param_prefix'] = 'rankings_';
     if (isset($args['count'])) {
         $args['display_entries'] = $args['count'];
     }
+
+    // Detail parameters
+    $detail_round = getattr($args, 'detail_round', 'false');
+    $detail_task = getattr($args, 'detail_task', 'false');
+
+    $detail_round = ($detail_round == 'true');
+    $detail_task = ($detail_task == 'true');
+
+    // Paginator options
     $options = pager_init_options($args);
     $options['show_count'] = true;
 
@@ -28,10 +47,18 @@
     if ($roundstr == '') {
         return macro_error("Parameters 'rounds' is required.");
     }
-    $rounds = preg_split('/\s*\|\s*/', $roundstr);
+    $round_param = preg_split('/\s*\|\s*/', $roundstr);
+    $rounds = array();
+    foreach ($round_param as $param) {
+        $round = preg_split('/\s*\:\s*/', $param);
+        array_push($rounds, array(
+            'round_id' => $round[0],
+            'round_name' => getattr($round, 1, ' ')
+            )
+        );
+    }
 
-    // FIXME: user / task parameters.
-    $rankings = score_get_range("score", null, null, $rounds, "user_id", $options['first_entry'], $options['display_entries'], true);
+    // Generating Table
 
     $column_infos = array(
         array(
@@ -46,16 +73,65 @@
             'rowform' => create_function_cached('$row',
                                          'return format_user_normal($row["user_name"], $row["user_full"], $row["user_rating"]);'),
         ),
-        array(
-            'title' => 'Scor',
+    );
+
+    $columns = array();
+    $tasks = array();
+    if ($detail_round == true || $detail_task == true) {
+        foreach ($rounds as $round) {
+            $round_id = $round['round_id'];
+
+            if ($detail_task == true) {
+            $new_tasks = round_get_tasks($round_id, null, null, null, null, null);
+            foreach ($new_tasks as $task) {
+                array_push($columns, array(
+                    'name' => $task['id'],
+                    'type' => 'task',
+                    'title' => $task['title']
+                ));
+                array_push($tasks, $task);
+                }
+            }
+
+            if ($detail_round == true) {
+                array_push($columns, array(
+                    'name' => $round['round_id'],
+                    'type' => 'round',
+                    'title' => $round['round_name']
+                ));
+            }
+        }
+    }
+
+    foreach ($columns as $column) {
+        array_push($column_infos, array(
+            'title' => $column['title'],
+            'key' => $column['name'],
+            'rowform' => create_function_cached('$row', 'return round($row[\''.$column['name'].'\']);'),
+            'css_class' => 'number score'
+            ));
+    }
+
+    $total = 'Scor';
+    if ($detail_round == true || $detail_task == true) {
+        $total = 'Total';
+    }
+    array_push($column_infos, array(
+            'title' => $total,
             'key' => 'score',
             'rowform' => create_function_cached('$row', 'return round($row[\'score\']);'),
             'css_class' => 'number score'
-        ),
+        )
     );
 
+    $round_ids = array();
+    foreach ($rounds as $round) {
+        array_push($round_ids, $round['round_id']);
+    }
+    $rankings = score_get_rankings($round_ids, $tasks, $options['first_entry'], $options['display_entries'], $detail_task, $detail_round);
+
     if (pager_needs_total_entries($options)) {
-        $options['total_entries'] = score_get_count("score", null, null, $rounds, 'user_id');
+        $options['total_entries'] = score_get_count(null, null, $round_ids);
     }
 
     if (0 >= count($rankings)) {
Index: www/macros/macro_tasks.php
===================================================================
--- www/macros/macro_tasks.php	(revision 1105)
+++ www/macros/macro_tasks.php	(working copy)
@@ -130,10 +130,10 @@
     $tasks = round_get_tasks($round_id,
              $options['first_entry'],
              $options['display_entries'],
-             $user_id, ($scores ? 'score' : null),
+             $user_id, $scores,
              $filter);
     $options['total_entries'] = round_get_task_count(
-             $round_id, $user_id, ($scores ? 'score' : null), $filter);
+             $round_id, $user_id, $filter);
     $options['row_style'] = 'task_row_style';
     $options['css_class'] = 'tasks';
 
