<?php
namespace DB;
final class mPDO {
	private $connection = null;
	private $statement = 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() {
		try {
			 
			 
			$this->connection = new \PDO("mysql:host=" . $this->hostname . ";port=" . $this->port . ";dbname=" . $this->database . ";charset=utf8mb4", $this->username, $this->password, array(\PDO::ATTR_PERSISTENT => false));
		} catch(\PDOException $e) {
			throw new \Exception('Failed to connect to database. Reason: \'' . $e->getMessage() . '\'');
		}

		$this->connection->exec("SET NAMES 'utf8mb4'");
		$this->connection->exec("SET CHARACTER SET utf8mb4");
		$this->connection->exec("SET CHARACTER_SET_CONNECTION=utf8mb4");
		$this->connection->exec("SET SQL_MODE = ''");
		if ($this->session_timezone_offset !== '') {
			$this->connection->exec("SET time_zone = '" . $this->session_timezone_offset . "'");
		}
	}

	public function prepare($sql) {
		$this->ensureConnection();
		$this->statement = $this->connection->prepare($sql);
	}

	public function bindParam($parameter, $variable, $data_type = \PDO::PARAM_STR, $length = 0) {
		if ($length) {
			$this->statement->bindParam($parameter, $variable, $data_type, $length);
		} else {
			$this->statement->bindParam($parameter, $variable, $data_type);
		}
	}

	public function execute() {
		try {
			if ($this->statement && $this->statement->execute()) {
				$data = array();

				while ($row = $this->statement->fetch(\PDO::FETCH_ASSOC)) {
					$data[] = $row;
				}

				$result = new \stdClass();
				$result->row = (isset($data[0])) ? $data[0] : array();
				$result->rows = $data;
				$result->num_rows = $this->statement->rowCount();
			}
		} catch(\PDOException $e) {
			throw new \Exception('Error: ' . $e->getMessage() . ' Error Code : ' . $e->getCode());
		}
	}

	public function query($sql, $params = array()) {
		$this->ensureConnection();
		$this->statement = $this->connection->prepare($sql);
		
		$result = false;

		try {
			if ($this->statement && $this->statement->execute($params)) {
				$data = array();

				while ($row = $this->statement->fetch(\PDO::FETCH_ASSOC)) {
					$data[] = $row;
				}

				$result = new \stdClass();
				$result->row = (isset($data[0]) ? $data[0] : array());
				$result->rows = $data;
				$result->num_rows = $this->statement->rowCount();
			}
		} catch (\PDOException $e) {
			throw new \Exception('Error: ' . $e->getMessage() . ' Error Code : ' . $e->getCode() . ' <br />' . $sql);
		}

		if ($result) {
			return $result;
		} else {
			$result = new \stdClass();
			$result->row = array();
			$result->rows = array();
			$result->num_rows = 0;
			return $result;
		}
	}

	public function escape($value) {
		return str_replace(array("\\", "\0", "\n", "\r", "\x1a", "'", '"'), array("\\\\", "\\0", "\\n", "\\r", "\Z", "\'", '\"'), $value);
	}

	public function countAffected() {
		if ($this->statement) {
			return $this->statement->rowCount();
		} else {
			return 0;
		}
	}

	public function getLastId() {
		$this->ensureConnection();
		return $this->connection->lastInsertId();
	}
	
	public function connected() {
		return $this->connection instanceof \PDO;
	}

	public function isConnected() {
		return $this->connected();
	}

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

	public function close() {
		$this->statement = null;
		$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();
		$this->connection->exec("SET time_zone = '" . $this->session_timezone_offset . "'");
	}

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