<?php

/**
 *
 * @author Peter Csubak (chuby)
 * @copyright Peter Csubak (chuby)
 */

/** @Target("property") */
class Attribute extends Annotation{
	public $name = "default";
	public $type; //<entityname>; string; int; boolean; float(==double); date; md5; datetime;
	public $length = "default";
	public $object = false;
}

/** @Target("property") */
class Id extends Annotation{
	public $hashBy = "nohash"; // ugyan úgy müxik, mint a meta select csak ez hashel, md5-el természetesen
}

/** @Target("property") */
class Select extends Annotation{
	public $type; //{<entityname>|array}
	public $tablename = "--default--";
	public $columns = "*"; //csak string, és array típust fogad el.
	public $where = "1";
	public $metaselect = false; //leírja, hogy az entitás hivatkozik a selectben saját adattagjaira, azaz, hogy meta-select-e a lekérdezés.
	public $onSet = null;
}

/** @Target("property") */
class Constraints extends Annotation{
	public $regex = "none";
	//igaz hogy ezzel mindent le lehet írni, de pl a szám típusokra ki lehet még találni párat.
	public $min = "unset";
	public $max = "unset";
}

/** @Target("class") */
class ReferencingFrom extends Annotation{
	public $value; // vagy tömb tehát {"ref1", "ref2", ... } vagy string "ref1"
}

/** @Target("property") */
class Nullable extends Annotation{}

/** @Target("class") */
class DisableAutoLoad extends Annotation{}

/** @Target("class") */
class Table extends Annotation{
	public $name = "default";
}

/** @Target("class") */
class PersistenceHandlerName extends Annotation{
	public $value = "default";
}

abstract class Entity implements DataBaseSerializable {

	private $oldElement;

	/**
	 * Lekérdezi a nem publikus adattagokat, ezzel oldva fel, hogy az automatikus
	 * foreach-es reflection nem támogatja minden attribútum feldolgozását.
	 * Ezzel a metódussal lehet megoldani, hogy kívülről ne kelljen használni 
	 * a __get __set metódusokat, hogy azok kihassanak az entitásra, és persze,
	 * hogy az entitás adattagjai ne legyenek publikusak. 
	 *
	 * @return array Az adott entitás privát és protected adattagjait adja vissza a foreach 
	 * által értelmezhető formában. (Azaz attribútum név => attribútum érték 
	 * formában.)
	 */
	private function getUnaccessableFields(){
		$res = array();
		$reflect = new ReflectionClass(get_class($this));
		$props = $reflect->getProperties();
		for ($i = 0; $i < count($props); $i++){
			$property = $props[$i];
			if ($property->isPrivate() || $property->isProtected()){
				$name = $property->getName();
				$res[$name] = $this->$name; 
			}
		}
		return $res;
	}

	/**
	 * Azt mondja meg, hogy az adott entitásra létezik-e referencia az 
	 * adatbázisban. Amennyiben a válasz igen, akkor az EntityHandler-nek 
	 * nem szabad engedélyeznie a módosítást, illetve a törlést, mert 
	 * elronthatja vele a referenciákat.
	 * 
	 * A referáltságot a ReferencingFrom annotációval lehet leírni, és itt a
	 * teszt csak azon osztályok példányaira fut, amiket a ReferencingFrom 
	 * annotációban megadtunk. (És természetesen csak azokra, amelyek objektum
	 * mezőkkel hivatkoznak erre a példányra, tehát a select típusú mezőket
	 * ez a metódus nem ellenőrzi. - A meta-select miatt nem is nagyon lehetne
	 * ezt megoldani.)
	 * 
	 * A metódusnak nincs visszatérési értéke, minthogy mindenképpen dobna 
	 * kivételeket, így ha van referencia, azt is egy PersistenceException-el
	 * fogja megmondani.
	 * 
	 * @throws PersistenceException Az összes feldolgozásközbeni hiba esetén, 
	 * illetve ha található az adatbázisban a megadott osztály(ok) példányaiban
	 * referencia erre a példányra, akkor 257-es hibakódú konzisztencia hiba 
	 * váltódik ki. (Ami természetesen még mindig PersistenceException típusú.)
	 */
	public function checkReferences(){
		$ref = new ReflectionAnnotatedClass(get_class($this));
		if ($ref->hasAnnotation("ReferencingFrom")){
			$revref = $ref->getAnnotation("ReferencingFrom");
			$id = null;
			foreach ($this->getUnaccessableFields() as $attr => $val){
				$refattr = new ReflectionAnnotatedProperty(get_class($this), $attr);
				if ($refattr->hasAnnotation("Id")){
					$id = $val;
					break;
				}
			}
			if ($id == null){
				throw new PersistenceException("Entitás vagy Entitás leíró hiba: A <<class>> nevű entitásban nincs definiálva azonosító, vagy egy előfordulásában nincs beállítva.", array("class" => get_class($this)), 219);
			}

			if (is_array($revref->value)){
				$arr = $revref->value;
				for ($i = 0; $i < count($arr); $i++){
					$vall = $arr[$i];
					
					$reflc = new ReflectionAnnotatedClass($vall);
					$refobj = $reflc->newInstance();
					$tname = "";
					if ($reflc->hasAnnotation("Table")){
						$tab = $reflc->getAnnotation("Table");
						if ($tab->name == "default"){
							$tname = strtolower(get_class($refobj))."s";
						}else{
							$tname = $tab->name;
						}
					}else{
						throw new PersistenceException("Entitás leíró hiba: A <<class>> nevű entitásra olyan ellenőrzött hivatkozás van meghatározva, amely nem egy másik tárolt entitásból történik, ez pedig nem ellenőrízhető automatikusan.", array("class" => get_class($this)), 220);
					}

					$cols = array();
					$cic = 0;
					if ($refobj instanceof Entity){
						foreach ($reflc->getProperties() as $prop){
							$pname = $prop->getName();
							$refprop = new ReflectionAnnotatedProperty(get_class($refobj), $pname);
							if ($refprop->hasAnnotation("Attribute")){
								$attrib = $refprop->getAnnotation("Attribute");
								if ($attrib->object && ($attrib->type == get_class($this))){
									if ($attrib->name == "default"){
										$cols[$cic++] = strtoupper(substr($pname, 0, 1)).substr($pname, 1);
									}else{
										$cols[$cic++] = $attrib->name;
									}
								}
							}
						}
					}else{
						throw new PersistenceException("Entitás leíró hiba: A <<class>> nevű entitásra olyan ellenőrzött hivatkozás van meghatározva, amely nem egy másik tárolt entitásból történik, ez pedig nem ellenőrízhető automatikusan.", array("class" => get_class($this)), 220);
					}

					$where = array();
					for ($j = 0; $j < count($cols); $j++){
						$where[$cols[$j]] = $id;
					}

					$res = $this->getPersistenceHandler()->selectArray($tname, $cols, $where);
					if (count($res) > 0){
						throw new PersistenceException("Konzisztencia hiba: Az entitásra hivatkozik egy másik persistens entitás az adatbázisban.", null, 257);
					}
				}
			}else{
				//elvileg müxik, ha a ez a sima megy akkor a másik is kell, hogy menjen (remélem :-) )
				$reflc = new ReflectionAnnotatedClass($revref->value);
				$refobj = $reflc->newInstance();
				$tname = "";
				if ($reflc->hasAnnotation("Table")){
					$tab = $reflc->getAnnotation("Table");
					if ($tab->name == "default"){
						$tname = strtolower(get_class($refobj))."s";
					}else{
						$tname = $tab->name;
					}
				}else{
					throw new PersistenceException("Entitás leíró hiba: A <<class>> nevű entitásra olyan ellenőrzött hivatkozás van meghatározva, amely nem egy másik tárolt entitásból történik, ez pedig nem ellenőrízhető automatikusan.", array("class" => get_class($this)), 220);
				}

				$cols = array();
				$cic = 0;
				if ($refobj instanceof Entity){
					foreach ($reflc->getProperties() as $prop){
						$pname = $prop->getName();
						$refprop = new ReflectionAnnotatedProperty(get_class($refobj), $pname);
						if ($refprop->hasAnnotation("Attribute")){
							$attrib = $refprop->getAnnotation("Attribute");
							if ($attrib->object && ($attrib->type == get_class($this))){
								if ($attrib->name == "default"){
									$cols[$cic++] = strtoupper(substr($pname, 0, 1)).substr($pname, 1);
								}else{
									$cols[$cic++] = $attrib->name;
								}
							}
						}
					}
				}else{
					throw new PersistenceException("Entitás leíró hiba: A <<class>> nevű entitásra olyan ellenőrzött hivatkozás van meghatározva, amely nem egy másik tárolt entitásból történik, ez pedig nem ellenőrízhető automatikusan.", array("class" => get_class($this)), 220);
				}

				$where = array();
				for ($j = 0; $j < count($cols); $j++){
					$where[$cols[$j]] = $id;
				}

				$res = $this->getPersistenceHandler()->selectArray($tname, $cols, $where);
				if (count($res) > 0){
					throw new PersistenceException("Konzisztencia hiba: Az entitásra hivatkozik egy másik persistens entitás az adatbázisban.", null, 257);
				}
			}
		}
	}

	/**
	 * Ellenőrzi az adott értékre, hogy megfelel-e a megszorításoknak, amiket 
	 * az annotációk előírnak rá. Értelem szerűen, igaz értéket ad vissza, ha 
	 * a megszorításnak megfelelő az érték, és hamissal, ha pedig nem.
	 *
	 * @param mixed $value Az érték amire ellenőrízni kell a megszorítást.
	 * @param Constraints $constraintsAnnotation A megszorítást leíró annotáció.
	 * @return boolean A megszorításnak megfelel-e az adott érték.
	 */
	private function checkConstraints($value, $constraintsAnnotation){
		if(is_numeric($value)){
			if ($constraintsAnnotation->min != "unset"){
				if (is_int($value)){
					if (((int) $value) < ((int) $constraintsAnnotation->min)){
						return false;
					}
				}elseif(is_float($value)){
					if (((double) $value) < ((double) $constraintsAnnotation->min)){
						return false;
					}
				}
			}
			if ($constraintsAnnotation->max != "unset"){
				if (is_int($value)){
					if (((int) $value) > ((int) $constraintsAnnotation->max)){
						return false;
					}
				}elseif (is_float($value)){
					if (((double) $value) > ((double) $constraintsAnnotation->max)){
						return false;
					}
				}
			}
			return true;
		}else{
			if ($constraintsAnnotation->regex == "none"){
				return true;
			}else{
				return (ereg($constraintsAnnotation->regex, $value) ? true : false);
			}
		}
	}

	/**
	 * Vissza adja a perzisztencia kezelőjét az adott entitásnak. Ennek 
	 * segítségével tud majd manipulálni szabályosan az adatbázison.
	 * Az entitás onnan tudja, hogy melyik perzisztencia kezelő az amelyikhez 
	 * maga is tartozik, hogy az a PersistenceHandlerName annotációval előírható 
	 * minden entitáson, ha pedig ez nincs megadva, akkor az alapértelmezett 
	 * perzisztencia kezelőt fogja lekérdezni.
	 * @return PersistenceHandler Az entitás perzisztencia kezelője.
	 */
	private function getPersistenceHandler(){
		$ref = new ReflectionAnnotatedClass(get_class($this));
		if ($ref->hasAnnotation("PersistenceHandlerName")){
			$ph = $ref->getAnnotation("PersistenceHandlerName");
			return PersistenceHandler::getInstance($ph->value);
		}else{
			return PersistenceHandler::getInstance();
		}
	}

	/**
	 * Az entitás adatbázisbeli táblájának a nevét lehet vele lekérdezni.
	 * Ha a Table annotációban szerepel a táblanév, akkor azt adja vissza a 
	 * prefix-el (ha van), ha pedig nem szerepel a név az annotációban, akkor a 
	 * szabályos elnevezési stílusban adja meg a tábla nevét: azaz a név az 
	 * osztálynévből úgy következik, hogy az összes betű kicsi, és a végén szerepel 
	 * egy s betű, mint a többesszám jele. (Természetesen ebben az esetben is 
	 * szerepel az prefix, ha be van állítva.)
	 *
	 * @return string Az entitás adatbázis beli táblaneve.
	 */
	public function getTableName(){
		$clss = get_class($this);
		$ref = new ReflectionAnnotatedClass($clss);
		$tname = strtolower(get_class($this))."s";
		if ($ref->hasAnnotation("Table")){
			$tab = $ref->getAnnotation("Table");
			if ($tab->name != "default"){
				$tname = $tab->name;
			}
		}
		return $this->getPersistenceHandler()->getPrefix().$tname;
	}

	/**
	 * A metaselect-ekben lévő meta adatokra való hivatkozásokat oldja fel, majd a 
	 * konkrét értékeket íra a where záradékba.
	 * Ha a where záradék string formában van megadva, akkor pusztán a stringben lévő
	 * egyezéseknél helyettesíti a megfelelő értékekkel a meta kifejezéseket.
	 * Ha viszont a where záradék egy tömb, akkor a meta kifejezések csak az értékekben
	 * íródnak felül. 
	 * 
	 * @param mixed $where A where záradék, eredeti formában
	 * @return mixed A where záradék, a meta adatok feloldása utáni formában
	 */
	private function extractMetaSelectWhere($where){
		if (is_array($where)){
			foreach ($where as $k => &$v){
				foreach ($this as $key => $value){
					if (!is_object($value)){
						$where[$k] = str_replace("<<".$key.">>", $value, $v);
					}
				}
			
				foreach ($this->getUnaccessableFields() as $key => $value){
					if (!is_object($value)){
						$where[$k] = str_replace("<<".$key.">>", $value, $v);
					}
				}
			}
		}else{
			foreach ($this as $key => $value){
				if (!is_object($value)){
					$where = str_replace("<<".$key.">>", $value, $where);
				}
			}
		
			foreach ($this->getUnaccessableFields() as $key => $value){
				if (!is_object($value)){
					$where = str_replace("<<".$key.">>", $value, $where);
				}
			}
		}
		
		return $where;
	}

	/**
	 * A select típusú adattagok feloldására szolgáló segédmetódus, tulajdonképpen a get része kéne,
	 * hogy legyen, de mivel nagyon hosszú, és komplikált (volt egésszen a perzisztencia kezelő 
	 * megjelenéséig :-) ), így külön ki lett helyezve.
	 * A Select annotációval lehet vezérelni, és kezeli a meta-selectet, ami azt jelenti, hogy a select
	 * where záradékában lehet hivatkozni az adott entitás egyes attribútumainak konkrét értékére,
	 * így elérve, hogy a select típus értékét ne az osztály, hanem az egyed tudja befolyásolni.
	 * Az annotációnak megfelelően, kétféle típust kezel az "array" illetve az objektumtípusút,
	 * ez utóbbi esetben a típus közvetlenül hivatkozik az entitás típusára. Az első esetben
	 * a select által visszaadott érték értelmezés nélkül kerül egy asszociatív tömbbe, a második esetben
	 * viszont a select nem foglalkozik azzal, hogy milyen mezőket kértünk, lekérdezi a hivatkozott 
	 * entity elsődleges kulcsát, és a csak azt a mezőt kérdezi le, majd példányosítja, és autoload-al
	 * betölti az összes lekérdezett elemet, majd egy tömbbe rakja és ezt is adja vissza.
	 *
	 * @param string $name Az entity azon attribútumának a neve, amibe a select eredménye kerül.
	 * @param ReflectionAnnotatedProperty $reflect Az attribútumhoz tartozó annotációk.
	 * @return array A select eredménye a megfelelő formában. (array esetben asszociatív tömbökben, objektum 
	 * típus esetében pedig egy tömb amiben az objektumok vannak)
	 * @throws PersistenceException entitás leírási hibák esetén 200, illetve 201-es hibakódú kivételeket 
	 * válthat ki.
	 */
	private function getSelectType($name, $reflect){
		$ann = $reflect->getAnnotation("Select");
		$where = $ann->where;
		if ($ann->metaselect){
			$where = $this->extractMetaSelectWhere($ann->where);
		}

		if ($ann->type == "array"){
			if (($ann->tablename == "--default--") || ($ann->tablename == "") || ($ann->tablename == null)){
				throw new PersistenceException("Hibás entitás leírás: A <<name>> nevű adattag ArraySelect típusú, viszont nincs beállítva a lekérdezés táblája.", array("name" => $name), 200);
			}

			if (is_array($ann->columns) || is_string($ann->columns)){
				return $this->getPersistenceHandler()->selectArray($ann->tablename, $ann->columns, $where);
			}else{
				throw new PersistenceException("Hibás entitás leírás: A <<name>> nevű mező Select típusú, viszont a mezők leírása hibás.", array("name" => $name), 201);
			}
		}else{
			$pershand = $this->getPersistenceHandler();
			//return $pershand->selectEntities(get_class($this), $where);
			return $pershand->selectEntities($ann->type, $where);
		}
	}
	
	/**
	 * Az entitás attibútumaiból összeszedi az Id automatikus hasheléséhez szükséges értékeket, és ezeket a 
	 * megfelelő módon össze is fűzi egy stringgé, és ezt adja vissza. Ezt használja majd fel a __get metódus
	 * a végleges hasheléshez. A metódus működése megegyezik a meta-select-hez használt megoldással.
	 * 
	 * @param string $hash A hashelés alapját leíró string, amiben a << és >>-közt leírt attribútumnevek 
	 * ki lesznek cserélve azok példány beli értékükre.
	 * @return string A hasheléshez előkészített string, amiben a megfelelő azonosítók már helyettesítve vannak
	 * a megfelelő értékekkel.
	 */
	private function extractHashBy($hash){
		foreach ($this as $key => $value){
			if (!is_object($value)){
				$hash = str_replace("<<".$key.">>", $value, $hash);
			}
		}
		
		foreach ($this->getUnaccessableFields() as $key => $value){
			if (!is_object($value)){ 
				$hash = str_replace("<<".$key.">>", $value, $hash);
			}
		}
		return $hash;
	}

	/**
	 * Az entity attribútumait kiszolgáló metódus, a szabvány __get magic method polimorf verziója
	 * az entitásokban az attribútumok privátak kellenek hogy legyenek, és így garantált, hogy a 
	 * lekérdezésük esetén mindenképpen keresztülhalad a __get illetve __set metóduson. 
	 * A __get metódus beállítja az objektum típusú mezőket, illetve a select típusú mezőket is, 
	 * a lekérdezésükkor, így egyrészt megmarad az aktualitásuk, másrészt pedig az autoload miatt
	 * létrejövő esetleges végtelen rekurziót is megoldja, minthogy egy-egy objektum típusú adatta
	 * csak akkor töltődik be ha arra ténylegesen szükség is van. (Amíg nem kérjük le, addig garantálatan
	 * csak az azonosítója lesz a memóriában)
	 * A primitív típusú adatokat értelemszerűen csak továbbítja.
	 * Az objektum típusú adatokat az autoload segítségével tölti be, és tárolja is az attribútumban, 
	 * hogy ne kelljen folyton betölteni, amikor szükség van rá. (Nyílván ennek megfelelően, ha az
	 * attribútum objektum típusú, akkor ellenőrzi az tényleg megfelelő típusú-e.
	 * A select típusúakat pedig a Select annotációval lehet vezérelni, és kezeli a meta-selectet, 
	 * ami azt jelenti, hogy a select where záradékában lehet hivatkozni az adott entitás egyes 
	 * attribútumainak konkrét értékére, így elérve, hogy a select típus értékét ne az osztály, 
	 * hanem az egyed tudja befolyásolni.
	 * Az annotációnak megfelelően, kétféle típust kezel az "array" illetve az objektumtípusút,
	 * ez utóbbi esetben a típus közvetlenül hivatkozik az entitás típusára. Az első esetben
	 * a select által visszaadott érték értelmezés nélkül kerül asszociatív tömbökbe, a második esetben
	 * viszont a select nem foglalkozik azzal, hogy milyen mezőket kértünk, lekérdezi a hivatkozott 
	 * entity elsődleges kulcsát, és a csak azt a mezőt kérdezi le, majd példányosítja, és autoload-al
	 * betölti az összes lekérdezett elemet, majd egy tömbbe rakja és ezt is adja vissza.
	 *
	 * @param string $name A lekérdezendő attribútum neve.
	 * @return mixed A meghatározott attribútum érték.
	 * @throws PersistenceException entitás leíró, és entitás hibák esetén 202, 203, 204, 205 és 206-os 
	 * hibakódú kivételeket váthat ki.
	 */
	public function __get($name){
		$clazz = get_class($this);
		$reflect = new ReflectionAnnotatedProperty($clazz, $name);
		if ($reflect->hasAnnotation("Attribute")){
			$attr = $reflect->getAnnotation("Attribute");
			if ($attr->object){
				//objektum típusú
				if ($this->$name == null){
					return null;
				}
				if ($reflect->hasAnnotation("Id")){
					throw new PersistenceException("Hibás entitás leírás: Az azonosítók nem lehetnek objektum típusúak.", null, 202);
				}
				if (is_object($this->$name)){
					if ($attr->type == get_class($this->$name)){
						return $this->$name;
					}else{
						throw new PersistenceException("Hibás entitás: A <<name>> nevű adattag nem megfelelő elemet tartlmaz.", array("name" => $name), 203);
					}
				}else{
					$ref = new ReflectionAnnotatedClass($attr->type);
					if ($ref->hasAnnotation("Table")){
						if ($ref->hasAnnotation("DisableAutoLoad")){
							throw new PersistenceException("Hibás entitás: A <<name>> nevű adattag nem automatikusan betölthető objektumot hivatkozik, és nem objektum tíusú elemet tartalmaz.", array("name" => $name), 204);
						}
						$ent = $ref->newInstance();
						$ent->autoLoad($this->$name);
						$this->$name = $ent;
						return $this->$name;
					}else{
						throw new PersistenceException("Hibás entitás leírás: A <<name>> nevű adattag egy nem perzisztens osztályt hivatkozik, annak elemeit pedig nem lehet betölteni.", array("name" => $name), 205);
					}
				}
			}else{
				if ($reflect->hasAnnotation("Id") && ($attr->type == "md5") && (($this->$name == null) || ($this->$name == "")) ){
					$hash = $reflect->getAnnotation("Id")->hashBy;
					if ($hash != "nohash"){
						$this->$name = md5($this->extractHashBy($hash));
					}
				}
				return $this->$name;
			}
		}elseif ($reflect->hasAnnotation("Select")){
			return $this->getSelectType($name, $reflect);
		}else{
			if ($reflect->isPublic()){
				return $this->$name;
			}else{
				throw new PersistenceException("A <<name> egy védett, vagy nem létező adattag, ezek az entitásokban csak akkor alkalmazhatóak, ha perzisztensek az értékeik - a hivatkozott adattag viszont nem ilyen.", array("name" => $name), 206);
			}
		}
	}

	/**
	 * Az entitás privát attribútumait állítja be. (Általában azokat, amelyeket tárolni is kell.)
	 * Az entitás vezérlés részleteihez lásd a __get metódusnál.
	 * A beállítandó attribútum, amennyiben valamilyen primitív típus (azaz nem objektum típusú,
	 * illetve nem select típusú), akkor csak egy konzisztencia ellenőrzés történik, (típus ellenőrzés
	 * nem, mert meg kell tartani a PHP gyengén típusos jellegét) majd a megadott érték beállítódik. 
	 * (feltéve persze, hogy a konzisztencia ellenőrzésen átmegy)
	 * A nem primitív típusok esetében az objektum típusú esetben csak egy típus azonosság ellenőrzés
	 * történik, amely nem insanceof-al zajlik, így az ősosztályokra, vagy a leszármazott osztályokra
	 * garantáltan nem fog működni, és nem fogja elfogadni mint megfelelő típust. (Ez egyelőre még nem 
	 * feloldható probléma, később javítva lesz.)
	 * A select típusok esetében az értékadás értelmezhetetlen, így annak bámilyen formályát elutasítja.
	 * (konkrétan kivételt dob)
	 *
	 * @param string $name A beállítandó attribútum neve.
	 * @param mixed $value A beállítandó attribútum leendő értéke (amennyiben megfelel minden kitételnek)
	 * @throws PersistenceException konzisztencia hibák esetén 250, 251, 252, 253, 254 és 255 hibakódú
	 * kivételeket válthat ki.
	 */
	public function __set($name, $value){
		$clazz = get_class($this);
		$annc = new ReflectionAnnotatedClass($clazz);
		if ($annc->hasAnnotation("Table")){
			$ann = new ReflectionAnnotatedProperty($clazz, $name);
			if ($ann->hasAnnotation("Attribute")){
				$attrib = $ann->getAnnotation("Attribute");
				if ($attrib->object){
					if (is_object($value)){
						if ($attrib->type == get_class($value)){ //instanceof jobb lenne, csak nem biztos, hogy megeszi egy szimpla string-re, de majd ki kell próbálni
							$this->$name = $value;
						}else{
							throw new PersistenceException("Konzisztencia hiba: A <<name>> nevű adattag típusa, és az értékadásban kapott objektum típusa nem egyezik.", array("name" => $name), 250); //konzisztencia hibák 250-től kezdődne
						}
					}else{
						throw new PersistenceException("Konzistencia hiba: A <<name>> nevú adattag objektum típusú kéne hogy legyen, viszont az értékadásban nem objektum adódott át.", array("name" => $name), 251);
					}
				}else{
					if (isset($value)){
						if ($ann->hasAnnotation("Constraints")){
							$constr = $ann->getAnnotation("Constraints");
							if ($this->checkConstraints($value, $constr)){
								if (($attrib->length != "default") && (strlen($value) < ((int) $attrib->length))){
									$this->$name = $value;
								}elseif (($attrib->length == "default") && (($attrib->type != "string") || (strlen($value) < 255))){
									$this->$name = $value;
								}else{
									throw new PersistenceException("Konzisztencia hiba: A <<name>> nevű adattag az értékadásban a maximális méretnél nagyobb méretű stringet kapott. Érték: <<value>>", array("name" => $name, "value" => $value), 252);
								}
							}else{
								throw new PersistenceException("Konzisztencia hiba: A <<name>> nevű adattagra az értékadásban nem teljesült valamelyik megszorítás.", array("name" => $name), 253);
							}
						}else{
							if (($attrib->length != "default") && (strlen($value) < ((int) $attrib->length))){
								$this->$name = $value;
							}elseif (($attrib->length == "default") && (($attrib->type != "string") || (strlen($value) < 255))){
								$this->$name = $value;
							}else{
								throw new PersistenceException("Konzisztencia hiba: A <<name>> nevű adattag az értékadásban a maximális méretnél nagyobb méretű stringet kapott. Érték: <<value>>", array("name" => $name, "value" => $value), 252);
							}
						}
					}else{
						if ($ann->hasAnnotation("Nullable")){
							$this->$name = $value;
						}else{
							throw new PersistenceException("Konzisztencia hiba: A <<name>> nevű adattag nem állítható null értékre.", array("name" => $name), 254);
						}
					}
				}
			}elseif ($ann->hasAnnotation("Select")){
				$sel = $ann->getAnnotation("Select");
				if ($sel->onSet == null){
					throw new PersistenceException("Konzisztencia hiba: Egy select típusú mező értékét nem lehet beállítani, mert azt az előre definiált SELECT utasítás fogja meghatározni.", null, 255);
				}else{
					$meth = $annc->getMethod($sel->onSet);
					if ($meth->isPublic()){
						$meth->invoke($this, $value);
					}else{
						throw new PersistenceException("Konzisztencia hiba: Egy select típusú mező értékét nem lehet beállítani, mert azt az előre definiált SELECT utasítás fogja meghatározni.", null, 255);
					}
				}
			}else{
				$this->$name = $value;
			}
		}else{
			$ann = new ReflectionAnnotatedProperty($name);
			if ($ann->hasAnnotation("Select")){
				throw new PersistenceException("Konzisztencia hiba: Egy select típusú mező értékét nem lehet beállítani, mert azt az előre definiált SELECT utasítás fogja meghatározni.", null, 255);
			}else{
				$this->$name = $value;
			}
		}
	}

	/**
	 * Egy asszociatív tömböt készít, az EntityHandler számára, amivel kényelmesen tud dolgozni az 
	 * adatbázisban. (Gyakorlatilag a normál Select utasítással kapott asszociatív tömbbel ekvivalens
	 * tömb, amivel egyszerűsíthetőek, az adatbázis műveletek.)
	 *
	 * @return array Az adatbázis elemet adja vissza, amit az entitás alapján felépít.
	 * @throws PersistenceException entitás leíró, és entitás hibák esetén 207, 208, 209, 210 és 211 
	 * hibakódú, illetve egy konkrét konzisztencia hiba esetén 256 hibakódú kivételeket válthat ki.
	 */
	public function toDBElement(){
		$dbelement = array();
		$clss = get_class($this);
		foreach ($this->getUnaccessableFields() as $attr => $value){
			$ref = new ReflectionAnnotatedProperty($clss, $attr);
			if ($ref->hasAnnotation("Attribute")){
				$attrib = $ref->getAnnotation("Attribute");
				$key = "";
				if ($attrib->name == "default"){
					$key = strtoupper(substr($attr, 0, 1)).substr($attr, 1);
				}else{
					$key = $attrib->name;
				}
				
				if (($value == "") && ($ref->hasAnnotation("Nullable"))){
					continue;
				}//nem biztos hogy ez jó ötlet, de egyelőre maradjon.
				
				if ($value === null){ // azt még nem tudjuk, hogy ami úres, az null-t jelent, így nem lehet azt mondani, hogy az egyezik
					if ($ref->hasAnnotation("Nullable")){
						continue;
					}elseif ($ref->hasAnnotation("Id") && ($ref->getAnnotation("Id")->hashBy != "nohash")){
						$value = $this->__get($attr);
					}else{
						throw new PersistenceException("Konzisztencia hiba: A <<attr>> nevű mező null értékű, viszont a leírásban nem szerepel, hogy lehet null.", array("attr" => $attr), 256);
					}
				}

				if ($attrib->object){
					$clazz = $attrib->type;
					$annot = new ReflectionAnnotatedClass($clazz);
					if ($annot->hasAnnotation("Table")){
						if (!is_object($value)){
							$value = $this->__get($attr);
						}
						if (is_object($value)){
							$id = "";
							foreach ($value as $a => $v){
								$annfield = new ReflectionAnnotatedProperty($clazz, $a);
								if ($annfield->hasAnnotation("Id") && $annfield->hasAnnotation("Attribute")){
									if ($v == null){
										$v = $value->__get($a);
									}
									if (isset($v) && (!is_object($v))){
										$id = $v;
										break;
									}else{
										throw new PersistenceException("Hibás entitás: A <<a>> nevű adattag azonosító kéne hogy legyen, viszont az egyik előfordulásban üres, vagy objektumot tartalmaz, Így a hivatkozó entitás nem menthető", array("a" => $a), 207);
									}
								}
							}
							if ($id == ""){
								throw new PersistenceException("Hibás entitás leíró: A <<clazz>> osztálynevű entitás nem tartalmaz azonosítót.", array("clazz" => $clazz), 208);
							}else{
								$dbelement[$key] = $id;
							}
							
						}else{
							throw new PersistenceException("Entitás hiba: A <<attr>> nevű adattag a leírás szerint objektum típusú kell legyen, viszont egy entitásban ez nem teljesül.", array("attr" => $attr), 209);
						}
					}else{
						throw new PersistenceException("Entitás leíró hiba: A <<attr>> nevű mező olyan típust hivatkozik, amit nem lehet adatbázisban tárolni.", array("attr" => $attr), 210);
					}
				}else{
					$type = strtolower($attrib->type);
					if (($type == "string") || ($type == "md5")){
						$dbelement[$key] = $value;
					}elseif ($type == "float"){
						$dbelement[$key] = (float) $value;
					}elseif ($type == "int"){
						$dbelement[$key] = (int) $value;
					}elseif ($type == "boolean"){
						$dbelement[$key] = ($value == "true") ? "1" : "0";
					}elseif ($type == "datetime"){
						if ($value instanceof DateTime){
							$dbelement[$key] = $value->format("Y.m.d H:i:s"); // TODO Kéne egy Locals osztály, amiben be lenne állítva, hogy milyen formázás legyen ehhez, meg esetleg a currency-khez (vizuális osztályokban)
						}else{
							$dbelement[$key] = $value;
						}
					}elseif ($type == "date"){
						if ($value instanceof DateTime){
							$dbelement[$key] = $value->format("Y.m.d");
						}else{
							$dbelement[$key] = $value;
						}
					}else{
						throw new PersistenceException("Entitás leíró hiba: Nem értelmezett típusú attribútum, neve: <<attr>>.", array("attr" => $attr), 211);
					}
				}
			}
		}
		return $dbelement;
	}

	/**
	 * Az adatbázis elem alapján belállítja az entitás attribútum értékeit. (Alapszinten
	 * ennél magasabb szinten a __get foglalkozik a beállításokkal, ez a metódus, csak
	 * primitív adatok szintjén állítja be az attribútomokat.
	 *
	 * @param array $dbelement A nyers adatbáziselem, ami az adott entitáshoz tartalmazza az adatokat.
	 * @throws PersistenceException entitás leíró hiba esetén 211-es hibakódú kivételt vált ki.
	 */
	public function fromDBElement($dbelement){ 
		$clss = get_class($this);
		foreach ($this->getUnaccessableFields() as $attr => $value){
			$ref = new ReflectionAnnotatedProperty($clss, $attr);
			if ($ref->hasAnnotation("Attribute")){
				$attrib = $ref->getAnnotation("Attribute");
				$key = "";
				if ($attrib->name == "default"){
					$key = strtoupper(substr($attr, 0, 1)).substr($attr, 1);
				}else{
					$key = $attrib->name;
				}
				
				//Ha objektum típusú egy-egy mező, akkor a getter megoldja. (nagyon is jól, mert ez így megállítja a végtelen rekurziót, illetve nem engedi, hogy beleessünk)
				$type = strtolower($attrib->type);
				if (($type == "string") || ($type == "md5")){
					$this->$attr = $dbelement[$key];
				}elseif ($type == "float"){
					$this->$attr = (float) $dbelement[$key];
				}elseif ($type == "int"){
					$this->$attr = (int) $dbelement[$key];
				}elseif ($type == "boolean"){
					$this->$attr = (($dbelement[$key] == "1") ? true : false);
				}elseif (($type == "date") ||($type == "datetime")){
					if ($dbelement[$key] == null){
						$this->$attr = null;
					}else{
						//$this->$attr = new DateTime($dbelement[$key]);
						$this->$attr = $dbelement[$key];
					}
				}else{
					if ($attrib->object){
						$this->$attr = $dbelement[$key];
					}else{
						throw new PersistenceException("Entitás leíró hiba: Nem értelmezett típusú attribútum, neve: <<attr>>.", array("attr" => $attr), 211);
					}
				}
			}
		}
	}

	/**
	 * Az entitás automatikus betöltését végzi, azaz az azonosító alapján kikeresi az entitás
	 * összes attribútumához kapcsolódó értéket az adatbázisból, majd a fromDBElement metódus
	 * segítségével beállítja azokat az entitásban.
	 *
	 * @param mixed $id Az entitás azonosítója.
	 * @throws PersistenceException entitás leíró hibák esetén 212, 213 és 214-es hibakósú
	 * kivételeket vált ki.
	 */
	public function autoLoad($id){
		$clss = get_class($this);
		$idField = "";
		foreach ($this->getUnaccessableFields() as $attr => $value){
			$ref = new ReflectionAnnotatedProperty($clss, $attr);
			if ($ref->hasAnnotation("Id")){
				if ($ref->hasAnnotation("Attribute")){
					$attrib = $ref->getAnnotation("Attribute");
					$key = "";
					if ($attrib->name == "default"){
						$key = strtoupper(substr($attr, 0, 1)).substr($attr, 1);
					}else{
						$key = $attrib->name;
					}
					$idField = $key;
					break;
				}else{
					throw new PersistenceException("Entitás leíró hiba: Az egyed azonosítónak attribútumnak kell lennie, viszont a <<clss>> nevű entitásban ez nincs így.", array("clss" => $clss), 212);
				}
			}
		}

		if ($idField == ""){
			throw new PersistenceException("A <<clss>> nevű entitás nem tartalmaz elsődleges kulcsot, így nem lehet kezelni!", array("clss" => $clss), 213);
		}
			
		$where = array($idField => $id);

		$res = $this->getPersistenceHandler()->selectArray($this->getTableName(), "*", $where);

		if (count($res) == 1){
			$row = $res[0];
			$this->fromDBElement($row);
			//EZ FONTOS EZ MARADJON ITT A REFACTOR UTÁN IS!!!!!
			$this->oldElement = $row;
		}else{
			throw new PersistenceException("Nincs a <<id>> értékű azonosítóval rendelkező egyed.", array("id" => $id), 214);
		}
	}

	/**
	 * Létrehozza az entitás adatbázis beli tábláját. A táblát az entitás annotációi alapján hozza 
	 * létre a perzisztencia kezelő, illetve az abban megadott entitás kezelő segítségével. 
	 *
	 * (a többi meg még kérdéses pl alter table, de leginkább megoldhatatlan, mert ahhoz olyan trükkök
	 * kellenének, amilyeneket jelenleg el sem tok képzelni, nem hogy lekódolni :-)) )
	 *
	 * @throws PersistenceException entitás leíró, illetve adatbázis hibák esetén 215, 216, 217 és 218-as
	 * hibakódú kivételeket válthat ki.
	 */
	public function createTheTable(){
		$clss = get_class($this);
		$primary = "";
		$fields = array();
		$fic = 0;
		foreach ($this->getUnaccessableFields() as $attr => $value){
			$ref = new ReflectionAnnotatedProperty($clss, $attr);
			if ($ref->hasAnnotation("Attribute")){
				$attrib = $ref->getAnnotation("Attribute");
				$key = "";
				if ($attrib->name == "default"){
					$key = strtoupper(substr($attr, 0, 1)).substr($attr, 1);
				}else{
					$key = $attrib->name;
				}

				$field = new DBField();
				$field->name = $key;

				if ($ref->hasAnnotation("Id")){
					$primary = $key;
				}

				if ($attrib->object){
					$clazz = $attrib->type;
					$annot = new ReflectionAnnotatedClass($clazz);
					if ($annot->hasAnnotation("Table")){
						$id = "";
						$inst = $annot->newInstance()->getUnaccessableFields();
						foreach ($inst as $a => $v){
							$annfield = new ReflectionAnnotatedProperty($clazz, $a);
							if ($annfield->hasAnnotation("Id") && $annfield->hasAnnotation("Attribute")){
								$id = $a;
								$attribb = $annfield->getAnnotation("Attribute");
								$type = strtolower($attribb->type);
								if ($type == "string"){
									$length = 0;
									if ($attribb->length == "default"){
										$length = 255;
									}else{
										$length = $attribb->length;
									}
									$field->type = "string";
									$field->length = $length;
								}elseif ($type == "md5"){
									$field->type = "string";
									$field->length = 32;
								}elseif ($type == "float"){
									$field->type = "double";
								}elseif ($type == "int"){
									$field->type = $type;
								}elseif ($type == "boolean"){
									$field->type = $type;
								}elseif ($type == "date"){
									$field->type = $type;
								}elseif ($type == "datetime"){
									$field->type = $type;
								}else{
									throw new PersistenceException("Entitás leíró hiba: Nem értelmezett típusú attribútum, neve: <<attr>>, típus: <<type>>.", array("attr" => $attr, "type" => $type), 215);
								}
								break;
							}
						}
						if ($id == ""){
							throw new PersistenceException("Hibás entitás leíró: A <<clazz>> osztálynevű entitás nem tartalmaz azonosítót.", array("clazz" => $clazz), 216);
						}
					}
				}else{
					$type = strtolower($attrib->type);
					if ($type == "string"){
						$length = 0;
						if ($attrib->length == "default"){
							$length = 255;
						}else{
							$length = $attrib->length;
						}
						$field->type = "string";
						$field->length = $length;
					}elseif ($type == "md5"){
						$field->type = "string";
						$field->length = 32;
					}elseif ($type == "float"){
						$field->type = "double";
					}elseif ($type == "int"){
						$field->type = $type;
					}elseif ($type == "boolean"){
						$field->type = $type;
					}elseif ($type == "date"){
						$field->type = $type;
					}elseif ($type == "datetime"){
						$field->type = $type;
					}else{
						throw new PersistenceException("Entitás leíró hiba: Nem értelmezett típusú attribútum, neve: <<attr>>, típus: <<type>>.", array("attr" => $attr, "type" => $type), 215);
					}
				}

				$field->nullable = $ref->hasAnnotation("Nullable");

				$fields[$fic++] = $field;
			}
		}

		if ($primary == ""){
			throw new PersistenceException("Entitás leíró hiba: Nincs meghatároza elsődleges kulcs ebben az entitásban: <<clss>>.", array("clss" => $clss), 217);
		}else{
			$this->getPersistenceHandler()->getEntityHandler()->setTableName($this->getTableName());
			$res = $this->getPersistenceHandler()->getEntityHandler()->createTable($fields, $primary);
			if (!$res){
				$error = $this->getPersistenceHandler()->getEntityHandler()->getErrors();
				if ($error == ""){
					$error = $this->getPersistenceHandler()->getEntityHandler()->getWarnings();
				}
				throw new PersistenceException("Adatbázis hiba, az entitás táblájának kialakításakor: <<clss>> a hiba szövege: <<error>>", array("clss" => $clss, "error" => $error), 218);
			}
		}
	}

	/**
	 * Leellenőrzi, hogy az entitáshoz tartozik-e már tábla az adatbázisban, és ennek megfelelően, 
	 * igaz vagy hamis értékkel tér vissza, ha már létezik a tábla, akkor igaz, máskülönben hamis
	 * értékkel tér vissza. A tábla mezőinek azonosságát ezzel az entitással nem ellenőrzi, csak a
	 * névütközések elkerülésére van itt. 
	 *
	 * @return boolean A tábla létezőségét megválaszoló boolean érték. <-- nice :-))
	 */
	public function hasTableCreated(){
		$this->getPersistenceHandler()->getEntityHandler()->setTableName($this->getTableName());
		return $this->getPersistenceHandler()->getEntityHandler()->isTableExists();
	}
	
	/**
	 * Perzisztens módon tárolja az adott entitást, ha szabályosan lett betöltve az adatbázisból, akkor
	 * ezzel a metódussal tárolhatjuk a változásokat is, amennyiben ez egy új elem, akkor pedig mint új
	 * elemet veszi fel az adatbázisba. 
	 * Ha az entitás nem szabályosan lett betöltve az adatbázisból, de már létezik ez az elem az 
	 * adatbázisban, akkor az updateFrom metódust kell használni.
	 * 
	 * @throws PersistenceException adattárolási hiba esetén 300-as hibakódú kivételt válthat ki.
	 */
	public function save(){
		$eh = $this->getPersistenceHandler()->getEntityHandler();
		$res = false;
		if (!isset($this->oldElement)){
			$eh->setTableName($this->getTableName());
			$eh->setDBElement($this->toDBElement());
			$res = $eh->insert();
		}else{
			$eh->setDBElementTo($this->toDBElement());
			$eh->setDBElementFrom($this->oldElement);
			$eh->setTableName($this->getTableName());
			$res = $eh->change();
		}
		if ($res == false){
			echo $this->oldElement["Number"];
			throw new PersistenceException("Hiba az adatok tárolásakor, a hibaüzenet: <<error>>", array("error" => $eh->getErrors()), 300);
		}
		$this->oldElement = $this->toDBElement();
	}
	
	/**
	 * A megadptt régi elemet frissíti ennek az entitásnak az adataival. A régi elem lehet egy 
	 * szabályos adatbázis elemet leíró asszociatív tömb is, illetve egy entitás is.
	 * 
	 * @param mixed $old a régi elem, ami lehet egy adatbáziselemet reprezentáló asszociatív tömb, 
	 * illetve egy entitás is.
	 * @throws PersistenceException hibás metódushívás, illetve adattárolási hiba esetén 280, illetve
	 * 300-as hibakódú kivételt válthat ki.
	 */
	public function updateFrom($old){
		$eh = $this->getPersistenceHandler()->getEntityHandler();
		$res = false;
		if (is_array($old)){
			$eh->setTableName($this->getTableName());
			$eh->setDBElementFrom($old);
			$eh->setDBElementTo($this->toDBElement());
			$res = $eh->change();
		}elseif ($old instanceof Entity){
			$eh->setTableName($this->getTableName());
			$eh->setDBElementFrom($old->toDBElement());
			$eh->setDBElementTo($this->toDBElement());
			$res = $eh->change();
		}else{
			throw new PersistenceException("Hibás metódushívás: frissíteni csak adatbázis elemet, vagy entitást lehet.", null, 280);
		}
		if ($res == false){
			throw new PersistenceException("Hiba az adatok tárolásakor, a hibaüzenet: <<error>>", array("error" => $eh->getErrors()), 300);
		}
		$this->oldElement = $this->toDBElement();
	}
	
	/**
	 * Az entitás szabályos eltávolítását végzi az adatbázisból. A szabályosság itt arra vonatkozik, 
	 * hogy elvégzi a referenciák tesztelését is a törlés előtt, és amennyiben vannak kötelezően 
	 * feloldandó referenciák, akkor a checkReferences metódus által dobott kivételeket dobja tovább.
	 * 
	 * @throws PersistenceException adatbázis hiba esetén 301-es hibakódú hibát válthat ki.
	 */
	public function remove(){
		$eh = $this->getPersistenceHandler()->getEntityHandler();
		$res = false;
		$this->checkReferences();
		$eh->setTableName($this->getTableName());
		$eh->setDBElement($this->toDBElement());
		$res = $eh->delete();
		if ($res == false){
			throw new PersistenceException("Hiba az entitás törlésekor, a hibaüzenet: <<error>>", array("error" => $eh->getErrors()), 301);
		}
	}
	
}
?>