<?php

/**
 * Class: ThinBuilder
 * @author Eugene Sukhodolskiy <eugene.sukhodolskiy@gmail.com>
 * @version 0.1
 * Date: 22.01.2020
 * Update At: 18.08.2022
 */

namespace Fury\Modules\ThinBuilder;

class ThinBuilder implements ThinBuilderInterface{

	use ThinBuilderProcessing;

	public function query(String $sql, String $fetch_func = '', Int $fetch_func_param = NULL){
		if($this -> driver){
			$this -> driver -> event_ready_sql($sql);
		}

		if($this -> gen_sql_only) {
			$this -> gen_sql_only = false;
			return $sql;
		}

		$response = $this -> pdo -> query($sql);
		if(!$response) {
			throw new \PDOException("Query execution failed: {$sql}");
		}
		$result = $fetch_func ? $response -> $fetch_func($fetch_func_param) : $response;

		if($this -> history_enabled){
			$this -> history -> add($sql, $result);
		}

		if($this -> driver){
			$this -> driver -> event_query($sql, $result);
		}

		return $result;
	}

	public function prepare(String $sql) {
		return $this -> pdo -> prepare($sql);
	}

	public function select(String $tablename, $fields = [], $where = [], $order_fields = [], String $order_sort = 'DESC', $limit = []){
		$this -> validate_identifier($tablename);
		list($fields_sql, $where_sql, $order_sql, $limit_sql, $params) = $this -> select_data_preprocessing($fields, $where, $order_fields, $limit);

		if($order_sql != ''){
			$order_sql .= " {$order_sort}";
		}

		$sql = "SELECT {$fields_sql} FROM `{$tablename}` {$where_sql} {$order_sql} {$limit_sql}";

		if($this -> driver){
			$this -> driver -> event_ready_sql($sql);
		}

		if($this -> gen_sql_only) {
			$this -> gen_sql_only = false;
			return $sql;
		}

		$stmt = $this -> pdo -> prepare($sql);
		$stmt -> execute($params);
		$result = $stmt -> fetchAll(\PDO::FETCH_ASSOC);

		if($this -> history_enabled){
			$this -> history -> add($sql, $result);
		}

		if($this -> driver){
			$this -> driver -> event_query($sql, $result);
		}

		return $result;
	}

	public function insert(String $tablename, Array $data){
		$this -> validate_identifier($tablename);
		$fields = array_keys($data);
		foreach ($fields as $f) {
			$this -> validate_identifier($f);
		}
		$placeholders = array_fill(0, count($fields), '?');

		$sql = "INSERT INTO `{$tablename}` (`" . implode('`,`', $fields) . "`) VALUES (" . implode(',', $placeholders) . ")";

		if($this -> driver){
			$this -> driver -> event_ready_sql($sql);
		}

		if($this -> gen_sql_only) {
			$this -> gen_sql_only = false;
			return $sql;
		}

		$stmt = $this -> pdo -> prepare($sql);
		$stmt -> execute(array_values($data));
		$id = $this -> pdo -> lastInsertId();

		if($this -> history_enabled){
			$this -> history -> add($sql, $id);
		}

		if($this -> driver){
			$this -> driver -> event_query($sql, $id);
		}

		return $id;
	}

	public function update(String $tablename, Array $data, $where = []){
		$this -> validate_identifier($tablename);
		list($where_sql, $where_params) = $this -> where_processing($where);

		$set_sql = [];
		foreach (array_keys($data) as $field) {
			$this -> validate_identifier($field);
			$set_sql[] = "`{$field}` = ?";
		}

		$sql = "UPDATE `{$tablename}` SET " . implode(',', $set_sql) . " {$where_sql}";

		if($this -> driver){
			$this -> driver -> event_ready_sql($sql);
		}

		if($this -> gen_sql_only) {
			$this -> gen_sql_only = false;
			return $sql;
		}

		$stmt = $this -> pdo -> prepare($sql);
		$stmt -> execute(array_merge(array_values($data), $where_params));
		$row_count = $stmt -> rowCount();

		if($this -> driver){
			$this -> driver -> event_query($sql, $row_count);
		}

		return $row_count;
	}

	public function delete(String $tablename, $where = []){
		$this -> validate_identifier($tablename);
		list($where_sql, $where_params) = $this -> where_processing($where);

		$sql = "DELETE FROM `{$tablename}` {$where_sql}";

		if($this -> driver){
			$this -> driver -> event_ready_sql($sql);
		}

		if($this -> gen_sql_only) {
			$this -> gen_sql_only = false;
			return $sql;
		}

		$stmt = $this -> pdo -> prepare($sql);
		$stmt -> execute($where_params);
		$row_count = $stmt -> rowCount();

		if($this -> driver){
			$this -> driver -> event_query($sql, $row_count);
		}

		return $row_count;
	}

	public function drop(String $tablename){
		$this -> validate_identifier($tablename);
		$sql = "DROP TABLE `{$tablename}`";
		return $this -> query($sql);
	}

	public function truncate(String $tablename){
		$this -> validate_identifier($tablename);
		$sql = "TRUNCATE TABLE `{$tablename}`";
		return $this -> query($sql);
	}

	public function create_table(String $tablename, Array $fields, String $primary_key, $engine = 'InnoDB'){
		/* $fields = [
			'id' => [
				'type' => 'INT',
				'length' => 11,
				'default' => 'NOT NULL',
				'auto_increment' => true,
				'can_be_null' => true
			],
			'option_key' => [
				'type' => 'VARCHAR',
				'length' => 255,
				'default' => 'NOT NULL'
				'auto_increment' => false,
				'can_be_null' => false
			],
		] */

		$this -> validate_identifier($tablename);
		$this -> validate_identifier($primary_key);
		$this -> validate_identifier($engine);

		$fields_str_arr = [];
		foreach ($fields as $name => $options) {
			$this -> validate_identifier($name);
			$length = (isset($options['length']) and !is_null($options['length'])) ? "({$options['length']})" : '';

			if(isset($options['default'])){
				$default = ($options['default'] == 'NULL' or $options['default'] == 'CURRENT_TIMESTAMP') ? "DEFAULT {$options['default']}" : "DEFAULT '{$options['default']}'";
			}else{
				$default = '';
			}

			$auto_increment = (isset($options['auto_increment']) and $options['auto_increment']) ? 'AUTO_INCREMENT' : '';
			$can_be_null = (isset($options['can_be_null']) and $options['can_be_null']) ? 'NULL' : 'NOT NULL';

			$fields_str_arr[] = "`{$name}` {$options['type']}{$length} {$can_be_null} {$default} {$auto_increment}";
		}

		$fields_string = implode(', ', $fields_str_arr);
		$sql = "CREATE TABLE IF NOT EXISTS `{$tablename}` ({$fields_string}, PRIMARY KEY (`{$primary_key}`)) ENGINE = {$engine}";

		return $this -> query($sql);
	}

	public function table_fields(String $tablename){
		$this -> validate_identifier($tablename);

		$sql = "SHOW COLUMNS FROM `{$tablename}`";
		$result = $this -> query($sql, 'fetchAll', \PDO::FETCH_NUM);
		$fields = [];
		foreach ($result as $raw_field) {
			list($type, $length) = explode('(', $raw_field[1]);
			$fields[$raw_field[0]] = ['type' => $type];
			$length = intval($length);
			if($length){
				$fields[$raw_field[0]]['length'] = $length;
			}
		}

		return $fields;
	}

	public function tables(){
		$sql = 'SHOW TABLES';
		$result = $this -> query($sql, 'fetchAll', \PDO::FETCH_ASSOC);
		return array_map(function($val){
			$k = array_keys($val);
			return $val[$k[0]];
		}, $result);
	}

	public function count(String $tablename, $where = []){
		$this -> validate_identifier($tablename);
		list($where_sql, $where_params) = $this -> where_processing($where);
		$sql = "SELECT COUNT(*) FROM `{$tablename}` {$where_sql}";

		if($this -> driver){
			$this -> driver -> event_ready_sql($sql);
		}

		$stmt = $this -> pdo -> prepare($sql);
		$stmt -> execute($where_params);
		$result = $stmt -> fetch(\PDO::FETCH_NUM);
		$count = $result ? intval($result[0]) : 0;

		if($this -> driver){
			$this -> driver -> event_query($sql, $count);
		}

		return $count;
	}

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

	public function sql_for() {
		$this -> gen_sql_only = true;
		return $this;
	}
}
