<?php
	namespace Juju;
	require_once('Data/securestring.class.php');
	require_once('PDO/transaction.class.php');
	require_once('PDO/table.class.php');
	use Juju\{Data\SecureString, PDO\Table, PDO\Transaction};

	class PDO {
		public static function from(string $dsnstring){
			$parts = explode(':', $dsnstring);
			$dsnstring = $parts[1];
			$dsn = explode(';', $dsnstring);
			$dsn = array_reduce($dsn, function($dsn, $item){
				$item = explode('=', $item);
				$dsn[$item[0]] = $item[1];
				return $dsn;
			});
			if(!isset($dsn['host'])){
				throw new \Exception("DSN '{$dsnstring}' missing host");
			}
			if(!isset($dsn['dbname'])){
				throw new \Exception("DSN '{$dsnstring}' missing dbname");
			}
			if(!isset($dsn['user'])){
				$user = $dsn['dbname'];
			}else{
				$user = $dsn['user'];
			}
			if(!isset($dsn['pass'])){
				$dsn['pass'] = $user;
			}
			$pass = SecureString::from($dsn['pass']);
			unset($dsn['pass'], $dsn['user']);
			$dsn = array_reduce(array_keys($dsn), function($a, $key) use($dsn){
				$a[] = "{$key}={$dsn[$key]}";
				return $a;
			});
			$dsnstring = $parts[0].':'.implode(';', $dsn);
			return new PDO($dsnstring, $user, $pass);
		}

		private $pdo;
		private function __construct(string $dsn, string $user, SecureString $pass){
			$mysql = explode(':', $dsn)[0] === 'mysql';
			$options = [];
			if($mysql){
				$options[\PDO::MYSQL_ATTR_INIT_COMMAND] = "SET NAMES 'UTF8'";
				$options[\PDO::MYSQL_ATTR_MULTI_STATEMENTS] = false;
				$options[\PDO::MYSQL_ATTR_FOUND_ROWS] = true;
			}
			$pdo = new \PDO($dsn, $user, (string)$pass, $options);
			$pdo->setAttribute(\PDO::ATTR_AUTOCOMMIT, true);
			$pdo->setAttribute(\PDO::ATTR_CASE, \PDO::CASE_LOWER);
			$pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);
			$pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC);
			$this->pdo = $pdo;
		}
		public function transaction(callable $fn){
			$pdo = $this->pdo;
			if($pdo->inTransaction()){
				throw new \Exception("Unable to start a new transaction");
			}
			$transaction = new Transaction($this);
			if($fn($transaction) === false){
				do{
					$transaction->rollback();
				}while($transaction->savepoint);
				$pdo->rollback();
			}else{
				$transaction->commit();
				if($pdo->inTransaction()){
					$pdo->commit();
				}
			}
			unset($transaction);
			$pdo->setAttribute(\PDO::ATTR_AUTOCOMMIT, true);
			return $this;
		}
		public function table(string $name){
			return new Table($this, $name);
		}
		public function prepare(string $statement, array $options = []){
			$pdo = $this->pdo;
			if($pdo->getAttribute(\PDO::ATTR_DRIVER_NAME) == 'mysql'){
				$options[\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY] = true;
			}
			$query = $pdo->prepare($statement);
			if($query === false){
				throw $this->getError();
			}
			return $query;
		}
		public function exec(string $statement){
			$query = $this->prepare($statement);
			$count = 0;
			$query->execute();
			while($query->fetch() !== false){
				$count++;
			}
			if($count == 0){
				$count = $query->rowCount();
			}
			$query->closeCursor();
			return $count;
		}
		public function query(string $statement, int $mode = null, ...$args){
			$query = $this->prepare($statement);
			if(!is_null($mode)){
				$query->setFetchMode($mode, ...$args);
			}
			$query->execute();
			return $query;
		}
		public function quote(...$args){
			return $this->pdo->quote(...$args);
		}
		public function beginTransaction(...$args){
			return $this->pdo->beginTransaction(...$args);
		}
		public function setAttribute(...$args){
			return $this->pdo->setAttribute(...$args);
		}
		public function getAttribute(...$args){
			return $this->pdo->getAttribute(...$args);
		}
		public function lastInsertId(...$args){
			return $this->pdo->lastInsertId(...$args);
		}
		public function getError(){
			$error = $this->pdo->errorInfo();
			return new \Exception($error[2], $error[1]);
		}
	}
?>