<?php

namespace modules\database;

// table models will use something based off this trait
require_once __DIR__ . '/model/engine_generic.php';

trait traits {

	private $_database_engine = FALSE;
	private $_autoinc = FALSE;
	protected $requested_engine = 'auto';
	private $collection = FALSE;
	private $pointer = 0;
	public $lastsql;

	public function __construct() {
		$this->collection = FALSE;
		$this->lastsql = FALSE;
	}
	
	public function engine() {
		if (!func_num_args()) {
			return $this->requested_engine;
		} else {
			$old = $this->requested_engine;
			$this->requested_engine = func_get_arg(0);
			if ($old != $this->requested_engine); {
				$this->_database_engine = FALSE;
			}
			return $this;
		}
	}
	
	private function _database_traits_init() {
		if ($this->_database_engine === FALSE) {
			// no engine assigned to this database model, so assign one
			$this->_database_engine = GetEngine($this->requested_engine);
		}
	}
	
	public function DatabaseEngine() {
		if (func_num_args()) {
			$this->_database_engine = func_get_arg(0);
			return $this;
		}
		return $this->_database_engine;
	}

	public function autoinc() {
		if (func_num_args()) {
			$this->_autoinc = func_get_arg(0);
			return $this;
		}
		return $this->_autoinc;
	}
	
	public function query($sql) {
		$this->collection = FALSE;	// begin new query => clear out the old
		$params = func_get_args();
		$this->_database_traits_init();
		if (!empty($this->_database_engine)) {
			$filler = $this->_database_engine->_state();		// reset Where/Limit/OrderBy/GroupBy
			$this->lastsql = call_user_func_array([$this->_database_engine, 'QuerySQL'], $params);
		} else {
			$this->lastsql = '';
		}
		return $this;
	}

	public function callback(Callable $callback) {
		$tablename = $this->tablename();
		if ($this->lastsql === FALSE && !empty($tablename) ) $this->lastsql = "select * from `{$tablename}`";
		$sqlresult = $this->_database_engine->execute($this->lastsql);
		if (is_object($sqlresult)) {
			$this->_database_engine->callable($this, $sqlresult, $callback);
			$sqlresult->free();
		}
		return $this;
	}
	
	// "SELECT TOP (top_value)"
	public function limit() {
		$this->_database_traits_init();
		if (func_num_args()) {
			if (!empty($this->_database_engine)) $this->_database_engine->limit(func_get_arg(0));
			return $this;
		}
		return $this->_database_engine->limit();
	}

	public function tablename() {
		$this->_database_traits_init();
		if (func_num_args()) {
			if (!empty($this->_database_engine)) $this->_database_engine->tablename(func_get_arg(0));
			return $this;
		}
		return empty($this->_database_engine) ? '' : $this->_database_engine->tablename();
	}

	public function where() {
		$this->_database_traits_init();
		if (func_num_args()) {
			if (!is_object($this->_database_engine)) {
				header('Content-type: text/plain');
				$bt = debug_backtrace(0);
				foreach($bt as $bt_item) {
					echo $this->_displayEntry($bt_item);
				}
				$ver = phpversion();
				die("\nWhere: Database engine not specified - is php-mysql installed for PHP {$ver}?\n");
			}
			
			call_user_func_array([$this->_database_engine, 'where'], func_get_args());
			return $this;
		}
		return $this->_database_engine->where();
	}
	
	public function whereOr($param1, $param2) {		// takes at least 2 arguments
		$this->_database_traits_init();
		call_user_func_array([$this->_database_engine, 'whereOr'], func_get_args());
		return $this;
	}

	public function wherein($fieldname, $valuelist) {
		$this->_database_traits_init();
		$this->_database_engine->wherein($fieldname, $valuelist);
		return $this;
	}

	public function orderby() {
		$this->_database_traits_init();
		call_user_func_array([$this->_database_engine, 'orderby'], func_get_args());
		return $this;
	}

	public function groupby() {
		$this->_database_traits_init();
		call_user_func_array([$this->_database_engine, 'groupby'], func_get_args());
		return $this;
	}

	public function get() {
		$this->_database_traits_init();
		if (empty($this->_database_engine)) return NULL;
		$tablename = $this->tablename();
		if ($this->lastsql === FALSE && !empty($tablename) ) $this->lastsql = "select * from `{$tablename}`";
		$hook = $this->hook_query();
		if ($hook !== FALSE && hook_exists($hook)) {
			hook_execute($hook, $this);
		}
		$sqlresult = $this->_database_engine->execute($this->lastsql);
		$this->pointer = 0;
		if (is_object($sqlresult)) {
			$this->collection = $this->_database_engine->AsCollection($sqlresult, $this);
			$sqlresult->free();
			return $this->collection;
		}
		return NULL;
	}
	
	// execute a standard hook to extend the query, and also to format results
	public function hook_query() {
		$this->_database_traits_init();
		if (func_num_args()) {
			if (!empty($this->_database_engine)) $this->_database_engine->hook_query(func_get_arg(0));
			return $this;
		}
		return $this->_database_engine->hook_query();
	}

	public function UpdateSql_AddField($field) {
		$this->_database_traits_init();
		if (!empty($this->_database_engine)) $this->_database_engine->UpdateSql_AddField($field);
		return $this;
	}
	
	public function first() {
		if ($this->collection === FALSE) {
			$this->limit(1)->get();
		}
		$this->pointer = 0;
		if (!is_bool($this->collection) && count($this->collection) > $this->pointer) {
			return $this->collection[$this->pointer];
		} else {
			return new \database_record($this->collection);
		}
	}
	
    // defaults used only when a new record
	public function InsertOrUpdate($key, $payload, $default = NULL) {
		$this->_database_traits_init();	
		$state = $this->State();
		$tablename = $this->tablename();
//		if ($tablename == 'users_source') var_dump($key);
        if (is_array($key) && !count($key)) {
            $data = NULL;
        } else {
            $data = $this->query("select * from `{$tablename}`")->where($key)->first();
        }
		if ($data === NULL) $data = new \database_record($this);
		if ($this->autoinc()) $data->autoinc($this->autoinc());
		$data->tablename($tablename);
		if ($data->empty()) {	// not found - create record, but key must be an associative array
			$payload = $this->_array_join($key, $payload);
            if (!is_null($default)) $payload = $payload + $default;
			$data->LoadFromSource($payload);
			$data->empty(TRUE);
		} else {				// update existing record
			$data->LoadFromSource($payload, TRUE);
		}
		$data->SaveRecord($key);
		$this->State($state);
		return $data;
	}
	
	public function Delete($key) {
		$this->_database_traits_init();	
		$state = $this->State();
		$tablename = $this->tablename();
		$this->query("delete from `{$tablename}`")->where($key)->get();
		$this->State($state);
		return TRUE;
	}
	
	// $keys could be "fieldname=value and fieldname2=value"
	private function _array_join($keys, $payload) {
		if (!is_array($keys)) throw new \Exception('keys must be an array' .var_export($keys, 1));
		reset($keys);
		$x = key($keys);
		if (is_numeric($keys)) throw new \Exception('keys must be an associative array (fieldname => value)');
		return $keys + $payload;
	}

/*	protected function loadDefinition($tablename) {
	// load the current [internal] definition for the table
		$this->_database_traits_init();
		return $this->_database_engine->GetInternalTableDefinition($tablename);
	} */

	private function _displayEntry($entry) {
		$function = $entry['function'];
		if (!empty($entry['class'])) $function = $entry['class'] . '::' . $function;
		$arg = '';
		if (isset($entry['args'])) {
			foreach($entry['args'] as $item) {
				$arg .= ' ';
				if (is_object($item)) {
					$arg .= '[Object]';
				} elseif (is_array($item)) {
					$arg .= '[Array]';
				} else {
					$arg .= '"' . $item . '"';
				}
			}
		}
		echo $function . $arg . " [{$entry['file']}:{$entry['line']}]<br>\n";
	}
	
	public function DbErr($flags = 0) {
		$this->_database_traits_init();
		if ($flags) {
            $bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
            foreach($bt as $bt_item) {
                echo $this->_displayEntry($bt_item);
            }
			echo "\nLast Query: {$this->_database_engine->lastsql}";
		} elseif ($this->_database_engine->errnum) {
            $bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
            foreach($bt as $bt_item) {
                echo $this->_displayEntry($bt_item);
            }
			echo "\nLast Query: {$this->_database_engine->lastsql}";
			die("\nDatabase error: {$this->_database_engine->errmsg} [{$this->_database_engine->errnum}]\n");
		}
	}
	
	public function Database_Reopen() {
		$this->_database_traits_init();
		$this->_database_engine->close();
		$this->_database_engine->open();
	}
	
	private function State() {
		$res = call_user_func_array([$this->_database_engine, '_state'], func_get_args());
		return func_num_args() > 0 ? $this : $res;
	}
	
	public function debug() {
		$res = call_user_func_array([$this->_database_engine, 'debug'], func_get_args());
		return func_num_args() > 0 ? $this : $res;
	}

}

require_once __DIR__ . '/model/database_record.php';
