<?php
namespace DB;
final class MySQLi {
	private $connection = null;
	private $hostname;
	private $username;
	private $password;
	private $database;
	private $port;
	private $session_timezone_offset = '';

	public function __construct($hostname, $username, $password, $database, $port = '3306') {
		$this->hostname = $hostname;
		$this->username = $username;
		$this->password = $password;
		$this->database = $database;
		$this->port = $port ?: '3306';
		$this->connect();
	}

	private function connect() {
		$this->connection = new \mysqli($this->hostname, $this->username, $this->password, $this->database, $this->port);

		if ($this->connection->connect_error) {
			throw new \Exception('Error: ' . $this->connection->connect_error . '<br />Error No: ' . $this->connection->connect_errno);
		}

		$this->connection->set_charset("utf8mb4");
		$this->connection->query("SET SQL_MODE = ''");
		$this->connection->query("SET SESSION sql_mode = 'NO_ENGINE_SUBSTITUTION'");
		if ($this->session_timezone_offset !== '') {
			$this->connection->query("SET time_zone = '" . $this->session_timezone_offset . "'");
		}
	}

	public function query($sql) {
		$this->ensureConnection();
		$query = $this->connection->query($sql);

		if (!$this->connection->errno) {
			if ($query instanceof \mysqli_result) {
				$data = array();

				while ($row = $query->fetch_assoc()) {
					$data[] = $row;
				}

				$result = new \stdClass();
				$result->num_rows = $query->num_rows;
				$result->row = isset($data[0]) ? $data[0] : array();
				$result->rows = $data;

				$query->close();

				return $result;
			} else {
				return true;
			}
		} else {
			throw new \Exception('Error: ' . $this->connection->error  . '<br />Error No: ' . $this->connection->errno . '<br />' . $sql);
		}
	}

	public function escape($value) {
		$this->ensureConnection();
		return $this->connection->real_escape_string($value);
	}
	
	public function countAffected() {
		$this->ensureConnection();
		return $this->connection->affected_rows;
	}

	public function getLastId() {
		$this->ensureConnection();
		return $this->connection->insert_id;
	}
	
	public function connected() {
		return $this->connection instanceof \mysqli && $this->connection->ping();
	}

	public function getRawConnection(): \mysqli {
		$this->ensureConnection();
		return $this->connection;
	}

	public function close() {
		if ($this->connection instanceof \mysqli) {
			try {
				$this->connection->close();
			} finally {
				$this->connection = null;
			}
			return;
		}
		$this->connection = null;
	}

	public function reconnect() {
		$this->close();
		$this->connect();
	}

	public function setSessionTimezoneOffset($offset) {
		if (!preg_match('/^[+-](?:0[0-9]|1[0-4]):[0-5][0-9]$/D', (string)$offset)) {
			throw new \InvalidArgumentException('Invalid database timezone offset.');
		}
		$this->session_timezone_offset = (string)$offset;
		$this->ensureConnection();
		if (!$this->connection->query("SET time_zone = '" . $this->session_timezone_offset . "'")) {
			throw new \RuntimeException('Could not configure database timezone.');
		}
	}

	private function ensureConnection() {
		if (!$this->connection instanceof \mysqli) {
			$this->connect();
		}
	}
	
	public function __destruct() {
		$this->close();
	}
}
