<?php


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

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

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

/** @Target("method") */
class XmlMethod extends Annotation{
	public $schemaName = "--default--"; //ha nincs megadva, akkor az alapértelmezett sémát módosítja
	public $methodName = "?default?"; //ha nincs megadva, akkor az eredeti metódusnév
	public $paramsName = "?default?"; //{"séma_beli_nev" = "valodi_nev", ...}
	public $paramsType = array(); //{"parameter_nev" = "tipus", ...} a tipus lehet "primitive" vagy egy konkrét osztálynév
	//ha nincs megadva, akkor minden string és minden az xml node beli string, még ha az nem is az.  
}

/** @Target({"property", "method"}) */
class XmlArray extends Annotation{
	public $schemaName = "?default?";
	public $type = "normal"; //vagy assoc ha asszociatív a tömb
	public $elementName = "element";
	public $keyName = "key";
	public $keyType = "simple";
	public $valueName = "value";
	public $valueType = "simple";
	public $paramName = null;
}

class XmlSerializer{

	private static function getEntityFields($entity){
		$res = array();
		$reflect = new ReflectionClass(get_class($entity));
		$props = $reflect->getProperties();
		for ($i = 0; $i < count($props); $i++){
			$property = $props[$i];
			$name = $property->getName();
			$res[$name] = $entity->__get($name);
		}
		return $res;
	}

	/**
	 *
	 * @return string
	 */
	public static function serializeArray($object, $key, $value, $schemaName, $method = false){
		//TODO Ide majd kell valamilyen teszt a megadott adatokra, mert amíg privát volt nem számított
		//viszont mostmár mindenképpen kell majd
		$clazz = get_class($object);
		$val = "";
		if ($object instanceof XmlSerializable2){
			$val = $object->manualSerialize($schemaName, $key);
		}
		if (($val == null) || ($val == "")){
			$val = "";
			$annot = null;
			if ($method){
				$annot = new ReflectionAnnotatedMethod($clazz, $key);
			}else{
				$annot = new ReflectionAnnotatedProperty($clazz, $key);
			}
			$xarr = null;
			if ($annot->hasAnnotation("XmlArray")){
				$xarrs = $annot->getAllAnnotations("XmlArray");
				$sch = $schemaName;
				if ($schemaName == "--default--"){
					$sch = "?default?";
				}
				foreach ($xarrs as $xxarr){
					if (($xxarr->schemaName == $sch) && ($xxarr->paramName == null)){
						$xarr = $xxarr;
						break;
					}
				}
			}else{
				$xarr = new XmlArray(null, new ReflectionProperty());
			}
			if ($xarr == null){
				throw new SerializerException("Szerializációs hiba, a <<class>> osztályban". 
					" nem található a <<key>> nevű tömb tipusú attribútumhoz a sémának megfelelő leírás.", 
				array("class" => get_class($object), "key" => $key), 100);
			}
			foreach ($value as $k => $v){
				if (($xarr->type == "normal") && ($xarr->valueType == "simple")){
					$val .= "<".$xarr->elementName.">".$v."</".$xarr->elementName.">";
				}elseif (($xarr->type == "normal") && ($xarr->valueType != "simple")){
					$val .= "<".$xarr->elementName.">".XmlSerializer::serialize($v, $schemaName)."</".$xarr->elementName.">";
				}elseif (($xarr->type == "assoc") && ($xarr->valueType == "simple") && ($xarr->keyType == "simple")){
					$val .= "<".$xarr->elementName.">";
					$val .= "<".$xarr->keyName.">".$k."</".$xarr->keyName.">";
					$val .= "<".$xarr->valueName.">".$v."</".$xarr->valueName.">";
					$val .= "</".$xarr->elementName.">";
				}elseif (($xarr->type == "assoc") && ($xarr->valueType == "simple") && ($xarr->keyType != "simple")){
					$val .= "<".$xarr->elementName.">";
					$val .= "<".$xarr->keyName.">".XmlSerializer::serialize($k, $schemaName)."</".$xarr->keyName.">";
					$val .= "<".$xarr->valueName.">".$v."</".$xarr->valueName.">";
					$val .= "</".$xarr->elementName.">";
				}elseif (($xarr->type == "assoc") && ($xarr->valueType != "simple") && ($xarr->keyType == "simple")){
					$val .= "<".$xarr->elementName.">";
					$val .= "<".$xarr->keyName.">".$k."</".$xarr->keyName.">";
					$val .= "<".$xarr->valueName.">".XmlSerializer::serialize($v, $schemaName)."</".$xarr->valueName.">";
					$val .= "</".$xarr->elementName.">";
				}elseif (($xarr->type == "assoc") && ($xarr->valueType != "simple") && ($xarr->keyType != "simple")){
					$val .= "<".$xarr->elementName.">";
					$val .= "<".$xarr->keyName.">".XmlSerializer::serialize($k, $schemaName)."</".$xarr->keyName.">";
					$val .= "<".$xarr->valueName.">".XmlSerializer::serialize($v, $schemaName)."</".$xarr->valueName.">";
					$val .= "</".$xarr->elementName.">";
				}else{
					throw new SerializerException("Szerializációs hiba: A <<class>> osztálynam helytelenül".
					"van megadva egy tömb szerializációs leírása.", array("class" => get_class($object)), 101);
				}
			}
		}
		return $val;
	}

	private static function deserializeArray($object, $xml, $schemaName, $annot, $parameterName = null){
		$res = array();
		$xarrs = $annot->getAllAnnotations("XmlArray");
		$sch = $schemaName;
		$xarr = null;
		if ($schemaName == "--default--"){
			$sch = "?default?";
		}
		foreach ($xarrs as $xxarr){
			if (($xxarr->schemaName == $sch) && ($xxarr->paramName == $parameterName)){
				$xarr = $xxarr;
				break;
			}
		}

		if ($xarr == null){
			throw new SerializerException("Szerializációs hiba, a <<class>> osztályban". 
				"nem található a <<key>> nevű tömb tipusú attribútumhoz a sémának megfelelő leírás.", 
			array("class" => get_class($object), "key" => $key), 100); // TODO $key hol a bánatban van????
		}

		//TODO EZT IS :-)
		//Logger::log("PEA", Logger::DEBUG, get_class().": ".$xml->asXML());
		$en = $xarr->elementName;
		$entries = $xml->$en; //elvileg több elemet is vissza kell neki adnia.
		
		$i = 0;
		foreach ($entries as $v){
			if (($xarr->type == "normal") && ($xarr->valueType == "simple")){
				//$en = $xarr->elementName;
				//$res[$i] = $v->$en."";
				$res[$i] =  $v;
			}elseif (($xarr->type == "normal") && ($xarr->valueType != "simple")){
				//$en = $xarr->elementName;
				$ref = new ReflectionClass($xarr->valueType);
				$obj = $ref->newInstance();
				$res[$i] = XmlSerializer::deserialize($obj, $v, $schemaName);
			}elseif (($xarr->type == "assoc") && ($xarr->valueType == "simple") && ($xarr->keyType == "simple")){
				$kn = $xarr->keyName."";
				$vn = $xarr->valueName.""; 
				//$en = $xarr->elementName;
				//$tmp = $v;
				//TODO EZT MAJD SZEDD KI!!!
				//Logger::log("PEA", Logger::DEBUG, get_class().": tmp: ".$tmp->asXML());
				//Logger::log("PEA", Logger::DEBUG, get_class().": v: ".$v->asXML());
				$res[$v->$kn.""] = $v->$vn.""; //TODO majdnem ok, csak még alább is rendbe kell rakni az új elvnek megfelelően, meg tesztelni kell több adatra.
				//Logger::log("PEA", Logger::DEBUG, get_class().": key: ".$v->$kn." value: ".$v->$vn);
				//Logger::log("PEA", Logger::DEBUG, get_class($this).": res-ből tf: ".$res["tf"]);
			}elseif (($xarr->type == "assoc") && ($xarr->valueType == "simple") && ($xarr->keyType != "simple")){
				$ref = new ReflectionClass($xarr->keyType);
				$obj = $ref->newInstance();
				$kn = $xarr->keyName."";
				$vn = $xarr->valueName."";
				//$en = $xarr->elementName;
				//$tmp = $v->$en;
				$obj = XmlSerializer::deserialize($obj, $v->$kn, $schemaName);
				$res[$obj] = $v->$vn;
			}elseif (($xarr->type == "assoc") && ($xarr->valueType != "simple") && ($xarr->keyType == "simple")){
				$ref = new ReflectionClass($xarr->valueType);
				$obj = $ref->newInstance();
				$kn = $xarr->keyName."";
				$vn = $xarr->valueName."";
				//$en = $xarr->elementName;
				//$tmp = $v->$en;
				$res[$v->$kn] = XmlSerializer::deserialize($obj, $v->$vn, $schemaName);
			}elseif (($xarr->type == "assoc") && ($xarr->valueType != "simple") && ($xarr->keyType != "simple")){
				$ref1 = new ReflectionClass($xarr->valueType);
				$ref2 = new ReflectionClass($xarr->keyType);
				$obj1 = $ref1->newInstance();
				$obj2 = $ref2->newInstance();
				$kn = $xarr->keyName."";
				$vn = $xarr->valueName."";
				//$en = $xarr->elementName;
				//$tmp = $v->$en;
				$obj2 = XmlSerializer::deserialize($obj2, $v->$kn, $schemaName);
				$res[$obj2] = XmlSerializer::deserialize($obj1, $v->$vn, $schemaName);
			}else{
				throw new SerializerException("Szerializációs hiba: A <<class>> osztálynam helytelenül".
					"van megadva egy tömb szerializációs leírása.", array("class" => get_class($object)), 101);
			}
		}
		return $res;
	}

	public static function serialize($object, $schemaName = "--default--"){
		if ($object instanceof XmlSerializable2){
			$object->beforeSerialize($schemaName);
		}

		$clazz = get_class($object);
		$schema = "";

		if ($schemaName == "--default--"){
			$schema = "<".$clazz.">";
			foreach ($object as $key => $value){
				$schema .= "<".$key.">".$key."</".$key.">";
			}
			$schema .= "</".$clazz.">";
		}else{
			$ann = new ReflectionAnnotatedProperty($clazz, $schemaName);
			if ($ann->hasAnnotation("XmlSchema")){
				$schema = $ann->getValue($object);
			}else{
				throw new SerializerException("Serializációs hiba: Hiba a séma megadásban, a megadott nevű attribútum, nincs annotálva, mint sémaleíró az osztályban: <<class>>", array("class" => $clazz), 102);
			}
		}

		foreach ((($object instanceof Entity) ? self::getEntityFields($object) : $object) as $key => $value){ //Nah ha ez működik, akkor ez a kedvenc sorom :-)
			$val = "";
			if (is_object($value)){
				$annot = new ReflectionAnnotatedProperty($clazz, $key);
				if ($annot->hasAnnotation("XmlObject")){
					$val .= XmlSerializer::serialize($object->$key, $schemaName);
				}else{
					if ($object instanceof XmlSerializable2){
						$val .= $object->manualSerialize($schema, $key);
					}else{
						$val .= XmlSerializer::serialize($object->$key);
					}
				}
			}elseif (is_array($value)){
				$val .= self::serializeArray($object, $key, $value, $schemaName);
			}else{
				$val .= $object->$key;
			}
			$schema = str_replace(">".$key."<", ">".$val."<", $schema);
		}

		if ($object instanceof XmlSerializable2){
			$object->afterSerialize($schemaName, $schema);
		}

		return $schema;
	}

	public static function deserialize($object, $xml, $schemaName = "--default--"){
		if ($object instanceof XmlSerializable2){
			$object->beforeDeserialize($schemaName, $xml);
		}
		
		$clazz = get_class($object);
		$schema = "";
		$inschclss = '';

		if ($schemaName == "--default--"){
			$schema = "<".$clazz.">";
			foreach ($object as $key => $value){
				$schema .= "<".$key.">".$key."</".$key.">";
			}
			$schema .= "</".$clazz.">";
		}else{
			$ann = new ReflectionAnnotatedProperty($clazz, $schemaName);
			if ($ann->hasAnnotation("XmlSchema")){
				$schema = $ann->getValue($object);
			}else{
				throw new SerializerException("Serializációs hiba: Hiba a séma megadásban, a megadott nevű attribútum, nincs annotálva, mint sémaleíró az osztályban: <<class>>", array("class" => $clazz), 102);
			}
		}

		try{
			$schema = new SimpleXMLElement($schema);
			$inschclss = $schema->getName();
			if ($xml->getName() != $inschclss){
				$xml = $xml->$inschclss;
			}
		}catch (Exception $e){
			throw new SerializerException("Serializációs hiba: A <<schemaName>> nevű sémaleíró a <<clazz>> nevű osztályban hibás.", 
			array("schemaName" => $schemaName, "clazz" => $clazz), 103);
		}

		foreach ($schema as $key => $value){
			$annot = new ReflectionAnnotatedProperty($clazz, $value);
			if ($annot->hasAnnotation("XmlObject")){
				$type = $annot->getAnnotation("XmlObject");

				if ($type->value != "?default?"){
					$ref = new ReflectionClass($type->value);
					$obj = $ref->newInstance();
					$obj = XmlSerializer::deserialize($obj, $xml->$key, $schemaName);
					$object->$value = $obj;
				}else{
					if ($object instanceof XmlSerializable2){
						$object->manualDeserialize($schema, $xml->$key, $value);
					}else{
						throw new SerializerException("Serializációs hiba: Nem lehet deserializálni a <<clazz>> egy pélányát,".
						" mert tartalmaz egy elemet, ami sem automatikusan, sem manuálisan nem oldható fel.", 
						array("clazz" => $clazz), 104);
					}
				}
			}elseif ($annot->hasAnnotation("XmlArray")){
				$vval = null;
				if ($object instanceof XmlSerializable2){
					$vval = $object->manualDeserialize($schema, $xml->$key, $value);
				}
				if (($vval == null) || ($vval == "")){
					$object->$value = self::deserializeArray($object, $xml->$key, $schemaName, $annot);
				}else{
					$object->$value = $vval;
				}
			}else{
				$object->$value = ($xml->$key != "null") ? $xml->$key."" : null;
			}
		}

		if ($object instanceof XmlSerializable2){
			$object->afterDeserialize($schemaName, $xml);
		}

		return $object;
	}

	/**
	 * 
	 * @param SimpleXMLElement $xml
	 * @return PeaMethod
	 */
	public static function deserializeMethod($object, $xml, $schemaName = "--default--"){
		$clazz = get_class($object);
		$result = new PeaMethod();
		$ref = new ReflectionClass($clazz);
		$methods = $ref->getMethods();
		foreach ($methods as $method){
			if ($method->isPublic()){
				$name = $method->getName();
				$refa = new ReflectionAnnotatedMethod($clazz, $name);
				if ($refa->hasAnnotation("XmlMethod")){
					$annmeths = $refa->getAllAnnotations("XmlMethod");
					$themeth = null;
					foreach ($annmeths as $ameth){
						if (($ameth->schemaName == $schemaName) && ($xml->getName() == (($ameth->methodName == "?default?") ? $method->getName() : $ameth->methodName))){
							$themeth = $ameth;
							break;
						}
					}
					if ($themeth != null){
						if ($themeth->paramsName == "?default?"){
							foreach ($xml as $pname => $pvalue){
								if (($themeth->paramsType[$pname] == "primitive") || ($themeth->paramsType[$pname] == null)){
									$result->params[$pname] = "".$pvalue."";
								}elseif ($themeth->paramsType[$pname] == "array"){
									if ($pvalue == ""){
										$result->params[$pname] = null;
									}else{
										$result->params[$pname] = XmlSerializer::deserializeArray($obj, $pvalue, $schemaName, $refa, $pname);
									}
								}else{
									$refl = new ReflectionClass($themeth->paramsType[$pname]);
									$obj = $refl->newInstance();
									$result->params[$pname] = XmlSerializer::deserialize($obj, $pvalue, $schemaName);
								}
							}
						}else{
							foreach($themeth->paramsName as $insch => $real){
								if (($themeth->paramsType[$real] == "primitive") || ($themeth->paramsType[$real] == null)){
									$result->params[$real] = "".$xml->$insch."";
								}elseif ($themeth->paramsType[$real] == "array"){
									if ($xml->$insch == ""){
										$result->params[$real] = null;
									}else{
										$result->params[$real] = XmlSerializer::deserializeArray($obj, $xml->$insch, $schemaName, $refa, $real);
									}
								}else{
									$refl = new ReflectionClass($themeth->paramsType[$real]);
									$obj = $refl->newInstance();
									$result->params[$real] = XmlSerializer::deserialize($obj, $xml->$insch, $schemaName);
								}
							}
						}
						$result->name = $name;
						$result->object = $object;
						break;
					}
				}elseif ($schemaName == "--default--"){
					if ($xml->getName() == $method->getName()){
						foreach ($xml as $pname => $pvalue){
							$result->params[$pname] = "".$pvalue."";
						}
						$result->name = $method->getName();
						$result->object = $object;
						break;
					}
				}
			}
		}
		return $result;
	}
	
	/**
	 * 
	 * @param Exception $exception
	 * TODO:MEG KELL MÉG NÉZNI MERT MOBILRA EZ ÍGY SOK ,,,, NEM KELL MESSAGE
	 */
	public static function serializeException($exception){
		$res = "<exception>";
		$res .= "<type>".get_class($exception)."</type>";
		$res .= "<message>".$exception->getMessage()."</message>";
		$res .= "<code>".$exception->getCode()."</code>";
		$res .= "</exception>";
		return $res;
	}
	
	/**
	 * 
	 * @param SimpleXMLElement $xml
	 */
	public static function deserializeEvent($xml, $schemaName="--default--"){
		if (($xml instanceof SimpleXMLElement) && ($xml->getName() == "event")){
			$type = $xml->type."";
			$clazz = new ReflectionClass($type);
			$event = $clazz->newInstance();
			if ($event instanceof ImportableEvent){
				$event->setName($xml->name."");
				$event->setSendedFrom($xml->from."");
				$event->setLock($xml->lock."");
				return self::deserialize($event, $xml, $schemaName);
			}
		}
	}
	
	/**
	 * 
	 * @param Event $event
	 */
	public static function serializeEvent($event, $schemaName="--default--"){
		if ($event instanceof ExportableEvent){
			$xml = new SimpleXMLElement(self::serialize($event, $schemaName));
			if ($xml->getName() != "event"){
				throw new SerializerException("Hibás esemény sémaleíró: Az események alap node-jának mindig <event>-nek kell lennie.", null, 180);
			}
			$xml->addChild("name", $event->getName());
			$xml->addChild("sendto", $event->getSendTo());
			return self::xmlToString($xml);
		}
	}
	
	/**
	 * 
	 * @param SimpleXMLElement $xml
	 */
	private static function xmlToString($xml){ ///EZT MAJD VALAHOGY MÁSKÉPP KÉNE!!!!!!
		$res = "<".$xml->getName().">";
		foreach ($xml as $key => $value){
			if ($key == "content"){
				$res .= "<".$key."><![CDATA[".$value."]]></".$key.">";
			}else{
				$res .= "<".$key.">".$value."</".$key.">";
			}
		}
		$res .= "</".$xml->getName().">";
		return $res;
	}

}
?>