<?php
if($_CPHP !== true) { die(); }

abstract class CPHPDatabaseRecordClass extends CPHPBaseClass
{
	public $fill_query = "";
	public $verify_query = "";
	public $table_name = "";
	public $query_cache = 60;
	
	public $prototype = array();
	public $prototype_render = array();	
	public $uData = array();
	
	public $sId = 0;
	
	public function __construct($uDataSource, $uCommunityId = 0)
	{
		$this->ConstructDataset($uDataSource, $uCommunityId);
	}
	
	public function ConstructDataset($uDataSource, $uCommunityId = 0)
	{
		$bind_datasets = true;
		
		if(is_numeric($uDataSource))
		{
			if($uDataSource != 0)
			{
				if(!empty($this->fill_query))
				{
					$this->sId = (is_numeric($uDataSource)) ? $uDataSource : 0;
					
					$query = sprintf($this->fill_query, $uDataSource);
					if($result = mysql_query_cached($query, $this->query_cache))
					{
						$uDataSource = $result->data[0];
					}
					else
					{
						$classname = get_class($this);
						throw new NotFoundException("Could not locate {$classname} {$uDataSource} in database.");
					}
				}
				else
				{
					$classname = get_class($this);
					throw new PrototypeException("No fill query defined for {$classname} class.");
				}
			}
			else
			{
				$bind_datasets = false;
			}
		}
		elseif(is_object($uDataSource))
		{
			if(isset($uDataSource->data[0]))
			{
				$uDataSource = $uDataSource->data[0];
			}
			else
			{
				throw new NotFoundException("No result set present in object.");
			}
		}
		elseif(is_array($uDataSource))
		{
			if(isset($uDataSource[0]))
			{
				$uDataSource = $uDataSource[0];
			}
		}
		else
		{
			$classname = get_class($this);
			throw new ConstructorException("Invalid type passed on to constructor for object of type {$classname}.");
		}
		
		if($bind_datasets === true)
		{
			$this->sId = (is_numeric($uDataSource['Id'])) ? $uDataSource['Id'] : 0;
			
			$this->uData = $uDataSource;
			
			foreach($this->prototype as $type => $dataset)
			{
				$this->BindDataset($type, $dataset);
			}
			
			$this->sFound = true;
		}
		else
		{
			$this->sFound = false;
		}
		
		if(!empty($uCommunityId) && !empty($this->sCommunityId))
		{
			$sCommunityId = (is_numeric($uCommunityId)) ? $uCommunityId : 0;
			
			if($sCommunityId != $this->sCommunity->sId)
			{
				$classname = get_class($this);
				throw new OwnershipException("{$classname} {$this->sId} does not belong to Community {$sCommunityId}.");
			}
		}
	}
	
	public function BindDataset($type, $dataset)
	{
		global $cphp_class_map;
		
		if(is_array($dataset))
		{
			foreach($dataset as $variable_name => $column_name) 
			{
				$original_value = $this->uData[$column_name];
				
				switch($type)
				{
					case "string":
						$value = htmlspecialchars(stripslashes($original_value));
						$variable_type = CPHP_VARIABLE_SAFE;
						break;
					case "html":
						$value = filter_html(stripslashes($original_value));
						$variable_type = CPHP_VARIABLE_SAFE;
						break;
					case "simplehtml":
						$value = filter_html_strict(stripslashes($original_value));
						$variable_type = CPHP_VARIABLE_SAFE;
						break;
					case "nl2br":
						$value = nl2br(htmlspecialchars(stripslashes($original_value)), false);
						$variable_type = CPHP_VARIABLE_SAFE;
						break;
					case "numeric":
						$value = (is_numeric($original_value)) ? $original_value : 0;
						$variable_type = CPHP_VARIABLE_SAFE;
						break;
					case "timestamp":
						$value = unix_from_mysql($original_value);
						$variable_type = CPHP_VARIABLE_SAFE;
						break;
					case "boolean":
						$value = (empty($original_value)) ? false : true;
						$variable_type = CPHP_VARIABLE_SAFE;
						break;
					case "user":
						$value = new User($original_value);
						$variable_type = CPHP_VARIABLE_SAFE;
						break;
					case "none":
						$value = $original_value;
						$variable_type = CPHP_VARIABLE_UNSAFE;
						break;
					default:
						$found = false;
						foreach($cphp_class_map as $class_type => $class_name)
						{
							if($type == $class_type)
							{
								$value = new $class_name($original_value);
								$variable_type = CPHP_VARIABLE_SAFE;
								$found = true;
							}
						}
						
						if($found == false)
						{
							$classname = get_class($this);
							throw new Exception("Cannot determine type of dataset ({$type}) passed on to {$classname}.BindDataset."); 
							break;
						}
				}
				
				if($variable_type == CPHP_VARIABLE_SAFE)
				{
					$variable_name_safe = "s" . $variable_name;
					$this->$variable_name_safe = $value;
				}
				
				$variable_name_unsafe = "u" . $variable_name;
				$this->$variable_name_unsafe = $original_value;
			}
		}
		else
		{
			$classname = get_class($this);
			throw new Exception("Invalid dataset passed on to {$classname}.BindDataset."); 
		}
	}
	
	public function DoRenderInternalTemplate()
	{
		if(!empty($this->render_template))
		{
			$strings = array();
			foreach($this->prototype_render as $template_var => $object_var)
			{
				$variable_name = "s" . $object_var;
				$strings[$template_var] = $this->$variable_name;
			}
			return $this->DoRenderTemplate($this->render_template, $strings);
		}
		else
		{
			$classname = get_class($this);
			throw new Exception("Cannot render template: no template defined for {$classname} class.");
		}
	}
	
	public function InsertIntoDatabase()
	{
		if(!empty($this->verify_query))
		{
			if($this->sId == 0)
			{
				$insert_mode = CPHP_INSERTMODE_INSERT;
			}
			else
			{
				$query = sprintf($this->verify_query, $this->sId);
				if($result = mysql_query_cached($query))
				{
					$insert_mode = CPHP_INSERTMODE_UPDATE;
				}
				else
				{
					$insert_mode = CPHP_INSERTMODE_INSERT;
				}
			}
			
			$element_list = array();
			
			foreach($this->prototype as $type_key => $type_value)
			{
				foreach($type_value as $element_key => $element_value)
				{
					switch($type_key)
					{
						case "none":
						case "numeric":
						case "boolean":
						case "timestamp":
						case "string":
							$element_list[$element_value] = array(
								'key'	=> $element_key,
								'type'	=> $type_key
							);
							break;
						default:
							break;
					}
				}
			}
			
			$sKeyList = array();
			$sValueList = array();
			
			foreach($element_list as $sKey => $value)
			{				
				$variable_name_safe = "s" . $value['key'];
				$variable_name_unsafe = "u" . $value['key'];
				
				if(isset($this->$variable_name_safe) || isset($this->$variable_name_unsafe))
				{
					switch($value['type'])
					{
						case "none":
							$sFinalValue = mysql_real_escape_string($this->$variable_name_unsafe);
							break;
						case "numeric":
							$number = (isset($this->$variable_name_unsafe)) ? $this->$variable_name_unsafe : $this->$variable_name_safe;
							$sFinalValue = (is_numeric($number)) ? $number : 0;
							break;
						case "boolean":
							$bool = (isset($this->$variable_name_unsafe)) ? $this->$variable_name_unsafe : $this->$variable_name_safe;
							$sFinalValue = ($bool) ? "1" : "0";
							break;
						case "timestamp":
							$sFinalValue = (isset($this->$variable_name_safe)) ? mysql_from_unix($this->$variable_name_safe) : mysql_from_unix(unix_from_local($this->$variable_name_unsafe));
							break;
						case "string":
							$sFinalValue = (isset($this->$variable_name_unsafe)) ? mysql_real_escape_string($this->$variable_name_unsafe) : mysql_real_escape_string($this->$variable_name_safe);
							break;
						case "default":
							$sFinalValue = mysql_real_escape_string($this->$variable_name_unsafe);
							break;
					}
					
					$sFinalValue = "'{$sFinalValue}'";
					$sKey = "`{$sKey}`";
					
					$sKeyList[] = $sKey;
					$sValueList[] = $sFinalValue;
				}
				else
				{
					$classname = get_class($this);
					throw new Exception("Database insertion failed: prototype property {$value['key']} not found in object of type {$classname}.");
				}
			}
			
			
			if($insert_mode == CPHP_INSERTMODE_INSERT)
			{
				$sQueryKeys = implode(", ", $sKeyList);
				$sQueryValues = implode(", ", $sValueList);
				$query = "INSERT INTO {$this->table_name} ({$sQueryKeys}) VALUES ({$sQueryValues})";
			}
			elseif($insert_mode == CPHP_INSERTMODE_UPDATE)
			{
				$sKeyValueList = array();
				
				for($i = 0; $i < count($sKeyList); $i++)
				{
					$sKey = $sKeyList[$i];
					$sValue = $sValueList[$i];
					$sKeyValueList[] = "{$sKey} = {$sValue}";
				}
				
				$sQueryKeysValues = implode(", ", $sKeyValueList);
				$query = "UPDATE {$this->table_name} SET {$sQueryKeysValues} WHERE `Id` = '{$this->sId}'";
			}
			
			if($result = mysql_query($query))
			{
				if($insert_mode == CPHP_INSERTMODE_INSERT)
				{
					$this->sId = mysql_insert_id();
				}
				
				return $result;
			}
			else
			{
				$classname = get_class($this);
				throw new DatabaseException("Database insertion query failed in object of type {$classname}. Error message: " . mysql_error());
			}
		}
		else
		{
			$classname = get_class($this);
			throw new Exception("No verification query defined for {$classname} class.");
		}
	}
	
	public function RetrieveChildren($type, $field)
	{
		if(!isset($cphp_class_map[$type]))
		{
			$classname = get_class($this);
			throw new NotFoundException("Non-existent 'type' argument passed on to {$classname}.RetrieveChildren function.");
		}
		
		$parent_type = get_parent_class($cphp_class_map[$type]);
		if($parent_type !== "CPHPDatabaseRecordClass")
		{
			$parent_type = ($parent_type === false) ? "NONE" : $parent_type;
			$classname = get_class($this);
			throw new TypeException("{$classname}.RetrieveChildren expected 'type' argument of parent-type CPHPDatabaseRecordClass, but got {$parent_type} instead.");
		}
		
		$query = "";
	}
	
	public function PurgeCache()
	{
		$query = sprintf($this->fill_query, $this->sId);
		$key = md5($query) . md5($query . "x");
		mc_delete($key);
	}
	
	public function RenderTemplate()
	{
		return $this->DoRenderInternalTemplate();
	}
}
?>
