<?php
//include_once "../objects/db_table_object.php";
include_once '../objects/db_session.php';
include_once '../objects/db_user.php';
include_once '../objects/db_view_sessiondebrief_row.php';
include_once '../objects/stats_user_globals.php';
//include_once '../objects/stats_user_in_session_row.php';

class StatsObject extends DBTableObject
{
	// database connection and table name
    //protected $conn;
    protected $array_key = "stats";
	protected $elements = array();


	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	protected $excludeCalibrationSessionsFromStats = true;
	protected function getCalibrationSessionsConstraint ($sessionsTableIndex="SD") : string
	{
		return $this->excludeCalibrationSessionsFromStats == true ? " AND " . $sessionsTableIndex . ".ScenarioName NOT LIKE '%Calibration%'" : "";
	}
	
	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	protected $minimumSessionDuration = 10;
	protected function getSessionDurationConstraint ($sessionsTableIndex="SD", $allowZero=false) : string
	{
		return $this->minimumSessionDuration > 0 ? " AND (" . $sessionsTableIndex . ".timeToFinish > " . $this->minimumSessionDuration . ($allowZero ? " OR " . $sessionsTableIndex . ".timeToFinish = 0" : "") . ")" : "";
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	protected function getSessionsWithUserConstraint ($userId) : string
	{
		return $userId > 0 ? "SELECT DISTINCT UP.SessionId FROM " . PARTICIPATES_TABLE_NAME . " UP WHERE UP.userId=" . $userId : "";
	}
	
	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public function toArray () : array
	{
		return $this->elements;
	}


	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	// get users who took place in given session
	public function fixDurations ()
	{
		$query = "SELECT S.id AS SessionId FROM " . SESSIONS_TABLE_NAME . " S, " . TRIGGEREVENTS_TABLE_NAME . " TE WHERE S.timeToFinish=0 AND S.id=TE.sessionId";

		// prepare and execute query
		$stmt = $this->conn->prepare($query);
		$stmt->execute();

		$nb = 0;
		
		// get max time for trigger events in these sessions
		while ($row = $stmt->fetch(PDO::FETCH_ASSOC))
		{
			$subQuery = "SELECT COALESCE(MAX(TE.timeStamp),0) AS LastTriggerEvent FROM " . TRIGGEREVENTS_TABLE_NAME . " TE WHERE TE.sessionId=" . $row['SessionId'];
			
			// prepare and execute query
			$subStmt = $this->conn->prepare($subQuery);
			$subStmt->execute();

			while ($subRow = $subStmt->fetch(PDO::FETCH_ASSOC))	// should be only 1 row
			{
				if ($subRow['LastTriggerEvent'] != 0)	// should always be true
				{
					$subQuery = "UPDATE " . SESSIONS_TABLE_NAME . " SET timeToFinish=" . $subRow['LastTriggerEvent'] . " WHERE id=" . $row['SessionId'];

					// prepare and execute query
					$subStmt = $this->conn->prepare($subQuery);
					$subStmt->execute();

					$nb++;
				}
			}
		}
		
		return $nb;
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	// get users who took place in given session
	public function getRawUsers ($sessionId)
	{
		// Check if we are looking for a specific session
		$sessionConstraint = $sessionId > 0 ? " AND P.sessionId=" . $sessionId : "";
		
		$query = "SELECT DISTINCT U.* FROM " . USERS_TABLE_NAME . " U LEFT JOIN " . PARTICIPATES_TABLE_NAME . " P ON U.id = P.userId " .
		"LEFT JOIN " . SESSIONS_TABLE_NAME . " SD ON P.sessionId=SD.id " . 
		"WHERE U.id > 0" . $sessionConstraint . $this->getSessionDurationConstraint("SD");
		//$query = "SELECT DISTINCT U.* FROM " . USERS_TABLE_NAME . " U, " . PARTICIPATES_TABLE_NAME . " P WHERE U.id = P.userId AND U.id > 0" . $sessionConstraint;

		// prepare and execute query
		$stmt = $this->conn->prepare($query);
		$stmt->execute();
		
		return $stmt;
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	// get users who took place in given session
	public function getUsersInSession ($sessionId)
	{
		// Check if we are looking for a specific session
		$stmt = $this->getRawUsers($sessionId);
		
		while ($row = $stmt->fetch(PDO::FETCH_ASSOC))
		{
			//$sess = new GameSession($this->conn);
			//$sess->readRow($row);
			$user = User::withRow($this->conn, $row);
			array_push( $this->elements, $user->toArray() );
		}
	}


	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	// get sessions of given type that given user has taken part in
	// $limit and $offset are optional : when $limit <= 0 the full list is returned (legacy behavior)
	public function getRawSessions ($userId=-1, $typeId=-1, $limit=-1, $offset=0)
	{
		// Check if we are looking for a specific user and/or type of session
		$userConstraint = $userId > 0 ? " AND P.userId=" . $userId : "";
		$typeConstraint = $typeId >= 0 ? " AND SD.sessionType=" . $typeId : "";

		// Order results from newest to oldest session
		$orderConstraint = " ORDER BY SD.id DESC";

		// Optional pagination : SQL-level LIMIT/OFFSET when caller provided a positive limit
		$limitConstraint = "";
		if (intval($limit) > 0) {
			$limitConstraint = " LIMIT " . intval($limit) . " OFFSET " . max(0, intval($offset));
		}

		//$query = "SELECT DISTINCT S.* FROM " . SESSIONS_TABLE_NAME . " S LEFT JOIN " . PARTICIPATES_TABLE_NAME . " P ON S.id = P.sessionId WHERE 1 " .
		$query = "SELECT DISTINCT SD.* FROM " . SESSIONS_TABLE_NAME . " SD, " . PARTICIPATES_TABLE_NAME . " P WHERE SD.id = P.sessionId " .
		$this->getCalibrationSessionsConstraint("SD") . $this->getSessionDurationConstraint("SD") . $userConstraint . $typeConstraint . $orderConstraint . $limitConstraint;

		// prepare and execute query
		$stmt = $this->conn->prepare($query);
		$stmt->execute();

		return $stmt;
	}


	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	// get sessions that given user has taken part in
	public function getSessionsForUser ($userId, $typeId=-1, $limit=-1, $offset=0)
	{
		// Check if we are looking for a specific user and/or session
		$stmt = $this->getRawSessions($userId, $typeId, $limit, $offset);

		while ($row = $stmt->fetch(PDO::FETCH_ASSOC))
		{
			//$sess = new GameSession($this->conn);
			//$sess->readRow($row);
			$sess = GameSession::withRow($this->conn, $row);
			array_push( $this->elements, $sess->toArray() );
		}
	}


	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	// debrief mission : get stats for given session
	public function getRawStatsForSession ($sessionId, $userId, $fromUser=-1)
	{
		$sessionStats = array();

		// first create expected rows for results, so that every user will get stats (even if user didn't fire or received any shot)
		if ($sessionId > 0 && $userId > 0)
		{
			// we want stats for a specific user in a specific session
			// => we only need to create one default row, just in case user didn't fire or received any shot
			$defaultRow = new UserStatsInSessionRow();
			$defaultRow->createSessionAndUser($this->conn, $sessionId, $userId);
			array_push( $sessionStats, $defaultRow );
		}
		else if ($userId == -1)
		{
			// we want stats for all users in a specific session
			// => we need to create one default row for each user
			$stmt_usersInSession = $this->getRawUsers($sessionId);
			while ($row = $stmt_usersInSession->fetch(PDO::FETCH_ASSOC))
			{
				$defaultRow = new UserStatsInSessionRow();
				$defaultRow->createSessionAndUser($this->conn, $sessionId, $row['id']);
				array_push( $sessionStats, $defaultRow );
			}
		}
		else if ($sessionId == -1)
		{
			// we want stats for a specific user in all sessions he took place in
			// => we need to create one default row for each user
			$stmt_sessionsForUser = $this->getRawSessions($userId);
			while ($row = $stmt_sessionsForUser->fetch(PDO::FETCH_ASSOC))
			{
				$defaultRow = new UserStatsInSessionRow();
				$defaultRow->createSessionAndUser($this->conn, $row['id'], $userId);
				array_push( $sessionStats, $defaultRow );
			}
		}
		else
		{
			// both $sessionId and $userId are set to -1
			// => do nothing, this will (should) not happen
		}

			

		// Get fired shots
		$stmt_firedShots = $this->getShotsFiredByUser($sessionId, $userId, $fromUser);
		while ($row = $stmt_firedShots->fetch(PDO::FETCH_ASSOC))
		{
			// Only create stats row for shooters with id > 0
			if ((int)$row['UserId'] > 0)
			{
				$statsRow = $this->findStats( $sessionStats, (int)$row['UserId'], (int)$row['SessionId'] );
				if ($statsRow == null)
				{
					$statsRow = UserStatsInSessionRow::withRow($this->conn, $row);
					$statsRow->setFiredShots($row);
					array_push( $sessionStats, $statsRow );
				}
				else
					$statsRow->setFiredShots($row);
			}
		}
	
		// Get received shots
		$stmt_receivedShots = $this->getReceivedHitsForUser($sessionId, $userId, $fromUser);
		while ($row = $stmt_receivedShots->fetch(PDO::FETCH_ASSOC))
		{
			// Only create stats row for hit users with id > 0
			if ((int)$row['UserId'] > 0)
			{
				$statsRow = $this->findStats ( $sessionStats, (int)$row['UserId'], (int)$row['SessionId'] );
				if ($statsRow == null)
				{
					$statsRow = UserStatsInSessionRow::withRow($this->conn, $row);
					$statsRow->setReceivedHits($row);
					array_push( $sessionStats, $statsRow );
				}
				else
					$statsRow->setReceivedHits($row);
			}
		}

		// Get average precision and reaction time
		$stmt_averages = $this->getPrecisionAndReactionTimeForUser($sessionId, $userId, $fromUser);
		while ($row = $stmt_averages->fetch(PDO::FETCH_ASSOC))
		{
			// Only create stats row for hit users with id > 0
			if ((int)$row['UserId'] > 0)
			{
				$statsRow = $this->findStats ( $sessionStats, (int)$row['UserId'], (int)$row['SessionId'] );
				if ($statsRow == null)
				{
					$statsRow = UserStatsInSessionRow::withRow($this->conn, $row);
					$statsRow->setPrecisionAndReactionTime($row);
					array_push( $sessionStats, $statsRow );
				}
				else
					$statsRow->setPrecisionAndReactionTime($row);
			}
		}
		
		// Get total of targets killed in the session
		$stmt_targetKilled = $this->getTargetKilledInSession($sessionId);
		while ($row = $stmt_targetKilled->fetch(PDO::FETCH_ASSOC))
		{
			// Add totals stats to all rows of the same session
			foreach ($sessionStats as $statsRow)
			{
				if ($statsRow->sessionId == (int)$row['SessionId'])
					$statsRow->setTotalKills($row);
			}
		}
		
		return $sessionStats;
	}


	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	// debrief mission : get stats for given session
	public function getStatsForSession ($sessionId, $userId, $fromUser=-1)
	{
		$sessionStats = $this->getRawStatsForSession($sessionId, $userId, $fromUser);

		// Parse workingStats array to output results
		foreach ($sessionStats as $outputStats)
			array_push( $this->elements, $outputStats->toArray() );
	}


	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	// find a row of stats for given user and stats
	function findStats (array $statsArray, int $userId, int $sessionId)
	{
		foreach ($statsArray as $row)
		{
			if (call_user_func_array(array($row, 'isForUserInSession'), array($userId, $sessionId)) === true)
		  		return $row;
		}

		return null;
	}


	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	// get stats : main call for getting stats from Unreal
	public function get ($sessionId, $userId, $sessionType, $fromUserId=-1)
	{
		if ($sessionType == 0 || $sessionType == 1 || $sessionType == 7)	// Firerange, Challenge or Long Range
			$this->getResultsForSession ($sessionId, $userId, $fromUserId);
		else if ($sessionId > 0 || $userId > 0)
			$this->getStatsForSession ($sessionId, $userId, $fromUserId);
	}

	
	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	// user history : get user "all-time" totals
	public function getUserHistory ($userId, $sessionId, $quickMode)
	{
		$userGlobals = new UserGlobalStats();

		if ($quickMode == 1 && $userId > 0)
		{
			$userGlobals->totals->createSessionAndUser($this->conn, $sessionId, $userId);
			$userGlobals->totals->averagePrecision = $userGlobals->totals->user->avgPrecision;
			$userGlobals->totals->averageReactionTime = $userGlobals->totals->user->avgReaction;
			
			$userSubConstraint = $userId >= 0 ? " AND P.UserId = " . $userId : "";	// always true

			// Calculate date of first and last sessions
			$query = "SELECT P.userID AS UserId, MIN(SD.SessionDate) AS MinDate, MAX(SD.SessionDate) AS MaxDate, " . "
			COUNT(DISTINCT P.UserId, P.sessionId) AS NbSessions, SUM(SD.timeToFinish) AS TotalDuration " . "
			FROM " . PARTICIPATES_TABLE_NAME . " P, " . SESSIONS_TABLE_NAME . " SD WHERE SD.id=P.sessionId " . $this->getCalibrationSessionsConstraint("SD") . $this->getSessionDurationConstraint("SD") . $userSubConstraint . " GROUP BY P.UserId";
			
			// prepare and execute query
			$stmt = $this->conn->prepare($query);
			$stmt->execute();

			while ($row = $stmt->fetch(PDO::FETCH_ASSOC))	// should be only 1 row
			{
				$userGlobals->setSessionTotals($row);
				//$userGlobals->nbSessions = $row['NbSessions'];
				//$userGlobals->totalDuration = $row['TotalDuration'];
				//$userGlobals->firstSession = $row['MinDate'];
				//$userGlobals->lastSession = $row['MaxDate'];
			}

			// Calculate shots fired
			$userConstraint = $userId > 0 ? " AND SDTE.srcUserId=" . $userId : "";
			$query = "SELECT COALESCE(SDTE.srcUserId,-1) AS UserId, 
			COALESCE(X.NbShotsFired,0) AS NbShotsFired, 
			COALESCE(X.NbShotsFired,0) - COALESCE(SUM(CASE WHEN Y.ReactTypeId>=0 AND Y.ReactTypeId<6 AND Y.ReactPrecision>0 THEN Y.NbHits ELSE 0 END),0) AS NbMissedShots, 
			SUM(CASE WHEN Y.ReactTypeId=0 AND Y.ReactPrecision>0 THEN Y.NbHits ELSE 0 END) AS NbEnemyHits,
			SUM(CASE WHEN Y.ReactTypeId=1 AND Y.ReactPrecision>0 THEN Y.NbHits ELSE 0 END) AS NbCivilHits,
			SUM(CASE WHEN Y.ReactTypeId=2 AND Y.ReactPrecision>0 THEN Y.NbHits ELSE 0 END) AS NbPoliceHits,
			SUM(CASE WHEN (Y.ReactTypeId=4 OR Y.ReactTypeId=5) AND Y.ReactPrecision>0 THEN Y.NbHits ELSE 0 END) AS NbObjectHits,
			SUM(CASE WHEN Y.ReactTypeId=6 AND Y.ReactPrecision>0 THEN Y.NbHits ELSE 0 END) AS NbDeadBodyHits
			FROM " . SESSIONS_TABLE_NAME . " SD LEFT JOIN " . TRIGGEREVENTS_TABLE_NAME . " SDTE ON (SDTE.sessionId=SD.id) 
			LEFT JOIN " . REACTEVENTS_TABLE_NAME . " SDRE ON (SDTE.sessionId=SDRE.srcEventSessionId AND SDTE.indexCount=SDRE.srcEventIndex) 
			LEFT JOIN " . "
			(SELECT TE.srcUserId AS ShooterId, COUNT(DISTINCT TE.sessionId, TE.indexCount) AS NbShotsFired FROM " . TRIGGEREVENTS_TABLE_NAME . " TE GROUP BY TE.srcUserId) AS X 
			ON X.ShooterId=SDTE.srcUserId
			LEFT JOIN " . "
			(SELECT TE.srcUserId AS ShooterId, RE.ReactType AS ReactTypeId, COUNT(DISTINCT TE.sessionId, TE.indexCount, RE.hitTargetName) AS NbHits, RE.id AS ReactId, RE.hitPrecision AS ReactPrecision
			FROM " . TRIGGEREVENTS_TABLE_NAME . " TE, " . REACTEVENTS_TABLE_NAME . " RE 
			WHERE TE.sessionId = RE.srcEventSessionId AND TE.indexCount = RE.srcEventIndex AND TE.indexCount!=-1 AND RE.ReactType!=-1 GROUP BY ShooterId, ReactTypeId, ReactID) AS Y 
			ON (Y.ShooterId=SDTE.srcUserId AND Y.ReactId=SDRE.id)
			LEFT JOIN " . "
			(SELECT TE1.srcUserId AS ShooterId, COALESCE(COUNT(DISTINCT TE1.sessionId, TE1.indexCount),0) AS NbMissedShots FROM " . TRIGGEREVENTS_TABLE_NAME . " TE1, " . REACTEVENTS_TABLE_NAME . " RE1 
			WHERE TE1.sessionId=RE1.srcEventSessionId AND TE1.indexCount NOT IN (SELECT RE2.srcEventIndex FROM " . REACTEVENTS_TABLE_NAME . " RE2 WHERE RE2.srcEventSessionId=RE1.srcEventSessionId)) AS Z
			ON (Z.ShooterId=SDTE.srcUserId) " . "
			WHERE 1 " . $this->getCalibrationSessionsConstraint("SD") . $this->getSessionDurationConstraint("SD") . $userConstraint . " GROUP BY SDTE.srcUserId";
			
			// prepare and execute query
			$stmt = $this->conn->prepare($query);
			$stmt->execute();

			while ($row = $stmt->fetch(PDO::FETCH_ASSOC))
			{
				$userGlobals->totals->setFiredShots($row);
				//$userGlobals->totals->nbFiredShotsByUser = $row['NbShotsFired'];
				//$userGlobals->totals->nbMissedShotsByUser = $row['NbMissedShots'];
				//$userGlobals->totals->nbEnemyHitsByUser = $row['NbEnemyHits'];
				//$userGlobals->totals->nbCivilHitsByUser = $row['NbCivilHits'];
				//$userGlobals->totals->nbPoliceHitsByUser = $row['NbPoliceHits'];
				//$userGlobals->totals->nbObjectHitsByUser = $row['NbObjectHits'];
				//$userGlobals->totals->nbDeadBodyHitsByUser = $row['NbDeadBodyHits'];
			}


			// Calculate received hits
			$userConstraint = $userId > 0 ? " AND X.TargetUserId=" . $userId : "";
			$query = "SELECT COALESCE(X.TargetUserId, -1) AS UserId, 
			SUM(CASE WHEN X.ShooterRoleId=3 THEN X.NbHits ELSE 0 END) AS NbEnemyShotsIA, 
			SUM(CASE WHEN X.ShooterRoleId=1 THEN X.NbHits ELSE 0 END) AS NbEnemyShotsUser, 
			SUM(CASE WHEN X.ShooterRoleId=0 THEN X.NbHits ELSE 0 END) AS NbPoliceHits 
			FROM (SELECT TE.SessionId AS SessionId, RE.hitUserId AS TargetUserId, 
			(SELECT IFNULL((SELECT P.role FROM " . PARTICIPATES_TABLE_NAME . " P WHERE P.sessionId=TE.sessionId AND P.userId=TE.srcUserId),3)) AS ShooterRoleId, 
			COUNT(DISTINCT TE.indexCount, RE.hitTargetName) AS NbHits 
			FROM " . TRIGGEREVENTS_TABLE_NAME . " TE, " . REACTEVENTS_TABLE_NAME . " RE, " . SESSIONS_TABLE_NAME . " SD " .
			"WHERE SD.id=TE.sessionId AND TE.sessionId = RE.srcEventSessionId AND TE.indexCount = RE.srcEventIndex AND TE.indexCount!=-1 AND RE.ReactType!=-1 " . 
			$this->getCalibrationSessionsConstraint("SD") . $this->getSessionDurationConstraint("SD") . 
			"GROUP BY SessionId, TargetUserId, ShooterRoleId) AS X 
			WHERE 1 " . $userConstraint . " GROUP BY X.TargetUserId";
			
			// prepare and execute query
			$stmt = $this->conn->prepare($query);
			$stmt->execute();

			while ($row = $stmt->fetch(PDO::FETCH_ASSOC))
			{
				$userGlobals->totals->setReceivedHits($row);
			}

			// Calculate totals hits by type
			$sessionConstraint = $sessionId > 0 ? " AND SDTE.SessionId = " . $sessionId : "";
			$userConstraint = $userId > 0 ? " AND SDTE.SessionId IN (" . $this->getSessionsWithUserConstraint($userId) . ")" : "";
			
			$query = "SELECT SUM(CASE WHEN X.ReactTypeId=0 THEN X.NbHits ELSE 0 END) AS NbEnemyKilled, 
			SUM(CASE WHEN X.ReactTypeId=1 THEN X.NbHits ELSE 0 END) AS NbCivilKilled, 
			SUM(CASE WHEN X.ReactTypeId=2 THEN X.NbHits ELSE 0 END) AS NbPoliceKilled 
			FROM " . SESSIONS_TABLE_NAME . " SD LEFT JOIN " . TRIGGEREVENTS_TABLE_NAME . " SDTE ON (SD.id = SDTE.sessionId) 
			LEFT JOIN
			(SELECT TE.SessionId AS SessionId, RE.hitUserId AS TargetUserId, TE.srcUserId AS ShooterRoleId, COUNT(DISTINCT TE.indexCount, RE.hitTargetName) AS NbHits, RE.srcEventIndex AS TriggerIndex, RE.ReactType AS ReactTypeId 
			FROM " . TRIGGEREVENTS_TABLE_NAME . " TE, " . REACTEVENTS_TABLE_NAME . " RE WHERE TE.sessionId = RE.srcEventSessionId AND TE.indexCount = RE.srcEventIndex AND TE.indexCount!=-1 AND RE.ReactType!=-1 AND RE.TargetKilled=1 
			GROUP BY SessionId, TargetUserId, ShooterRoleId, ReactTypeId) AS X 
			ON (X.SessionId=SDTE.SessionId AND X.TriggerIndex=SDTE.indexCount)
			WHERE 1 " . $this->getCalibrationSessionsConstraint("SD") . $this->getSessionDurationConstraint("SD") . $userConstraint;
			
			// prepare and execute query
			$stmt = $this->conn->prepare($query);
			$stmt->execute();

			while ($row = $stmt->fetch(PDO::FETCH_ASSOC))
			{
				$userGlobals->totals->setTotalKills($row);
			}


			$query = "SELECT X.*, " .
			"SUM(X.HitPrecision)/COALESCE(COUNT(DISTINCT X.SessionId, X.ShotIndex),1) AS AvgPrecision, " .
			"SUM(X.ReactionTime)/COALESCE(SUM(CASE WHEN X.ReactionTime > 0 THEN CEILING(X.HitPrecision) ELSE 0 END),1) AS AvgReactTime " .
			"FROM " .
			"(SELECT TE.sessionId AS SessionId, S.sessionType AS SessionTypeId, ST.displayName AS SessionType, S.sessionName AS SessionName, S.sessionDate AS SessionDate, S.mapName AS MapName, S.scenarioName AS ScenarioName, " .
			"S.success AS SessionSuccessful, S.timeToFinish AS SessionDuration, 0 AS TriggerTypeId, 'Fire' AS TriggerType, TE.srcUserId AS ShooterId, '' AS ShooterName, -1 AS ShooterRoleId, '' AS ShooterRole, " .
			"TE.indexCount AS ShotIndex, RE.id AS ReactId, -1 AS ReactModeId, '' AS ReactMode, -1 AS ReactTypeId, '' AS ReactType, RE.hitUserId AS TargetUserId, " .
			"'' AS TargetUserName, -1 AS TargetRoleId, '' AS TargetRole, RE.hitTargetName AS TargetName, RE.hitBoneName AS TargetBoneName, 0 AS TargetKilled, 0 AS HitLocationX, 0 AS HitLocationY, " .
			"'' AS HitLocationTag, RE.hitPrecision AS HitPrecision, 0 AS HitTargetDistance, RE.reactTime as ReactionTime, 0 AS TimeStamp, 0 AS NbHit, 0 AS NbKilled " .
			"FROM " . SESSIONS_TABLE_NAME . " S " .
			"LEFT JOIN " . TRIGGEREVENTS_TABLE_NAME . " TE ON (S.id=TE.sessionId) " .
			"LEFT JOIN " . REACTEVENTS_TABLE_NAME . " RE ON (TE.sessionId=RE.srcEventSessionId AND TE.indexCount=RE.srcEventIndex) " . 
			"LEFT JOIN " . SESSIONTYPES_TABLE_NAME . " ST ON (S.sessionType=ST.id) " .
			"WHERE 1 " . $this->getSessionDurationConstraint("S") . $this->getCalibrationSessionsConstraint("S") .
			" GROUP BY ShooterId, SessionId, TE.indexCount) AS X " .
			"WHERE X.ShooterId = " . $userId . " AND X.SessionId IN (" . $this->getSessionsWithUserConstraint($userId) . ") GROUP BY X.ShooterId, X.SessionId ORDER BY X.SessionId ASC";
			
			// prepare and execute query
			$stmt = $this->conn->prepare($query);
			$stmt->execute();

			while ($row = $stmt->fetch(PDO::FETCH_ASSOC))
			{
				$row['HitPrecision'] = $row['AvgPrecision'];
				$row['ReactionTime'] = $row['AvgReactTime'];

				$stats = SessionDebriefRow::withRow($row);
				array_push( $userGlobals->sessionDebriefRows, $stats->toArray() );
			}
		}
		else
		{
			// Lazy rebuild du cache materialise pour cette session avant le SELECT FROM sessiondebriefs
			if ($sessionId > 0 && function_exists('ensure_session_debriefs_cached_db')) {
				ensure_session_debriefs_cached_db($this->conn, $sessionId);
			}

			// create SQL constraints for query
			$sessionConstraint = $sessionId > 0 ? " AND SD.SessionId = " . $sessionId : "";
			$userConstraint = $userId >= 0 ? " AND SD.ShooterId = " . $userId : "";
			$userSubConstraint = $userId >= 0 ? " AND P.UserId = " . $userId : "";

			// get raw results from query
			$sessionStats = $this->getRawStatsForSession ($sessionId, $userId, -1);

			$newNbFiredShotsForReactionTime = 0;
			$nbSessions = 0;
			foreach ($sessionStats as $row)
			{
				if ( ($userId == -1 || $row->userId == $userId) && ($sessionId == -1 || $row->sessionId == $sessionId) )
				{
					// Increase number of sessions for this user
					$nbSessions++;

					// Update precision average
					$newPrecision = $userGlobals->totals->averagePrecision*$userGlobals->totals->nbFiredShotsByUser + $row->averagePrecision*$row->nbFiredShotsByUser;
					$userGlobals->totals->averagePrecision = $newPrecision/max($userGlobals->totals->nbFiredShotsByUser + $row->nbFiredShotsByUser, 1.0);

					// Update reactionTime average
					if ($row->averageReactionTime > 0)
					{
						$newReactionTime = $userGlobals->totals->averageReactionTime*$newNbFiredShotsForReactionTime + $row->averageReactionTime*$row->nbFiredShotsByUser;
						$newNbFiredShotsForReactionTime += $row->nbFiredShotsByUser;
						$userGlobals->totals->averageReactionTime = $newReactionTime/max($newNbFiredShotsForReactionTime, 1.0);
					}

					// Update other fields
					$userGlobals->totals->nbFiredShotsByUser = $userGlobals->totals->nbFiredShotsByUser + $row->nbFiredShotsByUser;
					$userGlobals->totals->nbEnemyHitsByUser += $row->nbEnemyHitsByUser;
					$userGlobals->totals->nbCivilHitsByUser += $row->nbCivilHitsByUser;
					$userGlobals->totals->nbPoliceHitsByUser += $row->nbPoliceHitsByUser;
					$userGlobals->totals->nbObjectHitsByUser += $row->nbObjectHitsByUser;
					$userGlobals->totals->nbMissedShotsByUser += $row->nbMissedShotsByUser;
					$userGlobals->totals->nbDeadBodyHitsByUser += $row->nbDeadBodyHitsByUser;
					$userGlobals->totals->nbReceivedHitsFromEnemyIA += $row->nbReceivedHitsFromEnemyIA;
					$userGlobals->totals->nbReceivedHitsFromEnemyUser += $row->nbReceivedHitsFromEnemyUser;
					$userGlobals->totals->nbReceivedHitsFromPoliceUser +=$row->nbReceivedHitsFromPoliceUser;
					$userGlobals->totals->totalEnemyKilled += $row->totalEnemyKilled;
					$userGlobals->totals->totalCivilKilled += $row->totalCivilKilled;
					$userGlobals->totals->totalPoliceKilled += $row->totalPoliceKilled;

					// Update duration
					$userGlobals->totalDuration += $row->session->timeToFinish;

					// Update user and session ids
					$userGlobals->totals->userId = $row->userId;
					$userGlobals->totals->sessionId = $row->sessionId;
				}
			}

			// Get number of sessions user took part in
			$userGlobals->nbSessions = $nbSessions;

			// Calculate averages for precision and reaction time
			$query = "SELECT SD.*, SUM(SD.HitPrecision) AS TotalPrecision, COUNT(DISTINCT SD.ShotIndex, SD.TargetName) AS TotalShots" .
			", SUM(SD.ReactionTime) AS TotalReactionTime,  SUM(CEILING(SD.HitPrecision)) AS TotalHits, X.MinDate AS MinDate, X.MaxDate AS MaxDate" .
			" FROM " . SESSIONDEBRIEFS_VIEW_NAME . " SD, " .
			" (SELECT P.userID AS UserId, MIN(S.SessionDate) AS MinDate, MAX(S.SessionDate) AS MaxDate" . 
			//" FROM " . PARTICIPATES_TABLE_NAME . " P INNER JOIN " . SESSIONS_TABLE_NAME . " S ON S.id=P.sessionId WHERE 1 " . $userSubConstraint . " GROUP BY P.userId) AS X" .
			" FROM " . PARTICIPATES_TABLE_NAME . " P, " . SESSIONS_TABLE_NAME . " S WHERE S.id=P.sessionId " . $userSubConstraint . " GROUP BY P.userId) AS X" .
			" WHERE 1 " . $sessionConstraint . $userConstraint . " GROUP BY SD.SessionId, SD.ShooterId";
			
			// prepare and execute query
			$stmt = $this->conn->prepare($query);
			$stmt->execute();

			while ($row = $stmt->fetch(PDO::FETCH_ASSOC))
			{
				// read MIN and MAX dates
				$userGlobals->firstSession = $row['MinDate'];
				$userGlobals->lastSession = $row['MaxDate'];

				// replace HitPrecision, ReactionTime and NbHit fields so that we don't have to create a new type for returning the results
				$row['HitPrecision'] = (float)(($row['TotalPrecision'] ?? 0.0) / max(($row['TotalShots'] ?? 1.0), 1.0) );
				$row['ReactionTime'] = (float)(($row['TotalReactionTime'] ?? 0.0) / max(($row['TotalHits'] ?? 1.0), 1.0) );
				$row['NbHit'] = $row['TotalHits'] ?? 0;

				$stats = SessionDebriefRow::withRow($row);
				array_push( $userGlobals->sessionDebriefRows, $stats->toArray() );
				//array_push( $this->elements, $stats->toArray() );	// old
			}

			if ($userGlobals->nbSessions == 1)
			{
				$userGlobals->firstSession = $sessionStats[0]->session->sessionDate;
				$userGlobals->lastSession = $userGlobals->firstSession;
			}
		}

		array_push( $this->elements, $userGlobals->toArray() );	// new
		//return $userGlobals->toArray();
	}


	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	// debrief mission : get totals for given session and/or user
	public function getReceivedHitsForUser ($sessionId, $userId, $fromUser=-1)
	{
		// create SQL constraints for query
		$sessionConstraint = $sessionId > 0 ? " AND SDRE.srcEventSessionId = " . $sessionId : "";
		$userConstraint = $userId > 0 ? " AND SDRE.hitUserId=" . $userId : "";

		$query = "SELECT SDRE.srcEventSessionId AS SessionId, COALESCE(SDRE.hitUserId, -1) AS UserId, 
		SUM(CASE WHEN X.ShooterRoleId=3 THEN X.NbHits ELSE 0 END) AS NbEnemyShotsIA, 
		SUM(CASE WHEN X.ShooterRoleId=1 THEN X.NbHits ELSE 0 END) AS NbEnemyShotsUser, 
		SUM(CASE WHEN X.ShooterRoleId=0 THEN X.NbHits ELSE 0 END) AS NbPoliceHits 
		FROM " . REACTEVENTS_TABLE_NAME . " SDRE LEFT JOIN
		(SELECT TE.SessionId AS SessionId, RE.hitUserId AS TargetUserId, TE.srcUserId AS ShooterRoleId, COUNT(DISTINCT TE.indexCount, RE.hitTargetName) AS NbHits, RE.id AS ReactId 
		FROM " . TRIGGEREVENTS_TABLE_NAME . " TE, " . REACTEVENTS_TABLE_NAME . " RE WHERE TE.sessionId = RE.srcEventSessionId AND TE.indexCount = RE.srcEventIndex AND TE.indexCount!=-1 AND RE.ReactType!=-1 GROUP BY SessionId, TargetUserId, ShooterRoleId) AS X 
		ON (X.SessionId=SDRE.srcEventSessionId AND X.TargetUserId=SDRE.hitUserId AND X.ReactId=SDRE.id) " .
		" LEFT JOIN " . SESSIONS_TABLE_NAME . " SD ON (SD.id=SDRE.srcEventSessionId) " . 
		"WHERE 1 " . $sessionConstraint . $userConstraint . $this->getSessionDurationConstraint("SD") . " GROUP BY SDRE.srcEventSessionId, SDRE.hitUserId";

		// prepare and execute query
		$stmt = $this->conn->prepare($query);
		$stmt->execute();

		return $stmt;
	}


	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	// debrief mission : get totals for given session and/or user
	public function getShotsFiredByUser ($sessionId, $userId, $fromUser=-1)
	{
		// create SQL constraints for query
		$sessionConstraint = $sessionId > 0 ? " AND SDTE.SessionId = " . $sessionId : "";
		$userConstraint = $userId > 0 ? " AND SDTE.srcUserId=" . $userId : "";
		
		$query = "SELECT SDTE.SessionId AS SessionId, SDTE.srcUserId AS UserId, 
		COALESCE(X.NbShotsFired,0) AS NbShotsFired, 
		COALESCE(X.NbShotsFired,0) - COALESCE(SUM(CASE WHEN Y.ReactTypeId>=0 AND Y.ReactTypeId<6 AND Y.ReactPrecision>0 THEN Y.NbHits ELSE 0 END),0) AS NbMissedShots,
		SUM(CASE WHEN Y.ReactTypeId=0 AND Y.ReactPrecision>0 THEN Y.NbHits ELSE 0 END) AS NbEnemyHits,
		SUM(CASE WHEN Y.ReactTypeId=1 AND Y.ReactPrecision>0 THEN Y.NbHits ELSE 0 END) AS NbCivilHits,
		SUM(CASE WHEN Y.ReactTypeId=2 AND Y.ReactPrecision>0 THEN Y.NbHits ELSE 0 END) AS NbPoliceHits,
		SUM(CASE WHEN (Y.ReactTypeId=4 OR Y.ReactTypeId=5) AND Y.ReactPrecision>0 THEN Y.NbHits ELSE 0 END) AS NbObjectHits,
		SUM(CASE WHEN Y.ReactTypeId=6 AND Y.ReactPrecision>0 THEN Y.NbHits ELSE 0 END) AS NbDeadBodyHits
		FROM " . SESSIONS_TABLE_NAME . " SD LEFT JOIN " . TRIGGEREVENTS_TABLE_NAME . " SDTE ON (SD.id = SDTE.sessionId) 
		LEFT JOIN " . REACTEVENTS_TABLE_NAME . " SDRE ON (SDTE.sessionId=SDRE.srcEventSessionId AND SDTE.indexCount=SDRE.srcEventIndex) 
		LEFT JOIN 
		(SELECT TE.sessionId AS SessionId, TE.srcUserId AS ShooterId, COUNT(DISTINCT TE.indexCount) AS NbShotsFired 
		FROM " . TRIGGEREVENTS_TABLE_NAME . " TE GROUP BY TE.sessionId, TE.srcUserId) AS X 
		ON (X.SessionId=SDTE.SessionId AND X.ShooterId=SDTE.srcUserId) 
		LEFT JOIN 
		(SELECT TE.sessionId AS SessionId, TE.srcUserId AS ShooterId, RE.ReactType AS ReactTypeId, COUNT(DISTINCT TE.indexCount, RE.hitTargetName) AS NbHits, RE.id AS ReactId, RE.hitPrecision AS ReactPrecision
		FROM " . TRIGGEREVENTS_TABLE_NAME . " TE, " . REACTEVENTS_TABLE_NAME . " RE 
		WHERE TE.sessionId = RE.srcEventSessionId AND TE.indexCount = RE.srcEventIndex AND TE.indexCount!=-1 AND RE.ReactType!=-1 GROUP BY SessionId, ShooterId, ReactTypeId, ReactID) AS Y 
		ON (Y.SessionId=SDTE.SessionId AND Y.ShooterId=SDTE.srcUserId AND Y.ReactId=SDRE.id)
		LEFT JOIN 
		(SELECT TE1.sessionId AS SessionId, TE1.srcUserId AS ShooterId, COALESCE(COUNT(DISTINCT TE1.sessionId, TE1.indexCount),0) AS NbMissedShots 
		FROM " . TRIGGEREVENTS_TABLE_NAME . " TE1, " . REACTEVENTS_TABLE_NAME . " RE1 
		WHERE TE1.sessionId=RE1.srcEventSessionId AND TE1.indexCount NOT IN (SELECT RE2.srcEventIndex FROM " . REACTEVENTS_TABLE_NAME . " RE2 WHERE RE2.srcEventSessionId=RE1.srcEventSessionId)) AS Z
		ON (Z.SessionId=SDTE.SessionId AND Z.ShooterId=SDTE.srcUserId)
		WHERE 1 " . $this->getCalibrationSessionsConstraint("SD") . $this->getSessionDurationConstraint("SD") . $sessionConstraint . $userConstraint . " GROUP BY SDTE.SessionId, SDTE.srcUserId";

		// prepare and execute query
		$stmt = $this->conn->prepare($query);
		$stmt->execute();
		
		return $stmt;
	}


	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	// debrief mission : get totals for given session and/or user
	public function getTargetKilledInSession ($sessionId)
	{
		// create SQL constraints for query
		$sessionConstraint = $sessionId > 0 ? " AND SDTE.SessionId = " . $sessionId : "";
		
		$query = "SELECT SDTE.SessionId AS SessionId, COALESCE(SDTE.srcUserId,-1) AS UserId, 
		SUM(CASE WHEN X.ReactTypeId=0 THEN X.NbHits ELSE 0 END) AS NbEnemyKilled, 
		SUM(CASE WHEN X.ReactTypeId=1 THEN X.NbHits ELSE 0 END) AS NbCivilKilled, 
		SUM(CASE WHEN X.ReactTypeId=2 THEN X.NbHits ELSE 0 END) AS NbPoliceKilled 
		FROM " . SESSIONS_TABLE_NAME . " SD LEFT JOIN " . TRIGGEREVENTS_TABLE_NAME . " SDTE ON (SD.id = SDTE.sessionId) 
		LEFT JOIN " . REACTEVENTS_TABLE_NAME . " SDRE ON (SDTE.sessionId=SDRE.srcEventSessionId AND SDTE.indexCount=SDRE.srcEventIndex) 
		LEFT JOIN
		(SELECT TE.SessionId AS SessionId, RE.hitUserId AS TargetUserId, TE.srcUserId AS ShooterRoleId, COUNT(DISTINCT TE.indexCount, RE.hitTargetName) AS NbHits, RE.id AS ReactId, RE.ReactType AS ReactTypeId 
		FROM " . TRIGGEREVENTS_TABLE_NAME . " TE, " . REACTEVENTS_TABLE_NAME . " RE WHERE TE.sessionId = RE.srcEventSessionId AND TE.indexCount = RE.srcEventIndex AND TE.indexCount!=-1 AND RE.ReactType!=-1 AND RE.TargetKilled=1 
		GROUP BY SessionId, TargetUserId, ShooterRoleId, ReactTypeId) AS X 
		ON (X.SessionId=SDTE.SessionId AND X.TargetUserId=SDRE.hitUserId AND X.ReactId=SDRE.id)
		WHERE 1 " . $this->getCalibrationSessionsConstraint("SD") . $this->getSessionDurationConstraint("SD") . $sessionConstraint . " GROUP BY SDTE.SessionId";
		
		// prepare and execute query
		$stmt = $this->conn->prepare($query);
		$stmt->execute();

		return $stmt;
	}


	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	// debrief mission : get averages for given session and/or user
	public function getPrecisionAndReactionTimeForUser ($sessionId, $userId, $fromUser=-1)
	{
		// create SQL constraints for query
		$sessionConstraint = $sessionId > 0 ? " AND SDTE.SessionId = " . $sessionId : "";
		
		$query = "SELECT SDTE.SessionId AS SessionId, SDTE.srcUserId AS UserId, AVG(X.AvgHitPrec) AS AveragePrecision, 
		AVG(CASE WHEN X.HitPrecision > 0 AND X.ReactId > 0 THEN X.AvgReactTime END) AS AverageReactionTime
		FROM " . SESSIONS_TABLE_NAME . " SD LEFT JOIN " . TRIGGEREVENTS_TABLE_NAME . " SDTE ON (SD.id = SDTE.sessionId) 
		LEFT JOIN " . REACTEVENTS_TABLE_NAME . " SDRE ON SDTE.sessionId=SDRE.srcEventSessionId AND SDTE.indexCount=SDRE.srcEventIndex 
		LEFT JOIN
		(SELECT DISTINCT TE.SessionId AS SessionId, TE.srcUserId AS ShooterId, TE.indexCount, RE.hitTargetName, COALESCE(AVG(RE.hitPrecision),0) AS AvgHitPrec, COALESCE(AVG(RE.reactTime),0) AS AvgReactTime, COALESCE(RE.id,-1) AS ReactId, COALESCE(RE.hitPrecision,0) AS HitPrecision 
		FROM " . TRIGGEREVENTS_TABLE_NAME . " TE LEFT JOIN " . REACTEVENTS_TABLE_NAME . " RE ON TE.indexCount=RE.srcEventIndex AND TE.sessionId=RE.srcEventSessionId GROUP BY TE.SessionId, TE.srcUserId, TE.indexCount, RE.id, RE.hitTargetName) AS X
		ON X.SessionId=SDTE.SessionId AND X.ShooterId=SDTE.srcUserId
		WHERE 1 " . $this->getCalibrationSessionsConstraint("SD") . $this->getSessionDurationConstraint("SD") . $sessionConstraint . " GROUP BY SDTE.SessionId, SDTE.srcUserId";

		// prepare and execute query
		$stmt = $this->conn->prepare($query);
		$stmt->execute();
		
		return $stmt;
	}
	

	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	// debrief mission : get given users results in given session
	public function getResultsForSession ($sessionId, $userId, $fromUserId=-1)
	{
		// Lazy rebuild du cache materialise (table session_debriefs_cache)
		// si la session a ete invalidee par un evenement et pas encore reconstruite
		if ($sessionId > 0 && function_exists('ensure_session_debriefs_cached_db')) {
			ensure_session_debriefs_cached_db($this->conn, $sessionId);
		}

		$userConstraint = $userId > 0 ? " AND SD.ShooterId=" . $userId : "";
		$sessionConstraint = $sessionId > 0 ? " AND SD.SessionId=" . $sessionId : "";

		$query = "SELECT SD.* FROM " . SESSIONDEBRIEFS_VIEW_NAME . " SD WHERE 1 " . $this->getCalibrationSessionsConstraint() . $userConstraint . $sessionConstraint . " GROUP BY SD.SessionId, SD.ShooterId, SD.ShotIndex";
		//$query = "SELECT SD.* FROM (" . SESSIONDEBRIEFS_VIEW_QUERY . ") AS SD WHERE 1 " . $userConstraint . $sessionConstraint . " GROUP BY SD.SessionId, SD.ShooterId, SD.ShotIndex";

		// prepare and execute query
		$stmt = $this->conn->prepare($query);
		$stmt->execute();
		
		while ($row = $stmt->fetch(PDO::FETCH_ASSOC))
		{
			$shot = SessionDebriefRow::withRow($row);
			array_push( $this->elements, $shot->toArray() );
		}
	}



	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	// debrief mission : get stats for given session and/or user
	/*public function getSessionDebrief ($sessionId, $userId, $fromUser=-1)
	{
		// prepare and execute query
		$stmt = $this->getDebriefRows($sessionId, $userId);
		
		while ($row = $stmt->fetch(PDO::FETCH_ASSOC))
		{
			// create a SessionDebriefRow with this data
			$stats = SessionDebriefRow::withRow($row);
			array_push( $this->elements, $stats->toArray() );
		}
	}

	
	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public function getDebriefRows ($sessionId, $userId)
	{
		// create SQL constraints for query
		$sessionConstraint = $sessionId > 0 ? " AND SD.SessionId = " . $sessionId : "";
		$userConstraint = $userId >= 0 ? " AND (SD.ShooterId = " . $userId . " OR SD.TargetUserId = " . $userId . ")" : "";

		//$query = "SELECT SD.* FROM " . SESSIONDEBRIEFS_VIEW_NAME . " SD WHERE 1 " . $sessionConstraint . $userConstraint;
		$query = "SELECT SD.* FROM (" . SESSIONDEBRIEFS_VIEW_QUERY . ") AS SD WHERE 1 " . $sessionConstraint . $userConstraint;

		// prepare and execute query
		$stmt = $this->conn->prepare($query);
		$stmt->execute();

		return $stmt;
	}*/
}
?>