<?php
namespace FileRun\Utils;

class DP {
	public string $table;
	static array $instances = [];

	static function factory($table) {
		if (!isset(self::$instances[$table])) {
			self::$instances[$table] = new self;
			self::$instances[$table]->table = $table;
		}
		return self::$instances[$table];
	}
	function q($v) {
		global $db;
		return $db->quote($v);
	}
	function update(array $recordData, $searchCriteria = [], $autoQuotes = true): bool {
		global $db;
		if (!$recordData) {return false;}
		$searchQueryPart = DPUtils::getSearchQueryPart([$searchCriteria]);
		$values = DPUtils::getValuesString($recordData, $autoQuotes);
		return (boolean) $db->query('UPDATE `'.$this->table.'` SET '.$values.($searchQueryPart?' WHERE '.$searchQueryPart:''));
	}
	function updateById(array $recordData, $id): bool {
		if (!is_numeric($id)) {return false;}
		return $this->update($recordData, ['id', '=', $this->q($id)]);
	}
	function debug(bool $state = true): static {
		global $db;
		$db->debug = $state;
		return $this;
	}
	function query($sql) {
		global $db;
		return $db->query($sql);
	}
	function delete(array $searchCriteria): bool {
		global $db;
		$searchQueryPart = DPUtils::getSearchQueryPart([$searchCriteria]);
		$rs = $db->query('DELETE FROM '.$this->table.' WHERE '.$searchQueryPart);
		if (!$rs) {return false;}
		return ($rs->rowCount() > 0);
	}
	function deleteById($id, $columnName = 'id'): bool {
		if (!is_numeric($id)) {return false;}
		return $this->delete([$columnName, '=', $this->q($id)]);
	}
	function insert(array $recordData, $autoQuotes = true, $ignore = false): bool {
		global $db;
		$q = 'INSERT';
		if ($ignore) {
			$q .= ' IGNORE';
		}
		$q .= ' INTO `'.$this->table.'` SET '.DPUtils::getValuesString($recordData, $autoQuotes);
		return $db->query($q) !== false;
	}
	function lastInsertId() {
		global $db;
		return $db->lastInsertId();
	}
	function select($fields = '*', $searchCriteria = false, $orderCriteria = [], $limit = false, $offset = 0, $countWithoutLimit = false, $groupBy = false) {
		global $db;
		$query = $this->getQuery($fields, $searchCriteria, $orderCriteria, $limit, $offset, $countWithoutLimit, $groupBy);
		if ($limit === 1) {
			return $db->GetRow($query);
		}
		return $db->GetAll($query);
	}
	function getQuery($fields, $searchCriteria = [], $orderCriteria = [], $limit = false, $offset = 0, $countWithoutLimit = false, $groupBy = false) {
		//todo: move to DPUtils
		$searchCondition = '';
		if (is_array($fields)) {
			$qSelect = implode(',', $fields);
		} else if ($fields && is_string($fields)) {
			$qSelect = $fields;
		} else {
			$qSelect = '*';
		}
		if ($countWithoutLimit) {
			$qSelect = 'SQL_CALC_FOUND_ROWS '.$qSelect;
		}
		$searchQueryPart = false;
		if ($searchCriteria) {
			$searchCondition .= 'WHERE ';
			$searchQueryPart = DPUtils::getSearchQueryPart([$searchCriteria]);
		}
		if ($searchQueryPart) {
			$searchCondition .= $searchQueryPart;
		} else {
			$searchCondition = '';
		}
		if ($groupBy) {
			$searchCondition .= ' GROUP BY '.$groupBy;
		}
		if ($orderCriteria) {
			if (is_array($orderCriteria)) {
				$tmp = '';
				$i = 0;
				foreach ($orderCriteria as $key => $val) {
					$val = strtoupper($val);
					if ($val != 'ASC' && $val != 'DESC') {
						continue;
					}
					if ($i) {
						$tmp .= ', ';
					}
					$tmp .= $key . ' ' . $val;
					$i++;
				}
				$orderCriteria = $tmp;
			}
			if ($orderCriteria) {
				$searchCondition .= ' ORDER BY '.$orderCriteria;
			}
		}
		if ($limit) {
			$searchCondition .= ' LIMIT '.(int) $offset.', '.(int) $limit;
		}
		return 'SELECT '.$qSelect.' FROM '.$this->table.' '.$searchCondition;
	}
	function selectOne($fields = '*', $searchCriteria = false, $orderCriteria = []) {
		return $this->select($fields, $searchCriteria, $orderCriteria, 1, 0);
	}
	function selectOneCol($field, $searchCriteria = false, $orderCriteria = []) {
		$rs = $this->selectOne($field, $searchCriteria, $orderCriteria);
		if (!$rs) {return false;}
		return reset($rs);
	}
	function selectColumn($field = 'id', $searchCriteria = false, $orderCriteria = [], $limit = false, $offset = 0, $countWithoutLimit = false) {
		$rs = $this->select($field, $searchCriteria, $orderCriteria, $limit,  $offset, $countWithoutLimit);
		if ($rs === false) {return false;}
		if ($rs === []) {return [];}
		return array_column($rs, $field);
	}
	function getById($data, $id) {
		if (!is_numeric($id)) {return false;}
		$where = ['id', '=', $this->q($id)];
		if (is_string($data) && $data !== '*') {
			return $this->selectOneCol($data, $where);
		}
		return $this->selectOne($data, $where);
	}
	function getByName($data, $name) {
		$where = ['name', '=', $this->q($name)];
		if (is_string($data) && $data !== '*') {
			return $this->selectOneCol($data, $where);
		}
		return $this->selectOne($data, $where);
	}

}

class DPUtils {

	static $sql;
	static $lastDepth;

	static function getValuesString($recordData, $autoQuotes = true): string {
		global $db;
		$values = [];
		foreach ($recordData as $key => $val) {
			if ($autoQuotes) {
				if ($val !== 'NOW()') {
					if (is_null($val) || $val === 'NULL') {
						$val = 'NULL';
					} else {
						$val = $db->quote($val);
					}
				}
			}
			$values[] = '`'.$key.'`='.$val;
		}
		return implode(',', $values);
	}

	static function getSearchQueryPart(array $searchCriteria) {
		$rs = self::arrayToSQL($searchCriteria);
		if (strpos($rs, 'OR') === 0) {
			$rs = substr($rs, 2);
		} else if (strpos($rs, 'AND') === 0) {
			$rs = substr($rs, 3);
		}
		return $rs;
	}

	static function arrayToSQL(array $array, $depth = 0): string {
		if ($depth === 0) {
			self::$sql = '';
		}
		$count = count($array);
		for ($i = 0 ; $i < $count ; $i++) {
			$a = $array[$i];
			$f = $a[0];
			if (is_array($a) && is_array($a['key'] ?? $f)) {
				$andOr = $f['type'] ?? ($f[3] ?? false);
				if ($andOr) {
					self::$sql .= $andOr;
				}
				self::arrayToSQL($a, $depth+1);
			} else {
				if (($depth-self::$lastDepth) > 0) {
					self::$sql .= str_repeat('(', ($depth-self::$lastDepth));
				}
				if ($i > 0) {
					$andOr = ($a['type'] ?? ($a[3] ?? false)) ?: 'AND';
					self::$sql .= ' '.$andOr.' ';
				}
				$value = $a['value'] ?? $a[2];
				self::$sql .= '('.($a['key'] ?? $f).' '.($a['operator'] ?? $a[1]).' '.$value.')';
			}
			if ((self::$lastDepth - $depth) > 0) {
				self::$sql .= str_repeat(')', (self::$lastDepth-$depth));
			}
			self::$lastDepth = $depth;
		}
		return self::$sql;
	}
}