<?php

//to do: note field changes
class database_record implements Iterator, JsonSerializable {
	
	private $attribute;
	private $changes;	// attributes that have changed
	private $keys;
	private $_database_engine;
	protected $requested_engine;
	private $__tablename = FALSE;
	protected $keyfields = [];
	protected $engine = 'auto';
	protected $_empty = FALSE;
	protected $__load = FALSE;
	private $_autoinc_field = FALSE;
	private $changed = FALSE;
	private $collection;

	public function __construct($collection = NULL) {
		$this->attribute = [];
		$this->changes = [];
		$this->keys = [];
		$this->empty(TRUE);
		$this->collection = $collection;
		if ($collection !== NULL) {
			$this->_init();
/*			if (!isset($collection->tablename)) {
				var_dump(debug_backtrace());
				var_dump($collection);
				exit;
			} */
			if (is_object($collection) && method_exists($collection, '__load')) {		// Called when a record is loaded
				$this->__load = [$collection, '__load'];
			}
		}
	}
	
	
	public function tablename() {
		if (!func_num_args()) {
			return empty($this->__tablename) ? FALSE : $this->__tablename;
		} else {
			$this->__tablename = func_get_arg(0);
			return $this;
		}
	}
	public function empty() {
		if (!func_num_args()) {
			return $this->_empty;
		} else {
			$this->_empty = func_get_arg(0);
			return $this;
		}
	}
	public function autoinc() {
		if (!func_num_args()) {
			return $this->_autoinc_field;
		} else {
			$this->_autoinc_field = func_get_arg(0);
			return $this;
		}
	}
	
	public function attribute() {
		return $this->attribute;
	}
	
	public function LoadFromSource($data, $changed = FALSE) {
		$this->changes = [];
		if ($changed) {
			$this->changed = FALSE;
			foreach($data as $key => $value) {
				$this->$key = $value;
			}
		} else {
			$this->attribute = $data;
		}
		$this->empty(FALSE);
		if ($this->__load !== FALSE) call_user_func_array($this->__load, $this);
//		var_dump($this->_database_engine);
		$this->keys = array_keys($this->attribute);
		// any serialized items - unserialize them
		if (!$changed) $this->changed = FALSE;
	}

	#[\ReturnTypeWillChange]
	public function rewind() {
		reset($this->keys);
    }

	#[\ReturnTypeWillChange]
    public function current() {
		$position = current($this->keys);
		return $this->attribute[$position];
    }

	#[\ReturnTypeWillChange]
    public function key() {
		return current($this->keys);
    }

	#[\ReturnTypeWillChange]
    public function next() {
		return next($this->keys);
    }

	#[\ReturnTypeWillChange]
    public function valid() {
		$position = current($this->keys);
		if (is_int($position) || is_string($position)) {
			return array_key_exists($position, $this->attribute);
		} else {
			return isset($this->attribute[$position]);
		}
    }
	
	public function __get($name) {
		if (isset($this->attribute[$name])) {
			return $this->attribute[$name];
		} else {
			return NULL;
		}
	}
	
	// what fields have changed
	public function ChangedFields() {
		return array_keys($this->changes);
	}
	
	public function __set($name, $value) {
		$existing = isset($this->attribute[$name]);
		if ($existing) {
			if ($this->attribute[$name] === $value) return;
			if (is_numeric($value) && $value == intval($this->attribute[$name])) return;
		}	
		$this->attribute[$name] = $value;
		$this->changed = TRUE;
		if (substr($name, 0, 2) != '__') $this->changes[$name] = 1;
		if (!$existing) $this->keys[] = $name;
		// send message on value changed?
	}

	public function __isset($name) {
		return isset($this->attribute[$name]);
	}

	public function __unset($name) {
		if (isset($this->attribute[$name])) {
			unset($this->attribute[$name]);
			// send message on value changed?
			$this->keys = array_keys($this->attribute);
		}
	}
	
	public function __toString() {
		return json_encode($this->attribute);
	}

	public function __debugInfo() {
		$x = $this->attribute;
		$x['tablename'] = $this->tablename();
		$x['empty'] = $this->_empty;
		$x['changed'] = $this->changed;
		if (empty($this->_database_engine)) {
			$x['engine'] = 'none';
		} else {
			$x['engine'] = get_class($this->_database_engine);
		}
		if (empty($this->collection)) {
			$x['collection'] = 'empty';
		} else {
			$x['collection'] = $this->collection;
		}
		return $x;
	}

	public function __serialize() {
        $x = $this->attribute;
		$x['empty'] = $this->_empty;
		return $x;
	}
	
	#[\ReturnTypeWillChange]
	public function jsonSerialize() {
        return $this->__serialize();
    }
    
    public function AsArray() {
        return $this->attribute;
    }
	
/*	function _CheckKeys() {
		if (is_string($this->keyfields)) {
			$keys = [$this->keyfields];
		} elseif (is_array($this->keyfields)) {
			$keys = $this->keyfields;
		} else {
			$keys = [];
		}
		$tablename = $this->tablename();
		if (!count($keys) && $tablename) {		// no keys defined - retrieve from database
			if (!isset($_SESSION[$tablename])) {
				$fields = $this->_database_engine->LoadPrimaryKey($tablename);
				asort($fields, SORT_NUMERIC);
				$_SESSION[$tablename] = array_values($fields);
			}
			$this->keyfields = $_SESSION[$tablename];
		}
	} */
	
	public function Clear() {
		$this->attribute = [];
		$this->keys = [];
		$this->empty(TRUE);
	}
	private function _displayEntry($entry) {
		$function = $entry['function'];
		if (!empty($entry['class'])) $function = $entry['class'] . '::' . $function;
		echo $function . " [{$entry['file']}:{$entry['line']}]<br>\n";
	}
	
	public function dump() {
		$bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
		foreach($bt as $bt_item) {
			echo $this->_displayEntry($bt_item);
		}
	}
	private function _init() {
		if (is_a($this->collection, 'database_collection')) {
			$this->tablename($this->collection->tablename());
//			$this->keyfields = $this->collection->keyfields;
			$requested_engine = $this->collection->Engine();
//			$this->_database_engine = GetEngine($requested_engine);
			$this->_database_engine = $this->collection->Parent();		// 2024
		} elseif (is_a($this->collection, 'database_model')) {
			$tablename = $this->collection->tablename();
			$this->tablename($tablename);
//			$this->tablename = $this->collection->InheritTable($this->tablename, $this->keyfields);
			$requested_engine = $this->collection->Engine();
			$this->_database_engine = GetEngine($requested_engine);
		} elseif (is_a($this->collection, 'moduleMain')) {
			$requested_engine = $this->collection->Engine();
			$this->_database_engine = GetEngine($requested_engine);
		} elseif ($this->collection === FALSE) {
			return;
			$this->dump();
			die("\nUndefined database parent (collection) property");
		} else {
			die("\nunsupported parent object: " . get_class($this->collection));
		}
	}
	
	public function SaveRecord($key = NULL) {
		if (empty($this->_database_engine)) $this->_init();
		if ($this->_empty) {
			return $this->_database_engine->InsertRecord($this);
			//to do: if autoinc field - get inserted value
		} else {
			return $this->_database_engine->UpdateRecord($this, $key);
		}
	}

	public function DeleteRecord($key = NULL) {
		if (empty($this->_database_engine)) $this->_init();
		return $this->_database_engine->DeleteRecord($this, $key);
	}
	
	public function err() {
		return [	'errnum' => $this->_database_engine->errnum,
					'errmsg' => $this->_database_engine->errmsg,
//                    'sql' => $this->_database_engine->lastsql,
				];
	}
	
	// debug only:
	public function DatabaseEngine() {
		if (func_get_args() > 0) {
			$this->_database_engine = func_get_arg(0);
			return $this;
		}
		if (empty($this->_database_engine)) $this->_init();
		return $this->_database_engine;
	}
}

class database_collection implements ArrayAccess, Iterator, Countable {
	
	private $pointer;
	private $attribute;
	private $_database_engine;
	private $requested_engine;
	private $__tablename;
	protected $_parent;
	
	public function __construct($database_model = NULL) {
		$this->attribute = [];
		$this->pointer = 0;
		if ($database_model !== NULL && is_object($database_model)) {
			if (method_exists($database_model, 'tablename')) {
				$tablename = $database_model->tablename();
				$this->tablename($tablename);
				$this->requested_engine = $database_model->Engine();
				$this->_database_engine = GetEngine($this->requested_engine); 
				
/*			}
			if (method_exists($database_model, 'InheritTable')) {
				$tablename = $this->tablename();
				$database_model->InheritTable($tablename, $this->keyfields);
				$this->requested_engine = $database_model->Engine();
				$this->_database_engine = GetEngine($this->requested_engine); */
			} else {
				$this->requested_engine = 'auto';		// modulemain?
				$this->_database_engine = GetEngine($this->requested_engine);
			}
//			var_dump('datarecord', $this->_database_engine );
		}
	}

	public function Parent() {
		if (func_num_args()) {
			$this->_parent = func_get_arg(0);
			return $this;
		}
		return $this->_parent;
	}

	public function Engine() {
		return $this->requested_engine;
	}
	
	#[\ReturnTypeWillChange]
	public function count() {
		return count($this->attribute);
	}

	#[\ReturnTypeWillChange]
	public function rewind() {
		$this->pointer = 0;
    }

	#[\ReturnTypeWillChange]
    public function current() {
		return $this->attribute[$this->pointer];
    }

	#[\ReturnTypeWillChange]
    public function key() {
		return $this->pointer;
    }

	#[\ReturnTypeWillChange]
    public function next() {
		$this->pointer++;
    }

	#[\ReturnTypeWillChange]
    public function valid() {
        return isset($this->attribute[$this->pointer]);
    }
	
	#[\ReturnTypeWillChange]
	public function first() {		// should be from database trait instead?		Returns NULL if no first record
		if (count($this->attribute)) {
			return $this->attribute[0];
		}
		return NULL;
	}
	
	#[\ReturnTypeWillChange]
	public function offsetExists($offset) {
		return isset($this->attribute[$offset]);
	}
	
	#[\ReturnTypeWillChange]
	public function offsetGet($offset) {
		return isset($this->attribute[$offset]) ? $this->attribute[$offset] : NULL;
	}
	
	#[\ReturnTypeWillChange]
	public function offsetSet($offset, $value) {
        if (is_null($offset)) {
            $this->attribute[] = $value;
        } else {
            $this->attribute[$offset] = $value;
        }
	}
	#[\ReturnTypeWillChange]
	public function offsetUnset($offset) {
		unset($this->attribute[$offset]);
	}
	
	public function Add($data) {
//		$node = new database_record(NULL);
		$node = new database_record($this);
		$node->LoadFromSource($data);
		$node->tablename($this->tablename());
		$this->attribute[] = $node;
		return $node;
	}
	
	public function tablename() {
		if (func_num_args()) {
			$this->__tablename = func_get_arg(0);
			return $this;
		}
		return $this->__tablename;
	}

}
