<?php
namespace Engine\Serializer\MagicMethods;

/**
 * Used by \Engine\Serializer\ImportedOrders
 * @author Lucian Sirbu
 */
abstract class CustomAbstract
{
	// used by \Engine\Serializer\ImportedOrders

	/**
	 * The "magic" function that has to be implemented by each class
	 * @param array $serializedObjects
	 * @return mixed
	 */
	public function _getSerializedRepresentation(&$serializedObjects)
	{
		return $this->_getSerializedRepresentation_object($this, $serializedObjects);
	}

	/**
	 * Custom call. Should be implemented in extending classes. Used to serialize a custom object. Should fill in necessary data in $ret and return it
	 * @param array $ret
	 * @param mixed $object
	 * @return mixed
	 */
	protected function serializeUnknownObject($ret, $object)
	{
		return null;
	}


	/**
	 * The "magic" function that has to be implemented by each class
	 * @param mixed $data
	 * @param array $serializedObjects
	 */
	public function _setSerializedRepresentation($data, &$serializedObjects)
	{
		$this->_setSerializedRepresentation_object($this, $data, $serializedObjects);
	}

	/**
	 * Custom call. Should be implemented in extending classes. Used to unserialize a custom object. Should fill in necessary data in $ret and return it
	 * @param mixed $object
	 * @param mixed $attributes
	 * @return mixed
	 */
	protected function unserializeUnknownObject($object, $attributes)
	{
		return null;
	}



	/**
	 * Internal call. Get the serialized reprezentation for a scalar
	 * @param mixed $data
	 * @return mixed
	 */
	protected function _getSerializedRepresentation_scalar($data)
	{
		return $data;
	}

	/**
	 * Internal call. Get the serialized reprezentation for an array
	 * @param mixed $data
	 * @param array $serializedObjects
	 * @return mixed
	 */
	protected function _getSerializedRepresentation_array($data, &$serializedObjects)
	{
		$ret = [
			'$%t*' => '$%a',
			'$%m*' => [],
		];
		foreach ($data as $k=>$v) {
			if (is_object($v)) {
				$ret['$%m*'][$k] = $this->_getSerializedRepresentation_object($v, $serializedObjects);
			}
			elseif (is_array($v)) {
				$ret['$%m*'][$k] = $this->_getSerializedRepresentation_array($v, $serializedObjects);
			}
			else {
				$ret['$%m*'][$k] = $this->_getSerializedRepresentation_scalar($v);
			}
		}

		return $ret;
	}

	/**
	 * Internal call. Get the serialized reprezentation for an object
	 * @param mixed $data
	 * @param array $serializedObjects
	 * @return mixed
	 */
	protected function _getSerializedRepresentation_object($obj, &$serializedObjects)
	{
		$ret = [
			'$%t*' => '$%o',
			'$%c*' => get_class($obj),
		];

		$isReference = false;
		$referenceIndex = null;
		foreach ($serializedObjects as $idx=>$so) {
			if($so===$obj) {
				$isReference = true;
				$referenceIndex = $idx;
				break;
			}
		}

		if ($isReference) {
			$ret = [
				'$%t*' => '$%o',
				'$%c*' => get_class($obj),
				'$%r*' => $referenceIndex,
			];
			return $ret;
		}
		else {
			if ($obj instanceof \Engine\Serializer\MagicMethods\CustomAbstract) {
				$ret['$%a*'] = [];
				foreach ($obj as $k=>$v) {
					if (is_object($v)) {
						$ret['$%a*'][$k] = $this->_getSerializedRepresentation_object($v, $serializedObjects);
					}
					elseif (is_array($v)) {
						$ret['$%a*'][$k] = $this->_getSerializedRepresentation_array($v, $serializedObjects);
					}
					else {
						$ret['$%a*'][$k] = $this->_getSerializedRepresentation_scalar($v);
					}
				}
			}
			else {
				$ret = $this->serializeUnknownObject($ret, $obj);
				if ($ret===null) {
					throw new \Exception(sprintf("[%s]Class %s needs to extend %s. Parent class: %s", __METHOD__, get_class($obj), __CLASS__, get_class($this)));
				}

				if ($ret['skip']) {
					return null;
				}
			}

			$serializedObjects[] = $obj;
		}

		return $ret;
	}





	/**
	 * Internal call
	 * @param mixed $scalar
	 * @return mixed
	 */
	protected function _setSerializedRepresentation_scalar($scalar)
	{
		return $scalar;
	}

	/**
	 * Internal call
	 * @param mixed $data
	 * @param array $serializedObjects
	 * @throws \Exception
	 * @return mixed
	 */
	protected function _setSerializedRepresentation_array($data, &$serializedObjects)
	{
		$ret = [];
		$o = null;
		foreach($data['$%m*'] as $k=>$v) {
			if (is_array($v) && array_key_exists('$%t*', $v) && $v['$%t*']=='$%o' && array_key_exists('$%c*', $v)) {	// is_object
				if (array_key_exists('$%a*', $v)) {
					$o = new $v['$%c*']();

					if ($o instanceof \Engine\Serializer\MagicMethods\CustomAbstract) {
						$o->_setSerializedRepresentation($v, $serializedObjects);
					}
					else {
						throw new \Exception(sprintf("[%s] Class needs to extend %s", __METHOD__, __CLASS__));
					}

					$serializedObjects[] = $o;
				}
				elseif (array_key_exists('$%r*', $v)) {
					$o = $serializedObjects[$v['$%r*']];
				}
				else {
					$o = $this->unserializeUnknownObject($o, $v);
					if ($o===null) {
						throw new \Exception("Invalid object declaration found");
					}
					# $serializedObjects[] = $o; // not sure why , but this line seems to never get executed
				}
				$v = $o;
			}
			if (is_array($v) && array_key_exists('$%t*', $v) && $v['$%t*']=='$%a' && array_key_exists('$%m*', $v)) {	// is_object
				$arr = $this->_setSerializedRepresentation_array($v, $serializedObjects);
				$v = $arr;
			}
			elseif (is_array($v)) {	// how come this is still array? is_array returned true
				throw new \Exception("this is an array, but we dont know how it got encoded like this");
			}
			else {
				$v = $this->_setSerializedRepresentation_scalar($v);
			}

			$ret[$k] = $v;
		}
		return $ret;
	}

	/**
	 * Internal call
	 * @param mixed $obj
	 * @param mixed $data
	 * @param array $serializedObjects
	 * @throws \Exception
	 * @return mixed
	 */
	protected function _setSerializedRepresentation_object($obj, $data, &$serializedObjects)
	{
		/**
		 * NOTE: if we do "foreach $this", we will ignore properties from the serialized data that are not present in the current class
		 * This means that removed properties are not handled in any way, they are simply ignored
		 * This would also mean that we wont correctly handle "dynamic members" (members forcefully injected into an object when they dont actually belong to that object)
		 * 		foreach ($this as $k=>$x) {
		 */

		/**
		 * NOTE: because we do foreach "attributes", if the unserialized object contains attributes that arent present anymore in the unserialized class,
		 * 	we will "inject" the old attributes forcefully in the new object
		 *
		 * This will treat "dynamic members" correctly
		 */
		$o = null;
		foreach ($data['$%a*'] as $k=>$x) {	// $x is ignored, we dont use the current assigned value when unserializing
			if (array_key_exists($k, $data['$%a*'])) {
				$v = $data['$%a*'][$k];

				if (is_array($v) && array_key_exists('$%t*', $v) && $v['$%t*']=='$%o' && array_key_exists('$%c*', $v)) {	// is_object
					if (array_key_exists('$%a*', $v)) {
						$o = new $v['$%c*']();

						if ($o instanceof \Engine\Serializer\MagicMethods\CustomAbstract) {
							$o->_setSerializedRepresentation($v, $serializedObjects);
						}
						else {
							throw new \Exception(sprintf("[%s] Class %s needs to extend %s", __METHOD__, get_class($o), __CLASS__));
						}

						$serializedObjects[] = $o;
					}
					elseif (array_key_exists('$%r*', $v)) {
						$o = $serializedObjects[$v['$%r*']];
					}
					else {
						$o = $this->unserializeUnknownObject($o, $v);
						if ($o===null) {
							throw new \Exception(sprintf("[%s] Class %s needs to extend %s, or %s needs to implement unserializeUnknownObject()", __METHOD__, $v['$%c*'], __CLASS__, get_class($this)));
						}

						$serializedObjects[] = $o;
					}

					$v = $o;
				}
				elseif (is_array($v) && array_key_exists('$%t*', $v) && $v['$%t*']=='$%a' && array_key_exists('$%m*', $v)) {	// is_array
					$v = $this->_setSerializedRepresentation_array($v, $serializedObjects);
				}
				elseif (is_array($v)) {
					throw new \Exception("this is an array, but we dont know how it got encoded like this");
				}
				else {
					$v = $this->_setSerializedRepresentation_scalar($v);	// scalar assignmment
				}

				if (property_exists($obj, $k)) {
					$obj->{$k} = $v;
				}
				// else {
				// 	// property not exists in the TINA class definition anymore
				// }
			}
			/*
			else {
				// Found property difference between the 2 objects. Not sure this is expected, but unserialize doesn't care for these type of changes to the class
			}
			*/
		}

		return $obj;
	}

}
