<?php
declare(strict_types = 1);

/**
 * Méthodes de base de données pour SQLite.
 *
 * @license https://www.gnu.org/licenses/gpl-3.0.html
 * @link https://www.igalerie.org/
 */
class DBLayer
{
	/**
	 * Nom du SGBD.
	 *
	 * @var string
	 */
	const NAME = 'SQLite';

	/**
	 * Numéro de port du serveur par défaut.
	 *
	 * @var string
	 */
	const PORT_DEFAULT = '';

	/**
	 * Version minimale requise.
	 *
	 * @var string
	 */
	const VERSION_MIN = '3';



	/**
	 * Retourne des informations sur la base de données.
	 *
	 * @return array
	 */
	public static function getDetails(): array
	{
		$details =
		[
			'filesize' =>
			[
				'text' => __('Taille de la base de données'),
				'value' => L10N::formatFilesize(filesize(CONF_DB_NAME))
			]
		];

		return $details;
	}

	/**
	 * Recrée une table à partir du schéma SQL de l'installation
	 * tout en conservant les données précédemment enregistrées.
	 * Utile pour supprimer une colonne ou en modifier le type.
	 *
	 * @param string $table_name
	 *   Nom de la table.
	 *
	 * @return bool
	 */
	public static function rebuildTable(string $table_name): bool
	{
		// Récupère la requête CREATE de la table.
		if (!$create_table_sql = SQL::getSchema($table_name))
		{
			trigger_error('Schema error.', E_USER_ERROR);
			return FALSE;
		}

		// Désactive les clés étrangères.
		if (!DB::execute('PRAGMA foreign_keys = OFF'))
		{
			return FALSE;
		}

		// Démarre une transaction.
		if (!($in_transaction = DB::inTransaction()) && !DB::beginTransaction())
		{
			return FALSE;
		}

		// Récupère le nombre de ligne de la table.
		if (!DB::execute("SELECT COUNT(*) FROM {{$table_name}}"))
		{
			goto fail;
		}
		$count = DB::fetchVal();

		// Crée la nouvelle table.
		if (!DB::execute(str_replace("{{$table_name}}", '_temp', $create_table_sql)))
		{
			goto fail;
		}

		// Récupère la liste des colonnes de la nouvelle table.
		if (!DB::execute('PRAGMA table_info(_temp)'))
		{
			goto fail;
		}
		if (!$columns = implode(', ', array_values(DB::fetchAll('name', 'name'))))
		{
			trigger_error('Columns error.', E_USER_ERROR);
			goto fail;
		}

		// Copie le contenu de l'ancienne table vers la nouvelle.
		if (!DB::execute("INSERT INTO _temp($columns) SELECT $columns FROM {{$table_name}}"))
		{
			goto fail;
		}

		// Récupère et compare le nombre de ligne de la nouvelle table.
		if (!DB::execute('SELECT COUNT(*) FROM _temp'))
		{
			goto fail;
		}
		if ($count !== DB::fetchVal())
		{
			trigger_error('Count error.', E_USER_ERROR);
			goto fail;
		}

		// Supprime l'ancienne table.
		if (!DB::execute("DROP TABLE {{$table_name}}"))
		{
			goto fail;
		}

		// Renomme la nouvelle table.
		if (!DB::execute("ALTER TABLE _temp RENAME TO {{$table_name}}"))
		{
			goto fail;
		}

		// Exécute la transaction.
		if (!$in_transaction && !DB::commitTransaction())
		{
			goto fail;
		}

		// Réactive les clés étrangères.
		return DB::execute('PRAGMA foreign_keys = ON');

		fail:
		DB::rollbackTransaction();
		DB::execute('PRAGMA foreign_keys = ON');
		return FALSE;
	}

	/**
	 * Nettoie la base de données.
	 *
	 * @return bool
	 */
	public static function vacuum(): bool
	{
		return DB::execute('VACUUM');
	}



	/**
	 * Retourne le DSN pour se connecter à la base de données.
	 *
	 * @return string
	 */
	protected static function _getDSN(): string
	{
		if (defined('CONF_ACCESS_KEY') && CONF_ACCESS_KEY && !file_exists(CONF_DB_NAME))
		{
			die('No database.');
		}
		return CONF_DB_TYPE . ':' . CONF_DB_NAME;
	}

	/**
	 * Paramètres d'initialisation.
	 *
	 * @param PDO $pdo
	 *
	 * @return bool
	 */
	protected static function _initialize(PDO $pdo): bool
	{
		// Fonction SQL "REGEXP".
		$regexp = function(string $pattern, $value): bool
		{
			if ($value === NULL)
			{
				return FALSE;
			}

			return (bool) mb_eregi(Utility::convertPOSIXtoPCRE($pattern), (string) $value);
		};

		// Fonction SQL "REGEXP_REPLACE".
		$regexp_replace = function($value, $pattern, $replace): string
		{
			return (string) mb_eregi_replace(Utility::convertPOSIXtoPCRE((string) $pattern),
				(string) $replace, (string) $value);
		};

		// Enregistrement de fonctions utilisateur.
		$create_function = version_compare(PHP_VERSION, '8.4', '<')
			? 'sqliteCreateFunction'
			: 'createFunction';
		if (method_exists($pdo, $create_function))
		{
			if (!$pdo->{$create_function}('regexp', $regexp, 2)
			 || !$pdo->{$create_function}('regexp_replace', $regexp_replace, 3))
			{
				return FALSE;
			}
		}

		// On booste les performances.
		// https://sqlite.org/pragma.html#pragma_synchronous
		if (!DB::execute('PRAGMA synchronous = OFF'))
		{
			return FALSE;
		}

		// Activation des clés étrangères.
		if (!DB::execute('PRAGMA foreign_keys = ON'))
		{
			return FALSE;
		}

		return TRUE;
	}

	/**
	 * Nom de la séquence d'objets (uniquement pour PostgreSQL).
	 *
	 * @param array $seq
	 *
	 * @return null
	 */
	protected static function _seqName(array $seq = [])
	{
		return NULL;
	}

	/**
	 * Code SQL à remplacer.
	 *
	 * @param string $sql
	 *
	 * @return string
	 */
	protected static function _replaceSQL(string $sql): string
	{
		// Remplacement de la fonction NOW().
		if (CONF_DB_DATE == 'php')
		{
			$sql = str_replace("NOW()", "'" . date('Y-m-d H:i:s') . "'", $sql);
		}

		// Addition de temps.
		$sql = preg_replace('`DATE_ADD\(([^,]+),\s*INTERVAL\s+([^\)]+)\)`',
			'datetime($1, \'$2\')', $sql);

		// Opérateur LIKE.
		if (strstr($sql, 'LIKE'))
		{
			$likes = '(?:\?|:?[0-9a-z_]+|\'.+?(?<!\x5c)\')';
			$sql = preg_replace(
				"`(LIKE\s+$likes(?:\s*\|\|\s*$likes)*)`i",
				"$1 ESCAPE '\x5c'",
				$sql
			);
		}

		// DATE_FORMAT() => strftime().
		$sql = preg_replace('`DATE_FORMAT\(([^,]+),\s*([^\)]+)\)`', 'strftime($2, $1)', $sql);

		// Autres parties à remplacer.
		$sql = strtr($sql,
		[
			'AUTO_INCREMENT' => 'AUTOINCREMENT',
			'INSERT IGNORE' => 'INSERT OR IGNORE',
			'NOW()' => "datetime('now')",
			'RAND()' => 'RANDOM()',
			'TO_SECONDS(' => "strftime('%s', ",
			'UPDATE IGNORE' => 'UPDATE OR IGNORE'
		]);

		return $sql;
	}
}
?>