<?php

/**
 * Abstract class for handling SQL installation code for add-ons.
 *
 * @author Liam W
 */
abstract class LiamW_Shared_DatabaseSchema_Abstract2
{
	/**
	 * Holds the versionId set in the constructor.
	 *
	 * @var int
	 */
	protected $_version;

	/**
	 * If false, returns false instead of throwing exception on error.
	 *
	 * @var bool
	 */
	protected $_throw = true;

	/**
	 * @var Zend_Db_Adapter_Abstract
	 */
	protected $_db = null;

	/**
	 * Factory method to create class. This should be used, the constructor is private.
	 *
	 * @param string $class   Name of class to instantiate.
	 * @param int    $version Addon versionId.
	 * @param bool   $throw   If false, no exception will be thrown on error.
	 *
	 * @return LiamW_Shared_DatabaseSchema_Abstract2|false
	 * @throws XenForo_Exception
	 */
	public static function create($class, $version = 0, $throw = true)
	{
		$class = XenForo_Application::resolveDynamicClass($class);

		if (!$class)
		{
			return false;
		}

		$instance = new $class($version, $throw);
		$instance->_db = XenForo_Application::getDb();

		return $instance;
	}

	final protected function __construct($version = 0, $throw = true)
	{
		$this->_version = $version;
		$this->_throw = $throw;
	}

	/**
	 * Install the tables from the {@link _getInstallSql} method.
	 *
	 * @param int $installedVersionId Overrides the versionId passed to the constructor.
	 *
	 * @return bool True on success, false on failure if {@link $_throw} is false.
	 * @throws XenForo_Exception
	 */
	final public function install($installedVersionId = 0)
	{
		if (!$this->_confirmRequirements())
		{
			return false;
		}

		if ($installedVersionId != $this->_version)
		{
			$this->_version = $installedVersionId;
		}

		$installSql = $this->_getInstallSql();
		$db = $this->_db;
		XenForo_Db::beginTransaction($db);

		foreach ($installSql as $version => $sqlArray)
		{
			if ($version <= $this->_version)
			{
				if (is_array($sqlArray))
				{
					foreach ($sqlArray as $sql)
					{
						try
						{
							$db->query($sql);
						} catch (Zend_Db_Exception $e)
						{
							return $this->_error($e->getMessage(), $version, $sql, $db, $e);
						}
					}
				}
				else
				{
					try
					{
						$db->query($sqlArray);
					} catch (Zend_Db_Exception $e)
					{
						return $this->_error($e->getMessage(), $version, $sqlArray, $db, $e);
					}
				}
			}
		}

		XenForo_Db::commit($db);

		return true;
	}

	/**
	 * Uninstalls tables based on SQL in the _getUninstallSql() method.
	 *
	 * @see _getUninstallSql()
	 */
	final public function uninstall()
	{
		if (!$this->_confirmRequirements())
		{
			return false;
		}

		$uninstallSql = $this->_getUninstallSql();
		$db = $this->_db;
		XenForo_Db::beginTransaction($db);

		foreach ($uninstallSql as $sql)
		{
			try
			{
				$db->query($sql);
			} catch (Zend_Db_Exception $e)
			{
				return $this->_error($e->getMessage(), null, $sql, $db, $e);
			}
		}

		XenForo_Db::commit($db);

		return true;
	}

	/**
	 * @param $sqlError string The error from the SQL.
	 * @param $version  int The version being installed.
	 * @param $sql      string The SQL code that caused the error.
	 * @param $db       Zend_Db_Adapter_Abstract The database instance.
	 * @param $e        Exception The exception instance.
	 *
	 * @return bool
	 * @throws XenForo_Exception
	 */
	final protected function _error($sqlError, $version, $sql, Zend_Db_Adapter_Abstract $db, Exception $e)
	{
		XenForo_Db::rollbackAll($db);

		if ($this->_throw)
		{
			throw new XenForo_Exception($this->_getErrorMessage($sqlError, $version, $sql), true);
		}
		else
		{
			return false;
		}
	}

	/**
	 * @param $sqlError string The error message from the database adapter.
	 * @param $version  int The version being run. If null, error occurred during uninstall.
	 * @param $sql      string The SQL string the error occurred on.
	 *
	 * @return string
	 */
	protected function _getErrorMessage($sqlError, $version, $sql)
	{
		return "An error occurred while running SQL.";
	}

	/**
	 * Check prerequisites for this database schema. If false, no SQL will run.
	 *
	 * @return boolean
	 */
	protected function _confirmRequirements()
	{
		return true;
	}

	/**
	 * Get the install SQL.
	 *
	 * The array should be an associative array of fields, like so:
	 *
	 * versionId =>
	 *        array =>
	 *                SQL Strings
	 *
	 *
	 * @return array
	 */
	abstract protected function _getInstallSql();

	/**
	 * Get the uninstall SQL.
	 *
	 * Unlike the install SQL, this should return an array of SQL code to run. All code will be run.
	 *
	 * @return array
	 */
	abstract protected function _getUninstallSql();
}