<?php
	namespace SQL {
		/**
		* Query class. Returned by \SQL::query()
		*
		* @class Query
		* @constructor
		*/
		class Query {
			private $query;
			private $sql;
			private $_result = false;
			public function __construct($sql, $source, $types=null, ...$args){
				$sql->queries[] = $this;
				$args = array_merge([$types], $args);
				$this->sql = $sql();
				$this->query = $this->sql->stmt_init();
				if(!$this->query->prepare($source)){
					throw new \RuntimeException($this->query->error);
				}
				if(!is_null($types)){
					if(!$this->query->bind_param(...\SQL::make_referenced($args))){
						throw new \RuntimeException("Unable to bind parameter {$this->query->error}");
					}
				}
			}
			public function __destruct(){
				if($this->_result){
					$this->_result = false;
					$this->query->free_result();
				}
				if($this->query){
					$this->query->close();
				}
				$sql->queries = array_diff($sql->queries, [$this]);
			}
			public function __invoke(){
				return $this->query;
			}
			public function execute(){
				if($this->query){
					$this->query->free_result();
					$this->_result = false;
					$this->query->reset();
					$r = $this->query->execute();
					$this->sql->commit();
					return $r;
				}else{
					return false;
				}
			}
			public function each_assoc(callable $fn){
				if($this->query){
					$r = $this->results;
					while($row = $r->fetch_assoc()){
						$fn($row);
					}
				}else{
					return false;
				}
			}
			public function each_num(callable $fn){
				if($this->query){
					$r = $this->results;
					while($row = $r->fetch_num()){
						$fn($row);
					}
				}else{
					return false;
				}
			}
			public function __get($name){
				switch($name){
					/**
					* Returns the mysqli::results object for the
					* query
					* 
					* @property results
					* @type {mysqli::results}
					* @public
					*/
					case 'results':
						if(!$this->_result && $this->query){
							$this->execute();
							$this->_result = $this->query->get_result();
							$this->query->close();
						}
						return $this->_result;
					break;
					/**
					* Returns an associative array of the query resulsts
					* 
					* @property assoc_results
					* @type {Array}
					* @public
					*/
					/**
					* Returns an associative array of the query resulsts
					* 
					* @property resulsts_assoc
					* @type {Array}
					* @public
					*/
					case 'assoc_results':case 'results_assoc':
						if($this->query){
							$a = [];
							$r = $this->results;
							while($row = $r->fetch_assoc()){
								array_push($a,$row);
							}
							return $a;
						}else{
							return false;
						}
					break;
					/**
					* Returns a numbered array of the query results
					* 
					* @property num_results
					* @type {Array}
					* @public
					*/
					/**
					* Returns a numbered array of the query results
					* 
					* @property resulsts_num
					* @type {Array}
					* @public
					*/
					case 'num_results':case 'results_num':
						if($this->query){
							$a = [];
							$r = $this->results;
							while($row = $r->fetch_num()){
								array_push($a,$row);
							}
							return $a;
						}else{
							return false;
						}
					break;
					case 'assoc_result':case 'result_assoc':
						if($this->query){
							$r = $this->results;
							return $r?$r->fetch_assoc():false;
						}else{
							return false;
						}
					break;
					case 'num_result':case 'result_num':
						if($this->query){
							$r = $this->results;
							return $r?$r->fetch_num():false;
						}else{
							return false;
						}
					break;
					case 'insert_id':
						return $this->sql->insert_id;
					break;
					case 'affected_rows':
						return $this->query->affected_rows;
					break;
				}
			}
		}
	}
?>