<?php

class XenForo_Install_Model_Upgrade extends XenForo_Model
{
	public function insertUpgradeLog($versionId = null, $type = 'upgrade', $userId = null)
	{
		if ($versionId === null)
		{
			$versionId = XenForo_Application::$versionId;
		}

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

		$this->_getDb()->query('
			INSERT IGNORE INTO xf_upgrade_log
				(version_id, completion_date, user_id, log_type)
			VALUES
				(?, ?, ?, ?)
		', array($versionId, XenForo_Application::$time, $userId, $type));
	}

	public function updateVersion()
	{
		$this->getModelFromCache('XenForo_Model_Option')->updateOptions(
			array('currentVersionId' => XenForo_Application::$versionId)
		);
	}

	public function getLatestUpgradeVersionId()
	{
		return $this->_getDb()->fetchOne('
			SELECT MAX(version_id)
			FROM xf_upgrade_log
		');
	}

	public function getRemainingUpgradeVersionIds($lastCompletedVersion)
	{
		$searchDir = XenForo_Application::getInstance()->getRootDir() . '/library/XenForo/Install/Upgrade';

		$upgrades = array();
		foreach (glob($searchDir . '/*.php') AS $file)
		{
			$file = basename($file);

			switch($file)
			{
				case '1010031-100b1.php': // this was badly named - make sure it's always skipped so the right one runs
					continue 2;
			}

			$versionId = intval($file);
			if (!$versionId)
			{
				continue;
			}

			$upgrades[] = $versionId;
		}

		sort($upgrades, SORT_NUMERIC);

		foreach ($upgrades AS $key => $upgrade)
		{
			if ($upgrade > $lastCompletedVersion)
			{
				return array_slice($upgrades, $key);
			}
		}

		return array();
	}

	public function getNextUpgradeVersionId($lastCompletedVersion)
	{
		$upgrades = $this->getRemainingUpgradeVersionIds($lastCompletedVersion);
		return reset($upgrades);
	}

	public function getNewestUpgradeVersionId()
	{
		$upgrades = $this->getRemainingUpgradeVersionIds(0);
		return end($upgrades);
	}

	public function getUpgrade($versionId)
	{
		$versionId = intval($versionId);
		if (!$versionId)
		{
			throw new XenForo_Exception('No upgrade version ID specified.');
		}

		$searchDir = XenForo_Application::getInstance()->getRootDir() . '/library/XenForo/Install/Upgrade';

		$matches = glob($searchDir . '/' . $versionId . '*.php');
		foreach ($matches AS $file)
		{
			$file = basename($file);
			if (intval($file) == $versionId)
			{
				require($searchDir . '/' . $file);
				$class = 'XenForo_Install_Upgrade_' . intval($file);

				return new $class();
			}
		}

		throw new XenForo_Exception('Could not find the specified upgrade.');
	}

	public function setupVisitorFromUpgradeCookie()
	{
		$cookie = XenForo_Helper_Cookie::getCookie('upgrade');
		if (!$cookie)
		{
			return false;
		}

		/** @var $userModel XenForo_Model_User */
		$userModel = $this->getModelFromCache('XenForo_Model_User');

		$userId = $userModel->loginUserByRememberCookie($cookie);
		if ($userId)
		{
			XenForo_Visitor::setup($userId);
		}

		return $userId;
	}

	public function setUpgradeCookie($userId)
	{
		/** @var $userModel XenForo_Model_User */
		$userModel = $this->getModelFromCache('XenForo_Model_User');

		$value = $userModel->getUserRememberKeyForCookie($userId);
		if (!$value)
		{
			return false;
		}

		return XenForo_Helper_Cookie::setCookie('upgrade', $value, 0, true);
	}

	public function getDefaultSchemaErrors()
	{
		if (class_exists('XenForo_Install_Data_FileSums'))
		{
			$fileLookup = XenForo_Install_Data_FileSums::getHashes();
		}
		else
		{
			$fileLookup = null;
		}

		return $this->runSchemaCompare(
			'XenForo_DataWriter',
			XenForo_Autoloader::getInstance()->getRootDir() . '/XenForo/DataWriter',
			$fileLookup
		);
	}

	public function runSchemaCompare($dwClassPrefix, $dwClassDir, array $fileLookup = null)
	{
		$db = $this->_getDb();
		$tables = array_fill_keys($db->fetchCol('SHOW TABLES'), true);
		$errors = array();

		$dwClasses = $this->_findSchemaClasses($dwClassPrefix, $dwClassDir, $fileLookup);
		foreach ($dwClasses AS $class)
		{
			if (!class_exists($class))
			{
				continue;
			}

			$reflection = new ReflectionClass($class);
			if (!$reflection->isInstantiable() || !$reflection->isSubclassOf('XenForo_DataWriter'))
			{
				continue;
			}

			$dw = XenForo_DataWriter::create($class);
			foreach ($dw->getFields() AS $table => $fields)
			{
				if (!isset($tables[$table]))
				{
					$errors[$table] = "Table $table missing.";
					continue;
				}

				$columns = $this->fetchAllKeyed('
					SHOW COLUMNS FROM ' . $db->quoteIdentifier($table) . '
				', 'Field');

				foreach (array_keys($fields) AS $field)
				{
					if (!isset($columns[$field]))
					{
						$errors["$table.$field"] = "Column $table.$field missing.";
					}
				}
			}
		}

		return $errors;
	}

	protected function _findSchemaClasses($classPrefix, $searchDir, array $fileLookup = null)
	{
		$searchDir = rtrim($searchDir, '/\\');
		$dir = opendir($searchDir);
		if (!$dir)
		{
			return array();
		}

		$output = array();
		while (($entry = readdir($dir)) !== false)
		{
			if ($entry == '.' || $entry == '..')
			{
				continue;
			}

			$fullPath = "$searchDir/$entry";

			if ($fileLookup !== null)
			{
				$testFile = str_replace(XenForo_Autoloader::getInstance()->getRootDir(), 'library', $fullPath);
				if (!isset($fileLookup[$testFile]))
				{
					// this file doesn't exist any more - likely a left over from a previous version
					continue;
				}
			}

			if (preg_match('#^([a-z0-9_]+)\.php$#i', $entry, $match))
			{
				$output[] = $classPrefix . '_' . $match[1];
			}
			else if (is_dir($fullPath))
			{
				$output = array_merge($output,
					$this->_findSchemaClasses("{$classPrefix}_{$entry}", $fullPath)
				);
			}
		}

		return $output;
	}
}