<?php

require_once("ressources/relations.inc");
require_once("ressources/db.inc");

/* Les prototypes des fonctions :
* ===============================>
*   checkLogin($user, $passwd);
*   randomCenterNode();
*   randomCloudNode();
*   cgBuildResultSets($cloudSize, $centerEid, $r1, $r2);
*   cgChooseRelations();
*   cgBuildCloud($centerEid, $cloudSize, $sources, $sumWeights);
*   cgInsert($centerEid, $cloud, $r1, $r2, $totalDifficulty);
*   randomGameCore();
*   randomGame();
*   formatWord($word);
*   game2json($user, $gameId);
*   game2array($user, $gameId);
*   createGame($nbParties, $mode);
*   createGameCore($cloudSize);
*   getGame($user, $nbGames, $mode);
*   computeScore($probas, $difficulty, $answer, $userReputation);
*   computeUserReputation($score);
*   normalizeProbas($row);
*   setGame($user, $pgid, $gid, $answers);
*   get_game_relations();
*   getGameRelationsJSON();
*   setGameGetScore($user, $pgid, $gid, $answers);
*   insertNode($node);
*   getNodeEid($node);
*/


/**  Vérifie la validité du couple nom d'utilisateur / mot de passe.
* @param user : Le nom d'utilisateur.
* @param passwd : Le mot de passe.
* @return boolean : true si OK sinon false.
*/
function checkLogin($user, $passwd) {
	if(isset($_SESSION['userId']))
		return true;
	connect($user, $passwd);
}

function connect($user, $passwd) {
	$db = getDB();
	$hashPasswd = md5($passwd);
	$loginIsOk = ($hashPasswd == $db->querySingle("SELECT hash_passwd FROM user WHERE login='".$user."';"));
	return $loginIsOk;
}

/** Selectionne aléatoirement l'eid d'un mot central.
* @return eid : Identifiant d'un mot central, NULL en cas d'erreur.
*/
function randomCenterNode()
{
	$db = getDB();
	return $db->querySingle("select eid from random_center_node where rowid = (abs(random()) % (select max(rowid) from random_center_node))+1;");
}

/** Selectionne aléatoirement un noeud d'un nuage.
* @return eid : L'identifiant du noeud.
*/
function randomCloudNode()
{
	$db = getDB();
	return $db->querySingle("select eid from random_cloud_node where rowid = (abs(random()) % (select max(rowid) from random_cloud_node))+1;");
}

/**
* @param cloudSize : Taille du nuage.
* @param centerEid : Identifiant du mot central.
* @param r1 Type de la relation 1.
* @param r2 Type de la relation 2.
*/
function cgBuildResultSets($cloudSize, $centerEid, $r1, $r2)
{
	$db = getDB();
	// 'w' => weight (poids), 'd' => difficulté, 's' => select
	// Le select doit ranvoyer trois colonnes :
	//   eid => l'eid du mot à mettre dans le nuage,
	//   r1 => la probabilité pour que le mot soit dans r1, entre -1 et 1 (négatif = ne devrait pas y être, positif = devrait y être à coup sûr, 0 = on sait pas).
	$typer1r2 = "type in ($r1, $r2)";
	$banned_types = "4, 12, 36, 18, 29, 45, 46, 47, 48, 1000, 1001";
	
	$sources = array(
		// Voisins 1 saut du bon type (= relations déjà existantes)
		array('w'=>40, 'd'=>1, 's'=>"select end as eid, type = $r1 as r1, type = $r2 as r2, 0 as r0, 0 as trash from relation where start = $centerEid and $typer1r2 order by random();"),
		// Voisins 1 saut via r_associated (0), donc qu'on voudrait spécifier si possible.
		array('w'=>40, 'd'=>2, 's'=>"select end as eid, 0.25 as r1, 0.25 as r2, 0.5 as r0, 0 as trash from relation where start = $centerEid and type = 0 order by random();"),
		// Voisins 1 saut via les autres relations
		array('w'=>20, 'd'=>3.1, 's'=>"select end as eid, 0.1 as r1, 0.1 as r2, 0.8 as r0, 0 as trash from relation where start = $centerEid and type not in (0, $r1, $r2, $banned_types) order by random();"),
		// Voisins 2 sauts, avec un mix de R1 et R2 pour les liens. Par ex [ A -R1-> B -R2-> C ] ou bien [ A -R2-> B -R2-> C ]
		// Version optimisée de : "select end as eid from relation where $typer1r2 and start in oneHopWithType order by random();"
		array('w'=>30, 'd'=>3.2, 's'=>"select B.end as eid, ((A.type = $r1) + (B.type = $r1)) / 3. as r1, ((A.type = $r2) + (B.type = $r2)) / 3. as r2, 1/6. as r0, 1/6. as trash from relation as A, relation as B where A.start = $centerEid and A.$typer1r2 and B.start = A.end and B.$typer1r2 order by random();"),
		// Voisins 1 saut r1/r2 + 1 saut synonyme
		// Version optimisée de : "select end as eid from relation where start in oneHopWithType and type = 5 order by random()";
		array('w'=>20, 'd'=>5, 's'=>"select B.end as eid, (A.type = $r1) * 0.75 as r1, (A.type = $r2) * 0.75 as r2, 0.25 as r0, 0 as trash from relation as A, relation as B where A.start = $centerEid and A.$typer1r2 and B.start = A.end and B.type = 5 order by random();"),
		// Version optimisée de : "select end as eid from relation where start in (select end from relation where start = $centerEid and type = 5) and $typer1r2 order by random();"
		array('w'=>20, 'd'=>6, 's'=>"select B.end as eid, (B.type = $r1) * 0.75 as r1, (B.type = $r2) * 0.75 as r2, 0.25 as r0, 0 as trash from relation as A, relation as B where A.start = $centerEid and A.type = 5 and B.start = A.end and B.$typer1r2 order by random();"),
		// Voisins 2 sauts (tous)
		// Version optimisée de : "select end as eid, 0.1 as r1, 0.1 as r2, 0.3 as r0, 0.5 as trash from relation where start in (select end from relation where start = $centerEid and type not in ($banned_types)) and type not in ($banned_types) order by random();"
		array('w'=>10, 'd'=>8, 's'=>"select x as eid, 0.1 as r1, 0.1 as r2, 0.3 as r0, 0.5 as trash from (select x from (select X.eid + Y.dumb as x from (select B.end as eid from relation as A, relation as B where A.type not in ($banned_types) and A.start = $centerEid and B.type not in ($banned_types) and B.start = A.end limit ".($cloudSize*4).") as X, (select 0 as dumb) as Y)) order by random();"),
		// Centre pointe vers X, M pointe vers X aussi, on prend M.
		// Version optimisée de : "select start as eid from relation where end in (select end from relation where start = $centerEid) and type not in ($banned_types) order by random();"
		// Ce n'est toujours pas ça… : "select eid from (select B.start as eid from relation as A, relation as B where A.type not in ($banned_types) and A.start = $centerEid and B.type not in ($banned_types) and B.end = A.end limit 1) order by random();"
		// Tordu, mais ça marche \o/ . En fait il faut empêcher l'optimiseur de ramener le random avant le limit (et l'optimiseur est malin… :)
		array('w'=>10, 'd'=>8, 's'=>"select x as eid, 0.1 as r1, 0.1 as r2, 0.2 as r0, 0.6 as trash from (select x from (select X.eid + Y.dumb as x from (select B.start as eid from relation as A, relation as B where A.type not in ($banned_types) and A.start = $centerEid and B.type not in ($banned_types) and B.end = A.end limit ".($cloudSize*4).") as X, (select 0 as dumb) as Y)) order by random();"),
		'rand' => array('w'=>5, 'd'=>10, 's'=>false) // random. Le r1 et r2 de random sont juste en-dessous
	);

	$sumWeights = 0;
	
	foreach ($sources as $k => $x)
	{
		$sumWeights += $x['w'];
		$sources[$k]['rsPos'] = 0;
		$sources[$k]['rsSize'] = 0;
		
		if ($x['s'] !== false)
		{
			$sources[$k]['resultSet'] = array();
			$res = $db->query($x['s']);
			$i = 0;
			
			while ($i < $cloudSize && $sources[$k]['resultSet'][] = $res->fetchArray())
			{
				$i++;
				$sources[$k]['rsSize']++;
			}
		} 
		else
		{
			$sources[$k]['resultSet'] = array();
			
			for ($i = 0; $i < $cloudSize; $i++)
			{
				$sources[$k]['resultSet'][] = array('eid'=>randomCloudNode(), 'r1'=>0, 'r2'=>0, 'r0'=>0, 'trash'=>1);
				$sources[$k]['rsSize']++;
			}
		}
	}

	return array($sources, $sumWeights);
}


/** Sélectionne aléatoirement deux relations.
* @return array : Tableau avec la relation 1 et la relation 2.
*/
function cgChooseRelations()
{
	$relations = array(5, 7, 9, 10, 13, 14, 22);
	$r1 = rand(0,count($relations)-1);
	$r2 = rand(0,count($relations)-2);

	if ($r2 >= $r1)
		$r2++;

	$r1 = $relations[$r1];
	$r2 = $relations[$r2];

	return array($r1, $r2);
}

/** Génération d'un nuage pour un mot central.
* @param cloudSize : Taille du nuage.
* @param sources Les sources.
* @param sumWeights La somme des poids.
* @return array : Tableau avec comme premier élément le nuage et comme second élément le total de difficulté.
*/
function cgBuildCloud($centerEid, $cloudSize, $sources, $sumWeights)
{
	$db = getDB();
	// On boucle tant qu'il n'y a pas eu au moins 2 sources épuisées
	$cloud = array();
	$nbFailed = 0;
	$i = 0;
	$totalDifficulty = 0;
	
	while ($i < $cloudSize && $nbFailed < 10*$cloudSize)
	{
		// On choisit une source aléatoire en tennant compte des poids.
		$rands = rand(1,$sumWeights);
		$sumw = 0;
		if (!isset($sources['rand'])) {
			break;
		}
		$src = $sources['rand'];
		$srck = 'rand';

		// Sélection d'une source aléatoire
		foreach ($sources as $k => $x)
		{
			$sumw += $x['w'];
			
			if ($rands < $sumw)
			{
				$src = $x;
				$srck = $k;
				break;
			}
		}
		
		// Vérification si on est à la fin du ResultSet de cette source.
		if ($src['rsPos'] >= $src['rsSize'])
		{
			$nbFailed++;

			$sumWeights -= $src['w'];
			unset($sources[$srck]);

			continue;
		}
		
		// On récupère un résultat de cette source.
		$res = $src['resultSet'][$src['rsPos']];
		$sources[$srck]['rsPos']++;

		// On vérifie si le mot n'a pas déjà été sélectionné.
		$rejected = false;
		// Ne pas mettre le mot central dans le nuage.
		if ($res['eid'] == $centerEid) { continue; }
		$nodeName = $db->querySingle("select name from node where eid=".$res['eid'].";");
		if (substr($nodeName, 0, 2) == "::") { continue; }
		foreach ($cloud as $c) {
			if ($c['eid'] == $res['eid']) {
				$nbFailed++;
				$rejected = true;
				break;
			}
		}
		if ($rejected) { continue; }

		// position dans le nuage, difficulté, eid, probaR1, probaR2
		$totalDifficulty += $src['d'];
		$cloud[$i] = array('pos'=>$i++, 'd'=>$src['d'], 'eid'=>$res['eid'], 'probaR1'=>$res['r1'], 'probaR2'=>$res['r2'], 'probaR0'=>$res['r0'], 'probaTrash'=>$res['trash']);
	}

	$res = $sources['rand']['resultSet'][0];
	
	while ($i < $cloudSize)
	{
		$totalDifficulty += $sources['rand']['d'];
		$cloud[$i] = array('pos'=>$i++, 'd'=>$sources['rand']['d'], 'eid'=>randomCloudNode(), 'probaR1'=>$res['r1'], 'probaR2'=>$res['r2'], 'probaR0'=>$res['r0'], 'probaTrash'=>$res['trash']);
	}

	return array($cloud, $totalDifficulty);
}


/** Insère la partie dans la base de données.
* @param centerEid : Identifiant du mot central.
* @param cloud : Le nuage.
* @param r1 : Le type de la relation 1.
* @param r2 : Le type de la relation 2.
* @param totalDifficulty : La difficulté totale.
*/
function cgInsert($centerEid, $cloud, $r1, $r2, $totalDifficulty)
{
	$db = getDB();
	// Insère dans la base une partie avec le mot central $centerEid, le nuage $cloud et les relations $r1 et $r2
	$db->exec("begin transaction;");
	$db->exec("INSERT INTO game(gid, eid_central_word, relation_1, relation_2, difficulty)
		   VALUES (null, $centerEid, $r1, $r2, $totalDifficulty);");
	$gid = $db->lastInsertRowID();
	
	$t = time();
	$db->exec("INSERT INTO played_game(pgid, gid, login, timestamp)
		   VALUES (null, $gid, null, $t);");
	$pgid1 = $db->lastInsertRowID();
	$db->exec("INSERT INTO played_game(pgid, gid, login, timestamp)
		   VALUES (null, $gid, null, $t);");
	$pgid2 = $db->lastInsertRowID();
	$db->exec("INSERT INTO played_game(pgid, gid, login, timestamp)
		   VALUES (null, $gid, null, $t);");
	$pgid0 = $db->lastInsertRowID();
	$db->exec("INSERT INTO played_game(pgid, gid, login, timestamp)
		   VALUES (null, $gid, null, $t);");
	$pgidT = $db->lastInsertRowID();

	// TODO : R0 et Trash + corrections
	foreach ($cloud as $c)
	{
		$totalWeight = $c['probaR1'] + $c['probaR2'] + $c['probaR0'] + $c['probaTrash'];
		$db->exec("INSERT INTO game_cloud(gid, num, difficulty, eid_word, totalWeight, probaR1, probaR2, probaR0, probaTrash)
			   VALUES ($gid, ".$c['pos'].", ".$c['d'].", ".$c['eid'].", $totalWeight, ".$c['probaR1'].", ".$c['probaR2'].", ".$c['probaR0'].", ".$c['probaTrash'].");");
		
		$db->exec("INSERT INTO played_game_cloud(pgid, gid, type, num, relation, weight, score)
			   VALUES ($pgid1, $gid, 0, ".$c['pos'].", $r1, ".$c['probaR1'].", 0);");

		$db->exec("INSERT INTO played_game_cloud(pgid, gid, type, num, relation, weight, score)
			   VALUES ($pgid2, $gid, 0, ".$c['pos'].", $r2, ".$c['probaR2'].", 0);");

		$db->exec("INSERT INTO played_game_cloud(pgid, gid, type, num, relation, weight, score)
			   VALUES ($pgid0, $gid, 0, ".$c['pos'].", 0, ".$c['probaR0'].", 0);");

		$db->exec("INSERT INTO played_game_cloud(pgid, gid, type, num, relation, weight, score)
			   VALUES ($pgidT, $gid, 0, ".$c['pos'].", -1, ".$c['probaTrash'].", 0);");
	}

	$db->exec("commit;");
}

function decodeAndInsertGame($user,$game) {
	$badWords = Array();	
	$centerEid = getNodeEid($game['center']);
	$r1 = $game['relations'][0];
	$r2 = $game['relations'][1];

	if($centerEid === null)
		$badWords[] = $game['center'];
		
	foreach($game['cloud'] as $key => $w) {
		if ($w['name'] == "") continue;
		$cloudEid = getNodeEid($w['name']);
		$cloud[] = Array("eid" => $cloudEid,
								"pos" => $key,
								"d" => 5,
								"probaR1" => $w['relations'][0] ? "1" : "0",
								"probaR2" => $w['relations'][1] ? "1" : "0",
								"probaR0" => $w['relations'][2] ? "1" : "0",
								"probaTrash" => $w['relations'][3] ? "1" : "0");
		
		if($cloudEid === null)
			$badWords[] = $w['name'];
	}			

	if(count($badWords) > 0) {
		echo JSON_encode($badWords);
	} else if (count($cloud) < 5) {
		echo "false";
	} else {
		insertCreatedGame($centerEid,$cloud,$r1,$r2,10,$user);
		echo "true";
	}
}

function insertCreatedGame($centerEid, $cloud, $r1, $r2, $totalDifficulty,$userName)
{
	$db = getDB();
	// Insère dans la base une partie avec le mot central $centerEid, le nuage $cloud et les relations $r1 et $r2
	$db->exec("begin transaction;");
	$db->exec("INSERT INTO game(gid, eid_central_word, relation_1, relation_2, difficulty, author)
		   VALUES (null, $centerEid, $r1, $r2, $totalDifficulty, '$userName');");
	$gid = $db->lastInsertRowID();
	
	$t = time();
	$db->exec("INSERT INTO played_game(pgid, gid, login, timestamp)
		   VALUES (null, $gid, null, $t);");
	$pgid1 = $db->lastInsertRowID();
	$db->exec("INSERT INTO played_game(pgid, gid, login, timestamp)
		   VALUES (null, $gid, null, $t);");
	$pgid2 = $db->lastInsertRowID();
	$db->exec("INSERT INTO played_game(pgid, gid, login, timestamp)
		   VALUES (null, $gid, null, $t);");
	$pgid0 = $db->lastInsertRowID();
	$db->exec("INSERT INTO played_game(pgid, gid, login, timestamp)
		   VALUES (null, $gid, null, $t);");
	$pgidT = $db->lastInsertRowID();

	// TODO : R0 et Trash + corrections
	foreach ($cloud as $c)
	{
		$totalWeight = $c['probaR1'] + $c['probaR2'] + $c['probaR0'] + $c['probaTrash'];
		$db->exec("INSERT INTO game_cloud(gid, num, difficulty, eid_word, totalWeight, probaR1, probaR2, probaR0, probaTrash)
			   VALUES ($gid, ".$c['pos'].", ".$c['d'].", ".$c['eid'].", $totalWeight, ".$c['probaR1'].", ".$c['probaR2'].", ".$c['probaR0'].", ".$c['probaTrash'].");");
		
		$db->exec("INSERT INTO played_game_cloud(pgid, gid, type, num, relation, weight, score)
			   VALUES ($pgid1, $gid, 0, ".$c['pos'].", $r1, ".$c['probaR1'].", 0);");

		$db->exec("INSERT INTO played_game_cloud(pgid, gid, type, num, relation, weight, score)
			   VALUES ($pgid2, $gid, 0, ".$c['pos'].", $r2, ".$c['probaR2'].", 0);");

		$db->exec("INSERT INTO played_game_cloud(pgid, gid, type, num, relation, weight, score)
			   VALUES ($pgid0, $gid, 0, ".$c['pos'].", 0, ".$c['probaR0'].", 0);");

		$db->exec("INSERT INTO played_game_cloud(pgid, gid, type, num, relation, weight, score)
			   VALUES ($pgidT, $gid, 0, ".$c['pos'].", -1, ".$c['probaTrash'].", 0);");
	}

	$db->exec("commit;");
}

/** Retourne un identifiant de partie aléatoire de la liste de parties jouables
* @return gid : Identifiant de partie.
*/
function randomGameCore() {
	$db = getDB();
	return $db->querySingle("select gid from game where gid = (abs(random()) % (select max(gid) from game))+1 or gid = (select max(gid) from game where gid > 0) order by gid limit 1;");
}

/** Sélection aléatoire d'une partie de la base de données parmis les parties à jouer.
* @return gid : Identifiant de la partie selectionnée.
*/
function randomGame()
{
	$gid = randomGameCore();

	if ($gid === null) {
		// TODO : séparer ces créations de parties dans une fonction qui détecte le mode toussa.
		for ($i = 0; $i < 100; $i++)
			createGameCore(rand(5,15));

		$gid = randomGameCore();

		if ($gid === null)
			throw new Exception("Erreur lors de la récupération de la partie. Vérifiez qu'il y a au moins une partie.", 6);
	}
	return $gid;
}

/** Formatage des mots lorsqu'il y a des généralisations/spécifications par le symbole ">".
* @param word : Le mot que l'on veut reformater.
* @return word : le mot formaté.
*/
function formatWord($word) {
	$db = getDB();
	$res = "";
	$stack = array();

	while (($pos = strpos($word, ">")) !== false) {
		$res .= substr($word,0,$pos) . " (";
		$eid = intval(substr($word,$pos+1));
		if ($eid == 0) { throw new Exception("Erreur lors du suivi des pointeurs de spécialisation du mot $word.", 7); }
		if (in_array($eid, $stack)) { throw new Exception("Boucle rencontrée lors du suivi des pointeurs de spécialisation du mot $word.", 8); }
		if (count($stack) > 10) { throw new Exception("Trop de niveaux de récursions lors du suivi des pointeurs de spécialisation du mot $word.", 9); }
		$stack[] = $eid;
		$word = $db->querySingle("select name from node where eid = $eid");
	}

	$res .= $word;

	for ($depth = count($stack); $depth > 0; $depth--)
		$res .= ')';

	return $res;
}

/** Formate une partie en JSON en l'imprimant.
* @param user : l'utilisateur.
* @param gameId : L'identifiant d'une partie.
*/
function game2json($user, $gameId)
{
	global $stringRelations;
	$db = getDB();
	// TODO : factoriser avec game2array() .
	// TODO : planter si la requête suivante échoue pour quelque raison que ce soit.
	$db->exec("INSERT INTO played_game(pgid, gid, login, timestamp) VALUES (null, ".$gameId.", '$user', -1);");
	$pgid = $db->lastInsertRowID();
	
	// TODO Yoann : faire des tests d'erreur pour ces select ?
	$game = $db->query("select gid, (select name from node where eid = eid_central_word) as name_central_word, eid_central_word, relation_1, relation_2 from game where gid = ".$gameId.";");
	$game = $game->fetchArray();

	$retstr = "";
	$retstr .= '{"gid":'.$gameId.',"pgid":'.$pgid.',"relations":[';
	$retstr .= '{"id":'.$game['relation_1'].', "name":'.json_encode(''.formatWord($stringRelations[$game['relation_1']])).'}';
	$retstr .= ', {"id":'.$game['relation_2'].', "name":'.json_encode(''.formatWord($stringRelations[$game['relation_2']])).'}';
	$retstr .= ', {"id":0, "name":'.json_encode(''.formatWord($stringRelations[0])).'}';
	$retstr .= ', {"id":-1, "name":'.json_encode(''.formatWord($stringRelations[-1])).'}],';
	//	$retstr .= '{"gid":'.$gameId.',"pgid":'.$pgid.',"cat1":'.$game['relation_1'].',"cat2":'.$game['relation_2'].',"cat3":0,"cat4":-1,';
	$retstr .= '"center":{"id":'.$game['eid_central_word'].',"name":'.json_encode(''.formatWord($game['name_central_word'])).'},';

	$cloudsize = 0;
	$tmp_retstr = '';
	$notfirst = false;
	$res = $db->query("select eid_word,(select name from node where eid=eid_word) as name_word from game_cloud where gid = ".$gameId.";");
	while ($x = $res->fetchArray())
	{
		if ($notfirst) 
			$tmp_retstr .= ",";
		else
			$notfirst=true;

		$tmp_retstr .= '{"id":'.$x['eid_word'].',"name":'.json_encode("".formatWord($x['name_word'])).'}';
		$cloudsize++;
	}
	$retstr .= '"cloudsize":'.$cloudsize.',"cloud":[';
	$retstr .= $tmp_retstr;
	
	$retstr .= "]}";
	return $retstr;
}

/** Récupère une partie sous forme de tableau.
* @param db : descripteur de la bdd (obtenu avec getDB()).
* @param user : Login de l'utilisateur demandant la partie.
* @param gameId : L'identifiant d'une partie.
*/
function game2array($user, $gameId)
{
	$db = getDB();
	// TODO : planter si la requête suivante échoue pour quelque raison que ce soit.
	$db->exec("INSERT INTO played_game(pgid, gid, login, timestamp) VALUES (null, ".$gameId.", '$user', -1);");
	$pgid = $db->lastInsertRowID();
	
	// TODO Yoann : faire des tests d'erreur pour ces select ?
	$game = $db->query("select gid, (select name from node where eid = eid_central_word) as name_central_word, eid_central_word, relation_1, relation_2 from game where gid = ".$gameId.";");
	$game = $game->fetchArray();

	$ret = array();
	$ret['gid'] = $gameId;
	$ret['pgid'] = $pgid;
	$ret['cat1'] = $game['relation_1'];
	$ret['cat2'] = $game['relation_2'];
	$ret['cat3'] = 0;
	$ret['cat4'] = -1;
	$ret['center'] = array('id' => $game['eid_central_word'], 'name' => formatWord($game['name_central_word']));
	$ret['cloud'] = array(); // TODO ! compter dynamiquement.
	
	$res = $db->query("select eid_word,(select name from node where eid=eid_word) as name_word, num, difficulty, totalWeight, probaR1, probaR2, probaR0, probaTrash from game_cloud where gid = ".$gameId.";");
	
	while ($x = $res->fetchArray())
	{
		$ret['cloud'][$x['num']] = array(
			'id' => $x['eid_word'],
			'name' => formatWord($x['name_word']),
			'difficulty' => $x['difficulty'],
			'totalWeight' => $x['totalWeight'],
			'probaR1' => $x['probaR1'],
			'probaR2' => $x['probaR2'],
			'probaR0' => $x['probaR0'],
			'probaTrash' => $x['probaTrash'],
			'probas' => normalizeProbas($x)
		);
	}
	
	$ret['cloudsize'] = count($ret['cloud']);
	return $ret;
}


/** Création d'un lot de parties suivant un mode donnée.
* @param nbParties : le nombre de parties à créer.
* @param mode : Le mode de jeu pour les parties à créer.
*/
function createGame($nbParties, $mode)
{
	for ($i = 0; $i < $nbParties; $i++)
		createGameCore(rand(5,15));
}

/** Génère une partie (mode normal pour l'instant) pour une certaine taille de nuage.
* @param cloudSize : Taille du nuage.
*
* Est appelée par randomGame(), donc il faudra adapter quand on aura plusieurs modes, par exemple en ayant une fonction intermédiaire qui puisse être appelée par createGame et randomGame.
*/
function createGameCore($cloudSize)
{
	// select random node
	$centerEid = randomCenterNode();

	$r1 = cgChooseRelations(); $r2 = $r1[1]; $r1 = $r1[0];
	$sources = cgBuildResultSets($cloudSize, $centerEid, $r1, $r2); $sumWeights = $sources[1]; $sources = $sources[0];
	$cloud = cgBuildCloud($centerEid, $cloudSize, $sources, $sumWeights); $totalDifficulty = $cloud[1]; $cloud = $cloud[0];
	cgInsert($centerEid, $cloud, $r1, $r2, $totalDifficulty);
}

/** Récupération d'une partie.
* @param user : L'identifiant de l'utilisateur.
* @param nbGames : Le nombre de parties à récupérer.
* @param mode : Le mode de jeu des parties à récupérer.
*/
function getGame($user)
{
	echo game2json($user, randomGame());
}


function computeScore($probas, $difficulty, $answer, $userReputation) {
	// Calcul du score. Score = proba[réponse de l'utilisateur]*coeff1 - proba[autres reponses]*coeff2 + bonus
	// score = - proba[autres reponses]*coeff2
	// On aura donc -5 <= score <= 0
	$score = -5 * (($probas[0] + $probas[1] + $probas[2] + $probas[3]) - $probas[$answer]);
	
	// score = proba[réponse de l'utilisateur]*coeff1 - proba[autres reponses]*coeff2
	// On aura donc -5 <= score <= 10
	$score += 10 * $probas[$answer];
	
	// On est indulgent si la réponse est 3 (poubelle) :
	if ($answer == 3 && $score < 0) {
		$score = $score / 2;
	}
	
	// Adapter le score en fonction de la réputation de l'utilisateur (quand il est jeune, augmenter le score pour le motiver).
	// On aura donc -5 <= score <= 15
	if ($score > 3) {
		$score += max(0, min(5, 5 - $userReputation));
	}
	
	return round($score);
}

/** Calcul de la réputation de l'utilisateur selon son score.
* @param score : Le score du joueur.
*/
function computeUserReputation($score) {
	return max(round(log(max($score/10,1))*100)/100, 0);
}

/** Formatage des probalitées dans un tableau.
* @param row : le vecteur de probabilités.
* @return array : Le vecteur de probabilités normalisé.
*/
function normalizeProbas($row) {
	return array($row['probaR1']/$row['totalWeight'], $row['probaR2']/$row['totalWeight'], $row['probaR0']/$row['totalWeight'], $row['probaTrash']/$row['totalWeight']);
}

/** Insertion des données d'une partie joué par un utilisateur.
* @param user : L'identifiant de l'utilisateur ayant joué à la partie.
* @param pgid : L'identifiant de la partie jouée.
* @param gid : L'identifiant du jeu auquel la partie appartient.
* @return score : Le score réalisé par le joueur.
*/
function setGame($user, $pgid, $gid, $answers)
{
	$db = getDB();
	if ('ok' !== $db->querySingle("SELECT 'ok' FROM played_game WHERE pgid = $pgid and $gid = $gid and login = '$user' and timestamp = -1;")) {
		return getGameScores($user, $pgid, $gid);
	}
	
	$userReputation = computeUserReputation($db->querySingle("SELECT score FROM user WHERE login='".SQLite3::escapeString($user)."';"));
	
	$db->exec("begin transaction;");
	$db->exec("update played_game set timestamp = ".time()." where pgid = $pgid;");

	$r0 = 0;
	$trash = -1;
	$r1 = $db->querySingle("SELECT relation_1, relation_2 FROM game WHERE gid = $gid;", true);
	$r2 = $r1['relation_2'];
	$r1 = $r1['relation_1'];
	$res = $db->query("SELECT num, difficulty, totalWeight, probaR1, probaR2, probaR0, probaTrash FROM game_cloud WHERE gid = $gid;");
	$gameScore = 0;
	$scores = array();
	
	while ($row = $res->fetchArray())
	{
		$num = intval($row['num']);
		if (!isset($answers[$num])) {
			throw new Exception("Cette requête \"Set partie\" ne donne pas de réponse (une relation) pour le mot numéro $num de la partie.", 5);
		}
		$relanswer = intval($answers[$num]);

		switch ($relanswer) 
		{
			case $r1:    $answer = 0; $probaRx = "probaR1"; break;
			case $r2:    $answer = 1; $probaRx = "probaR2"; break;
			case $r0:    $answer = 2; $probaRx = "probaR0"; break;
			case $trash: $answer = 3; $probaRx = "probaTrash"; break;
			default:     throw new Exception("Réponse ($relanswer) invalide pour le mot $num. Les réponses possibles sont : $r1, $r2, $r0, $trash", 5);
		}
			
		$wordScore = computeScore(normalizeProbas($row), $row['difficulty'], $answer, $userReputation);
		$gameScore += $wordScore;
		$scores[$num] = $wordScore;
		
		$db->exec("insert into played_game_cloud(pgid, gid, type, num, relation, weight, score) values($pgid, $gid, 1, $num, $r1, ".$userReputation.", ".$wordScore.");");
		$db->exec("update game_cloud set $probaRx = $probaRx + ".max(min($userReputation/2,5),0)." where gid = $gid and num = $num;");
		$db->exec("update game_cloud set totalWeight = totalWeight + ".max(min($userReputation/2,5),0)." where gid = $gid and num = $num;");
	}
	$db->exec("update user set score = score + ".$gameScore." where login = '$user';");

	$db->exec("commit;");
	
	return array(
		'scoreTotal' => $gameScore,
		'alreadyPlayed' => false,
		'scores' => $scores
	);
}

function getGameScores($user, $pgid, $gid) {
	$db = getDB();
	$timestamp = $db->querySingle("SELECT timestamp FROM played_game WHERE pgid = $pgid and $gid = $gid and login = '$user';");
	if ($timestamp == -1) {
		throw new Exception("Cette partie n'a jamais été jouée.", 4); // TODO : code d'erreur en doublon avec celui ci-dessous.
	} else if ($timestamp == null) {
		throw new Exception("Cette partie n'est associée à votre nom d'utilisateur.", 4);
	}
	
	$gameScore = 0;
	$scores = array();
	$res = $db->query("SELECT num,score from played_game_cloud where pgid = $pgid and gid = $gid;");
	while ($row = $res->fetchArray())
	{
		$gameScore += $row['score'];
		$scores[$row['num']] = $row['score'];
	}
	return array(
		'scoreTotal' => $gameScore,
		'alreadyPlayed' => true,
		'scores' => $scores
	);
}

/** Fourni l'ensembles des relations pouvant apparaître dans le jeu.
* @return array : un tableau de realtions.
*/
function get_game_relations()
{
		$reqlations = array();

		$relations[] = 5;
		$relations[] = 7;
		$relations[] = 9;
		$relations[] = 10;
		$relations[] = 14;
		$relations[] = 22;

		return $relations;
}

function getGameRelationsJSON() {
	$json = "{";
	global $stringRelations;
	
	foreach($stringRelations as $id=>$description)
		if($id == -1)
			$json .= '"'.$id.'":"'.$description.'"';
		else
			$json .= ',"'.$id.'":"'.$description.'"';
			
	$json .= '}';
	
	return $json;
}

function setGameGetScore($user, $pgid, $gid, $answers) {
	$scores = setGame($user, intval($pgid), intval($gid), $answers);
	// On renvoie une nouvelle partie pour garder le client toujours bien alimenté.
	$scores['newGame'] = game2json($user, randomGame());
	$scores['minScore'] = -5;
	$scores['maxScore'] = 10;
	echo json_encode($scores);
}

/** Insère dans la base de données le noeud si il n'existe pas encore.
* @param node : le noeud à ajouter.
*/
function insertNode($node) {
	$db = getDB();
	
	if($db->querySingle("SELECT eid FROM node WHERE name='".SQLite3::escapeString($node)."'") == null) {
		$db->exec("INSERT INTO node(name,type,weight) VALUES('".SQLite3::escapeString($node)."',1,50);");
		return true;
	}

	return false;
}


/** retourne l'eid d'un mot.
* @param node : le mot dont on veut obtenir l'eid.
*/
function getNodeEid($node) {
	$db = getDB();

	return $db->querySingle("SELECT eid FROM node WHERE name='".SQLite3::escapeString($node)."';");
}

function wordExist($node) {
	$db = getDB();

	return getNodeEid($node) === null ? "0" : "1";
}

function getUserInfo($user, $key) {
	return getdb()->querySingle("SELECT value FROM user_info WHERE user = '".sqlite3::escapestring($user)."' AND key = '".SQLite3::escapeString($key)."';");
}

function setUserInfo($user, $key, $value) {
	getDB()->exec("INSERT OR REPLACE INTO user_info(user, key, value) values('".SQLite3::escapeString($user)
				  ."', '".SQLite3::escapeString($key)
				  ."', '".SQLite3::escapeString($value)."');");
}

function userPrefsDefaults() {
	return Array(
		"theme" => "green"
	);
}

function userPrefs($user) {
	$res = userPrefsDefaults();
	foreach ($res as $k => &$v) {
		$x = getUserInfo($user, $k);
		if ($x !== null) $v = $x;
	}
	echo json_encode($res);
}

function setUserPref($user, $key, $value) {
	if (array_key_exists($key, userPrefsDefaults())) {
		setUserInfo($user, $key, $value);
	}
}

?>