<?php

/**
 * Model for trophies.
 *
 * @package XenForo_Trophy
 */
class XenForo_Model_Trophy extends XenForo_Model
{
	/**
	 * Gets the named trophy.
	 *
	 * @param integer $id
	 *
	 * @return array|false
	 */
	public function getTrophyById($id)
	{
		return $this->_getDb()->fetchRow('
			SELECT *
			FROM xf_trophy
			WHERE trophy_id = ?
		', $id);
	}

	/**
	 * Gets all trophies, ordered by their points (ascending).
	 *
	 * @return array Format: [trophy id] => info
	 */
	public function getAllTrophies()
	{
		return $this->fetchAllKeyed('
			SELECT *
			FROM xf_trophy
			ORDER BY trophy_points
		', 'trophy_id');
	}

	/**
	 * Gets all trophies that the specified user has earned. Ordered by award date descending.
	 *
	 * @param integer $userId
	 *
	 * @return array Format: [trophy id] => trophy info plus award_date
	 */
	public function getTrophiesForUserId($userId)
	{
		return $this->fetchAllKeyed('
			SELECT trophy.*,
				user_trophy.award_date
			FROM xf_user_trophy AS user_trophy
			INNER JOIN xf_trophy AS trophy ON (trophy.trophy_id = user_trophy.trophy_id)
			WHERE user_trophy.user_id = ?
			ORDER BY user_trophy.award_date DESC
		', 'trophy_id', $userId);
	}

	/**
	 * Counts the number of trophies that have been awarded to the specified user.
	 *
	 * @param integer $userId
	 *
	 * @return integer
	 */
	public function countTrophiesForUserId($userId)
	{
		return $this->_getDb()->fetchOne('
			SELECT COUNT(*)
			FROM xf_user_trophy AS user_trophy
			INNER JOIN xf_trophy AS trophy ON (trophy.trophy_id = user_trophy.trophy_id)
			WHERE user_trophy.user_id = ?
		', $userId);
	}

	/**
	 * Prepares a trophy for display.
	 *
	 * @param array $trophy
	 *
	 * @return array
	 */
	public function prepareTrophy(array $trophy)
	{
		$trophy['title'] = new XenForo_Phrase($this->getTrophyTitlePhraseName($trophy['trophy_id']));
		$trophy['description'] = new XenForo_Phrase($this->getTrophyDescriptionPhraseName($trophy['trophy_id']));

		return $trophy;
	}

	/**
	 * Prepares a list of trophies for display.
	 *
	 * @param array $trophies
	 *
	 * @return array
	 */
	public function prepareTrophies(array $trophies)
	{
		foreach ($trophies AS &$trophy)
		{
			$trophy = $this->prepareTrophy($trophy);
		}

		return $trophies;
	}

	/**
	 * Gets information about the default trophy for use when adding
	 * a new trophy. Includes prepared data.
	 *
	 * @return array
	 */
	public function getDefaultTrophy()
	{
		return array(
			'trophy_id' => 0,
			'trophy_points' => 10,

			'user_criteria' => '',
			'userCriteriaList' => array(),
			'title' => '',
			'description' => ''
		);
	}

	/**
	 * Gets the name of a trophy's title phrase.
	 *
	 * @param integer $trophyId
	 *
	 * @return string
	 */
	public function getTrophyTitlePhraseName($trophyId)
	{
		return 'trophy_' . $trophyId . '_title';
	}

	/**
	 * Gets a trophy's master title phrase text.
	 *
	 * @param integer $trophyId
	 *
	 * @return string
	 */
	public function getTrophyMasterTitlePhraseValue($trophyId)
	{
		$phraseName = $this->getTrophyTitlePhraseName($trophyId);
		return $this->_getPhraseModel()->getMasterPhraseValue($phraseName);
	}

	/**
	 * Gets the name of a trophy's description phrase.
	 *
	 * @param integer $trophyId
	 *
	 * @return string
	 */
	public function getTrophyDescriptionPhraseName($trophyId)
	{
		return 'trophy_' . $trophyId . '_description';
	}

	/**
	 * Gets a trophy's master description phrase text.
	 *
	 * @param integer $trophyId
	 *
	 * @return string
	 */
	public function getTrophyMasterDescriptionPhraseValue($trophyId)
	{
		$phraseName = $this->getTrophyDescriptionPhraseName($trophyId);
		return $this->_getPhraseModel()->getMasterPhraseValue($phraseName);
	}

	/**
	 * Get all trophies for the specified users.
	 *
	 * @return array Format: [user id][trophy id] => award date
	 */
	public function getUserTrophiesByUserIds(array $userIds)
	{
		if (!$userIds)
		{
			return array();
		}

		$db = $this->_getDb();

		$output = array();
		$userTrophiesResult = $db->query('
			SELECT user_id, trophy_id, award_date
			FROM xf_user_trophy
			WHERE user_id IN (' . $db->quote($userIds) . ')
		');
		while ($userTrophy = $userTrophiesResult->fetch())
		{
			$output[$userTrophy['user_id']][$userTrophy['trophy_id']] = $userTrophy['award_date'];
		}

		return $output;
	}

	/**
	 * Award the specified user with a specific trophy.
	 *
	 * @param array $user
	 * @param string $username
	 * @param array $trophy
	 * @param integer|null $awardDate If null, use current time
	 */
	public function awardUserTrophy(array $user, $username, array $trophy, $awardDate = null)
	{
		if ($awardDate === null)
		{
			$awardDate = XenForo_Application::$time;
		}

		$db = $this->_getDb();
		XenForo_Db::beginTransaction($db);

		$result = $db->query('
			INSERT IGNORE INTO xf_user_trophy
				(user_id, trophy_id, award_date)
			VALUES
				(?, ?, ?)
		', array($user['user_id'], $trophy['trophy_id'], $awardDate));

		if ($result->rowCount())
		{
			$db->query('
				UPDATE xf_user SET
					trophy_points = trophy_points + ?
				WHERE user_id = ?
			', array($trophy['trophy_points'], $user['user_id']));

			if (XenForo_Model_Alert::userReceivesAlert($user, 'user', 'trophy'))
			{
				XenForo_Model_Alert::alert(
					$user['user_id'],
					$user['user_id'], $username,
					'user', $user['user_id'],
					'trophy',
					array('trophy_id' => $trophy['trophy_id'])
				);
			}
		}

		XenForo_Db::commit($db);
	}

	public function updateTrophyPointsForUser($userId, $points = null)
	{
		if ($points === null)
		{
			$points = $this->recalculateTrophyPointsForUser($userId);
		}

		$this->_getDb()->update('xf_user',
			array('trophy_points' => $points),
			'user_id = ' . $this->_getDb()->quote($userId)
		);

		return $points;
	}

	public function recalculateTrophyPointsForUser($userId)
	{
		return intval($this->_getDb()->fetchOne("
			SELECT SUM(trophy.trophy_points)
			FROM xf_user_trophy AS user_trophy
			INNER JOIN xf_trophy AS trophy ON (user_trophy.trophy_id = trophy.trophy_id)
			WHERE user_trophy.user_id = ?
		", $userId));
	}

	public function updateTrophiesForUser(array $user, array $userTrophies = null, array $trophies = null)
	{
		$awarded = 0;

		if ($trophies === null)
		{
			$trophies = $this->getAllTrophies();
		}
		if (!$trophies)
		{
			return 0;
		}

		if ($userTrophies === null)
		{
			$userTrophies = $this->getTrophiesForUserId($user['user_id']);
		}

		foreach ($trophies AS $trophy)
		{
			if (isset($userTrophies[$trophy['trophy_id']]))
			{
				continue;
			}

			if (XenForo_Helper_Criteria::userMatchesCriteria($trophy['user_criteria'], false, $user))
			{
				$this->awardUserTrophy($user, $user['username'], $trophy);
				$awarded++;
			}
		}

		return $awarded;
	}

	/**
	 * Gets all trophy user titles.
	 *
	 * @return array [minimum points] => info
	 */
	public function getAllTrophyUserTitles()
	{
		return $this->fetchAllKeyed('
			SELECT *
			FROM xf_trophy_user_title
			ORDER BY minimum_points
		', 'minimum_points');
	}

	/**
	 * Updates the given set of user titles. The set is assumed to be all user titles,
	 * as the existing ones are removed before updating.
	 *
	 * @param array $titles [] => [title, minimum_points]
	 * @param boolean $rebuildCache If true, rebuilds the user title cache
	 */
	public function updateTrophyUserTitles(array $titles, $rebuildCache = true)
	{
		$db = $this->_getDb();
		XenForo_Db::beginTransaction($db);

		$db->delete('xf_trophy_user_title');

		foreach ($titles AS $titleInfo)
		{
			if (isset($titleInfo['title'], $titleInfo['minimum_points']))
			{
				$this->insertTrophyUserTitle($titleInfo['title'], $titleInfo['minimum_points'], false);
			}
		}

		if ($rebuildCache)
		{
			$this->rebuildTrophyUserTitleCache();
		}

		XenForo_Db::commit($db);
	}

	/**
	 * Inserts a new trophy user title. Throws an exception if error occurs.
	 *
	 * @param string $title
	 * @param integer $minimumPoints
	 * @param boolean $rebuildCache
	 */
	public function insertTrophyUserTitle($title, $minimumPoints, $rebuildCache = true)
	{
		$minimumPoints = intval($minimumPoints);
		if ($minimumPoints < 0)
		{
			$minimumPoints = 0;
		}

		$existing = $this->_getDb()->fetchRow('
			SELECT minimum_points
			FROM xf_trophy_user_title
			WHERE minimum_points = ?
		', $minimumPoints);
		if ($existing)
		{
			throw new XenForo_Exception(new XenForo_Phrase('trophy_already_exists_for_x_points', array('count' => $minimumPoints)), true);
		}

		$this->_getDb()->insert('xf_trophy_user_title', array(
			'minimum_points' => $minimumPoints,
			'title' => utf8_substr($title, 0, 50)
		));

		if ($rebuildCache)
		{
			$this->rebuildTrophyUserTitleCache();
		}
	}

	/**
	 * Deletes the sepcified user titles.
	 *
	 * @param array $points List of minimum point values to delete
	 * @param boolean $rebuildCache
	 */
	public function deleteTrophyUserTitles(array $points, $rebuildCache = true)
	{
		if (!$points)
		{
			return;
		}

		$db = $this->_getDb();
		$db->delete('xf_trophy_user_title', 'minimum_points IN (' . $db->quote($points) . ')');

		if ($rebuildCache)
		{
			$this->rebuildTrophyUserTitleCache();
		}
	}

	/**
	 * Rebuilds the trophy user title cache.
	 *
	 * @return array [minimum_points] => title
	 */
	public function rebuildTrophyUserTitleCache()
	{
		$titles = $this->getAllTrophyUserTitles();
		$cache = array();
		foreach ($titles AS $title)
		{
			$cache[$title['minimum_points']] = $title['title'];
		}

		krsort($cache, SORT_NUMERIC);

		$this->_getDataRegistryModel()->set('trophyUserTitles', $cache);
		return $cache;
	}

	/**
	 * @return XenForo_Model_Phrase
	 */
	protected function _getPhraseModel()
	{
		return $this->getModelFromCache('XenForo_Model_Phrase');
	}

	/**
	 * @return XenForo_Model_Alert
	 */
	protected function _getAlertModel()
	{
		return $this->getModelFromCache('XenForo_Model_Alert');
	}
}