<?php

/**
 * Model for polls.
 *
 * @package XenForo_Poll
 */
class XenForo_Model_Poll extends XenForo_Model
{
	/**
	 * Gets the specified poll.
	 *
	 * @param integer $id
	 *
	 * @return array|false
	 */
	public function getPollById($id)
	{
		return $this->_getDb()->fetchRow('
			SELECT *
			FROM xf_poll
			WHERE poll_id = ?
		', $id);
	}

	/**
	 * Gets the specified poll by the content it belongs to.
	 *
	 * @param string $contentType
	 * @param integer $contentId
	 *
	 * @return array|false
	 */
	public function getPollByContent($contentType, $contentId)
	{
		return $this->_getDb()->fetchRow('
			SELECT *
			FROM xf_poll
			WHERE content_type = ?
				AND content_id = ?
		', array($contentType, $contentId));
	}

	/**
	 * Gets poll IDs starting from after the specified start, up to the given limit
	 *
	 * @param integer $start
	 * @param integer $limit
	 */
	public function getPollIdsInRange($start, $limit)
	{
		$db = $this->_getDb();

		return $db->fetchCol($db->limit('
			SELECT poll_id
			FROM xf_poll
			WHERE poll_id > ?
			ORDER BY poll_id
		', $limit), $start);
	}

	/**
	 * Gets poll response.
	 *
	 * @param integer $id
	 *
	 * @return array|false
	 */
	public function getPollResponseById($id)
	{
		return $this->_getDb()->fetchRow('
			SELECT *
			FROM xf_poll_response
			WHERE poll_response_id = ?
		', $id);
	}

	/**
	 * Gets all poll responses that belong to the specified poll.
	 *
	 * @param $pollId
	 *
	 * @return array [poll response id] => info
	 */
	public function getPollResponsesInPoll($pollId)
	{
		return $this->fetchAllKeyed('
			SELECT *
			FROM xf_poll_response
			WHERE poll_id = ?
			ORDER BY poll_response_id
		', 'poll_response_id', $pollId);
	}

	/**
	 * Gets poll response cache for use in the poll table.
	 *
	 * @param integer $pollId
	 *
	 * @return array [poll response id] => [response, response_vote_count, voters]
	 */
	public function getPollResponseCache($pollId)
	{
		$responses = $this->getPollResponsesInPoll($pollId);
		$output = array();

		foreach ($responses AS $response)
		{
			$output[$response['poll_response_id']] = array(
				'response' => $response['response'],
				'response_vote_count' => $response['response_vote_count'],
				'voters' => unserialize($response['voters'])
			);
		}

		return $output;
	}

	/**
	 * Rebuilds the poll response cache in the specified poll.
	 *
	 * @param integer $pollId
	 *
	 * @return array The response cache
	 */
	public function rebuildPollResponseCache($pollId)
	{
		$cache = $this->getPollResponseCache($pollId);

		$db = $this->_getDb();
		$db->update('xf_poll',
			array('responses' => serialize($cache)),
			'poll_id = ' . $db->quote($pollId)
		);

		return $cache;
	}

	/**
	 * Prepares the poll responses for viewing from the poll record's response cache.
	 *
	 * @param array|string $responses Serialized array or array itself
	 * @param array|null $viewingUser
	 *
	 * @return array|false Responses prepared; false if responses can't be prepared
	 */
	public function preparePollResponsesFromCache($responses, array $viewingUser = null)
	{
		$this->standardizeViewingUserReference($viewingUser);

		if (!is_array($responses))
		{
			$responses = unserialize($responses);
		}
		if (!is_array($responses))
		{
			return false;
		}

		foreach ($responses AS &$response)
		{
			$response['response'] = XenForo_Helper_String::censorString($response['response']);
			$response['hasVoted'] = isset($response['voters'][$viewingUser['user_id']]);
		}

		return $responses;
	}

	/**
	 * Prepares the poll for viewing.
	 *
	 * @param array $poll
	 * @param boolean $canVote If user can vote based on content-specified permissions
	 * @param array|null $viewingUser
	 *
	 * @return array
	 */
	public function preparePoll(array $poll, $canVote, array $viewingUser = null)
	{
		if (!is_array($poll['responses']))
		{
			$poll['responses'] = $this->preparePollResponsesFromCache($poll['responses'], $viewingUser);
		}
		if (!is_array($poll['responses']))
		{
			$poll['responses'] = $this->preparePollResponsesFromCache(
				$this->rebuildPollResponseCache($poll['poll_id']),
				$viewingUser
			);
		}

		$poll['hasVoted'] = false;
		foreach ($poll['responses'] AS $response)
		{
			if (!empty($response['hasVoted']))
			{
				$poll['hasVoted'] = true;
				break;
			}
		}

		$poll['open'] = (!$poll['close_date'] || $poll['close_date'] > XenForo_Application::$time);

		if (!$canVote || $poll['hasVoted'] || !$poll['open'])
		{
			$poll['canVote'] = false;
		}
		else
		{
			$poll['canVote'] = true;
		}

		$poll['question'] = XenForo_Helper_String::censorString($poll['question']);

		return $poll;
	}

	/**
	 * Determines if the viewing user can vote on the poll. This does not take into account
	 * content-specific permissions.
	 *
	 * @param array $poll
	 * @param string $errorPhraseKey
	 * @param array|null $viewingUser
	 *
	 * @return boolean
	 */
	public function canVoteOnPoll(array $poll, &$errorPhraseKey = '', array $viewingUser = null)
	{
		if ($poll['close_date'] && $poll['close_date'] < XenForo_Application::$time)
		{
			return false;
		}

		$this->standardizeViewingUserReference($viewingUser);

		if (!$viewingUser['user_id'])
		{
			return false;
		}

		return ($this->hasVotedOnPoll($poll['poll_id'], $viewingUser['user_id']) ? false : true);
	}

	/**
	 * Returns true if the user has voted on this poll.
	 *
	 * @param integer $pollId
	 * @param integer $userId
	 */
	public function hasVotedOnPoll($pollId, $userId)
	{
		$voted = $this->_getDb()->fetchRow('
			SELECT poll_response_id
			FROM xf_poll_vote
			WHERE poll_id = ?
				AND user_id = ?
		', array($pollId, $userId));
		return ($voted ? true : false);
	}

	/**
	 * Votes on the specified poll.
	 *
	 * @param integer $pollId
	 * @param integer|array $votes One or more poll response IDs to vote on. This does not check if the poll allows multiple votes.
	 * @param integer|null $userId
	 * @param integer|null $voteDate
	 *
	 * @return boolean
	 */
	public function voteOnPoll($pollId, $votes, $userId = null, $voteDate = null)
	{
		if (!is_array($votes))
		{
			if (!$votes)
			{
				return false;
			}
			$votes = array($votes);
		}
		if (!$votes)
		{
			return false;
		}

		if ($userId === null)
		{
			$userId = XenForo_Visitor::getUserId();
		}
		if (!$userId)
		{
			return false;
		}

		if ($voteDate === null)
		{
			$voteDate = XenForo_Application::$time;
		}

		$responses = $this->getPollResponsesInPoll($pollId);

		if ($this->hasVotedOnPoll($pollId, $userId))
		{
			return false;
		}

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

		$newVoter = true;

		foreach ($votes AS $voteResponseId)
		{
			if (isset($responses[$voteResponseId]))
			{
				$res = $db->query('
					INSERT IGNORE INTO xf_poll_vote
						(user_id, poll_response_id, poll_id, vote_date)
					VALUES
						(?, ?, ?, ?)
				', array($userId, $voteResponseId, $pollId, $voteDate));

				if ($res->rowCount())
				{
					$voterCache = $this->getPollResponseVoterCache($voteResponseId);
					$db->query('
						UPDATE xf_poll_response SET
							response_vote_count = response_vote_count + 1,
							voters = ?
						WHERE poll_response_id = ?
					', array(serialize($voterCache), $voteResponseId));
				}
				else
				{
					$newVoter = false;
				}
			}
		}

		if ($newVoter)
		{
			$pollDw = XenForo_DataWriter::create('XenForo_DataWriter_Poll');
			$pollDw->setExistingData($pollId);
			$pollDw->set('voter_count', $pollDw->get('voter_count') + 1);
			$pollDw->save();
		}

		XenForo_Db::commit($db);

		return true;
	}

	public function getPollResponseVoterCache($pollResponseId)
	{
		return $this->fetchAllKeyed('
			SELECT poll_vote.user_id, user.username
			FROM xf_poll_vote AS poll_vote
			LEFT JOIN xf_user AS user ON (poll_vote.user_id = user.user_id)
			WHERE poll_vote.poll_response_id = ?
		', 'user_id', $pollResponseId);
	}

	public function getPollVoterCount($pollId)
	{
		return $this->_getDb()->fetchOne('
			SELECT COUNT(DISTINCT user_id)
			FROM xf_poll_vote
			WHERE poll_id = ?
		', $pollId);
	}

	public function rebuildPollData($pollId)
	{
		$db = $this->_getDb();

		$votes = array();
		$voters = array();
		$results = $db->query('
			SELECT poll_vote.poll_response_id, poll_vote.user_id, user.username
			FROM xf_poll_vote AS poll_vote
			LEFT JOIN xf_user AS user ON (poll_vote.user_id = user.user_id)
			WHERE poll_vote.poll_id = ?
		', $pollId);
		while ($vote = $results->fetch())
		{
			$votes[$vote['poll_response_id']][$vote['user_id']] = array(
				'user_id' => $vote['user_id'],
				'username' => $vote['username']
			);
			$voters[$vote['user_id']] = true;
		}

		$responses = $this->getPollResponsesInPoll($pollId);

		XenForo_Db::beginTransaction($db);

		foreach ($responses AS $responseId => $response)
		{
			if (!isset($votes[$responseId]))
			{
				$db->update('xf_poll_response', array(
					'response_vote_count' => 0,
					'voters' => ''
				), 'poll_response_id = ' . $db->quote($responseId));
			}
			else
			{
				$db->update('xf_poll_response', array(
					'response_vote_count' => count($votes[$responseId]),
					'voters' => serialize($votes[$responseId])
				), 'poll_response_id = ' . $db->quote($responseId));
			}
		}

		$dw = XenForo_DataWriter::create('XenForo_DataWriter_Poll', XenForo_DataWriter::ERROR_SILENT);
		if ($dw->setExistingData($pollId))
		{
			$dw->set('voter_count', count($voters));
			$dw->set('responses', serialize($this->getPollResponseCache($pollId)));
			$dw->save();
		}

		XenForo_Db::commit($db);
	}
}