<?php

namespace App\Utils\Db;

/**
 * The class is used for creating a new table in the db with the scope of dropping it at the end.
 * The table can be temporary or not (by default it is temporary). Only a table created by the
 * same class instance can be dropped.
 * @author Gabriel
 */
class ManageTable
{

	/**
	 * The DB table name that stores the report content.
	 * @var string
	 */
	protected string $tableName = '';

	/**
	 * Indicates if the table should be temporary or not.
	 * By default, the table is temporary.
	 * @var boolean
	 */
	protected bool $temporary = true;

	/**
	 * The list of columns to be added in the table.
	 * @var array
	 */
	protected array $columns = [];

	/**
	 * Indicates that the table was created by this class; otherwise you cannot call drop table.
	 * @var boolean
	 */
	protected bool $tableCreated = false;

	/**
	 * The list of PK columns, indicated as array.
	 * @var array
	 */
	protected array $pkColumns = [];


	/**
	 * Sets the DB table name.
	 * @param string $tableName
	 */
	public function __construct(string $tableName)
	{
		$this->tableName = $tableName;
	}

	/**
	 * Indicates if the DB table should be temporary or not.
	 * @param bool $temporary
	 */
	public function setTemporary(bool $temporary) :void
	{
		$this->temporary = $temporary;
	}

	/**
	 * Adds a new column to the table (just stored internally).
	 * @param string $columnName
	 * @param string $columnDef
	 */
	public function addColumnByDef(string $columnName, string $columnDef) :void
	{
		if (!strlen($columnName)) {
			throw new \Exception('You must set a valid column name.');
		}
		if (!strlen($columnDef)) {
			throw new \Exception('You must set a valid column definition.');
		}
		$this->columns[$columnName] = $columnDef;
	}

	/**
	 * Stores the combination of PK columns.
	 * @param array $pkColumns
	 */
	public function addPkColumns(array $pkColumns)
	{
		// validate PK columns
		foreach ($pkColumns as $pkCol) {
			if (!key_exists($pkCol, $this->columns)) {
				throw new \Exception("The PK column `{$pkCol}` is not valid.");
			}
		}

		$this->pkColumns = $pkColumns;
	}

	/**
	 * Returns the already set PK column(s).
	 * @param array $pkColumns
	 * @return array
	 */
	public function getPkColumns() :array
	{
		return $this->pkColumns;
	}

	/**
	 * Returns the SQL for the create table.
	 * @return string
	 */
	public function getCreateTableSQL() :string
	{

		// CHECK INPUT ----------------------------------------------------------------------------

		if (!$this->tableName) {
			throw new \Exception('No table name set for the table creation.');
		}
		if (!$this->columns) {
			throw new \Exception('No columns set for the table creation.');
		}

		$CFG = \Engine\Loader_Cfg::getInstance()->getGlobal();

		// BUILD SQL FOR TABLE CREATION -----------------------------------------------------------

		// create table
		$sql = "CREATE" . ($this->temporary?' TEMPORARY':'') . " TABLE `{$this->tableName}` (";

		// add cols
		$colsSqls = [];
		foreach ($this->columns as $colName=>$colDef) {
			$colsSqls[] = "`{$colName}` {$colDef}";
		}
		$sql .= implode(', ', $colsSqls);

		// add PKs
		if ($this->pkColumns) {
			$sql .= ", PRIMARY KEY (" . implode(', ', array_map( fn($elem) => "`{$elem}`", $this->pkColumns) ) . ")";
		}

		// finish and add extra infos
		$sql .= ") ENGINE=INNODB DEFAULT CHARSET={$CFG['encodings']['dbCharacterSet']} COLLATE={$CFG['encodings']['dbCollation']} ROW_FORMAT=DYNAMIC";

		return $sql;
	}

	/**
	 *
	 */
	public function createTable()
	{

		// create the table - this will throw exception in case of failure
		\App\Utils\Db\TablesUtils::getInstance()->createTable($this->tableName, $this->getCreateTableSQL());

		// stores that the table was created, so it is allowed to be dropped by this class.
		$this->tableCreated = true;

	}


	/**
	 * Drops the current table
	 */
	public function dropTable() :void
	{
		// table name must be defined
		if (!$this->tableName) {
			throw new \Exception('You must set the table name before calling '.__METHOD__);
		}
		// if table is not yet created, skip this step
		if (!$this->tableCreated) {
			return ;
		}

		$db = \DB::getInstance();

		if ($this->temporary) {
			$sql = "DROP TEMPORARY TABLE `{$this->tableName}`";
		} else {
			$sql = "DROP TABLE `{$this->tableName}`";
		}

		$db->Execute($sql);

		if ($db->ErrorNo()) {
			throw new \DBException("The report DB table named `{$this->tableName}` could not be created.");
		}

		// reset the tableCreated flag
		$this->tableCreated = false;

	}

	/**
	 * Returns the name of the DB table.
	 * @return string
	 */
	public function getTableName() :string
	{
		return $this->tableName;
	}

}
