<?php
/**
 * AMFDeserializer takes the raw amf input stream and converts it PHP objects
 * representing the data.
 * 
 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
 * @copyright (c) 2003 amfphp.org
 * @package flashservices
 * @subpackage io
 * @version $Id$
 */

/**
 * Required classes
 */ 
require_once(AMFPHP_BASE . "shared/util/MessageBody.php");
require_once(AMFPHP_BASE . "shared/util/MessageHeader.php");
require_once(AMFPHP_BASE . "amf/util/DateWrapper.php");

class AMFBaseDeserializer {
	/**
	 * The raw data input
	 * 
	 * @access private 
	 * @var string 
	 */
	var $raw_data;

	/**
	 * The current seek cursor of the stream
	 * 
	 * @access private 
	 * @var int 
	 */
	var $current_byte;

	/**
	 * The length of the stream.  Since this class is not actually using a stream
	 * the entire content of the stream is passed in as the initial argument so the
	 * length can be determined.
	 * 
	 * @access private 
	 * @var int 
	 */
	var $content_length;
	
	/**
	 * The number of headers in the packet.
	 * 
	 * @access private 
	 * @var int 
	 */
	var $header_count;

	/**
	 * The content of the packet headers
	 * 
	 * @access private 
	 * @var string 
	 */
	var $headers;

	/**
	 * The number of bodys in the packet.
	 * 
	 * @access private 
	 * @var int 
	 */
	var $body_count;

	/**
	 * The content of the body elements
	 * 
	 * @access private 
	 * @var string 
	 */
	var $body;

	/**
	 * The object to store the amf data.
	 * 
	 * @access private 
	 * @var object 
	 */
	var $amfdata;

	/**
	 * The instance of the amfinput stream object
	 * 
	 * @access private 
	 * @var object 
	 */
	var $inputStream;
	
	/**
	 * metaInfo
	 */
	var $meta;
	
	var $storedStrings;
	var $storedObjects;
	var $storedDefinitions;
	var $amf0storedObjects;
	
	var $native;

	/**
	 * Constructor method for the deserializer.  Constructing the deserializer converts the input stream
	 * content to a AMFObject.
	 * 
	 * @param object $is The referenced input stream
	 */
	function AMFBaseDeserializer($rd) {
		$this->isBigEndian = AMFPHP_BIG_ENDIAN;
		$this->current_byte = 0;
		$this->raw_data = $rd;  // store the stream in this object
		$this->content_length = strlen($this->raw_data); // grab the total length of this stream
		$this->charsetHandler = new CharsetHandler('flashtophp');
		$this->storedStrings = array();
		$this->storedObjects = array();
		$this->storedDefinitions = array();
		$this->native = $GLOBALS['amfphp']['native'] && function_exists('amf_decode');
		$this->decodeFlags = (AMFPHP_BIG_ENDIAN*2) | 4;
	} 

	/**
	 * deserialize invokes this class to transform the raw data into valid object
	 * 
	 * @param object $amfdata The object to put the deserialized data in
	 */
	function deserialize (&$amfdata) {
		$time = microtime_float();
		$this->amfdata = &$amfdata;
		$this->readHeader(); // read the binary header
		$this->readBody(); // read the binary body
		if($this->decodeFlags & 1 == 1)
		{
			//AMF3 mode
			$GLOBALS['amfphp']['encoding'] = "amf3";
		}
		global $amfphp;
		$amfphp['decodeTime'] = microtime_float() - $time;
	} 
	/**
	 * returns the built AMFObject from the deserialization operation
	 * 
	 * @return object The deserialized AMFObject
	 */
	function getAMFObject() {
		return $this->amfdata;
	} 
	
	/**
	 * Decode callback is triggered when an object is encountered on decode
	 */
	function decodeCallback($event, $arg)
	{
		if($event == 1) //Object
		{
		   $type =$arg;
		   return $this->mapClass($type);
		}
		else if($event == 2) //Object post decode
		{
		   $obj = $arg;
		   if(method_exists($obj, 'init'))
		   {
		       $obj->init();
		   }
		   return $obj;
		} 
		else if($event == 3) //XML post-decode
		{
			return $arg;
		}
		else if($event == 4) //Serializable post-decode
		{
			if($type == 'flex.messaging.io.ArrayCollection' || $type == 'flex.messaging.io.ObjectProxy')
			{
			    return;
			}
			else
			{
			   trigger_error("Unable to read externalizable data type " . $type, E_USER_ERROR);
			   return "error";
			}
		}
		else if($event == 5) //ByteArray post decode
		{
			return new ByteArray($arg);
		}
	}

	/**
	 * readHeader converts that header section of the amf message into php obects.
	 * Header information typically contains meta data about the message.
	 */
	function readHeader() {

		$topByte = $this->readByte(); // ignore the first two bytes --  version or something
		$secondByte = $this->readByte(); //0 for Flash,
										 //1 for FlashComm                   
		//Disable debug events for FlashComm
		$GLOBALS['amfphp']['isFlashComm'] = $secondByte == 1;
		
		//If firstByte != 0, then the AMF data is corrupted, for example the transmission 
		//
		if(!($topByte == 0 || $topByte == 3))
		{
			trigger_error("Malformed AMF message, connection may have dropped");
			exit();
		}
		$this->header_count = $this->readInt(); //  find the total number of header elements
		while ($this->header_count--) { // loop over all of the header elements
			$name = $this->readUTF();
			$required = $this->readByte() == 1; // find the must understand flag
			//$length   = $this->readLong(); // grab the length of  the header element
			$this->current_byte += 4; // grab the length of the header element
			if($this->native)
			{
			    $content = amf_decode($this->raw_data, $this->decodeFlags, $this->current_byte, array(& $this, "decodeCallback"));
			}
			else
			{
			    $type = $this->readByte();  // grab the type of the element
			    $content = $this->readData($type); // turn the element into real data
			}
			
			$this->amfdata->addHeader(new MessageHeader($name, $required, $content)); // save the name/value into the headers array
		}

	} 

	/**
	 * readBody converts the payload of the message into php objects.
	 */
	function readBody() {
		$this->body_count = $this->readInt(); // find the total number  of body elements
		while ($this->body_count--) { // loop over all of the body elements
			
			$this->amf0storedObjects = array();
			$this->storedStrings = array();
			$this->storedObjects = array();
			$this->storedDefinitions = array();
			
			$target = $this->readUTF();
			$response = $this->readUTF(); //    the response that the client understands
			
			//$length = $this->readLong(); // grab the length of    the body element
			$this->current_byte += 4;
			
			if($this->native)
			   $data = amf_decode($this->raw_data, $this->decodeFlags, $this->current_byte, array(& $this, "decodeCallback"));
			else
			{
			   $type = $this->readByte();  // grab the type of the element
			   $data = $this->readData($type); // turn the element into real data
			}
			
			$this->amfdata->addBody(new MessageBody($target, $response, $data)); // add the body element to the body object
			
		} 
	} 
	
	/********************************************************************************
	 *                       This used to be in AmfInputStream
	 ********************************************************************************

	/**
	 * readByte grabs the next byte from the data stream and returns it.
	 * 
	 * @return int The next byte converted into an integer
	 */
	function readByte() {
		return ord($this->raw_data[$this->current_byte++]); // return the next byte
	}

	/**
	 * readInt grabs the next 2 bytes and returns the next two bytes, shifted and combined
	 * to produce the resulting integer
	 * 
	 * @return int The resulting integer from the next 2 bytes
	 */
	function readInt() {
		return ((ord($this->raw_data[$this->current_byte++]) << 8) |
			ord($this->raw_data[$this->current_byte++])); // read the next 2 bytes, shift and add
	} 

	/**
	 * readUTF first grabs the next 2 bytes which represent the string length.
	 * Then it grabs the next (len) bytes of the resulting string.
	 * 
	 * @return string The utf8 decoded string
	 */
	function readUTF() {
		$length = $this->readInt(); // get the length of the string (1st 2 bytes)
		//BUg fix:: if string is empty skip ahead
		if($length == 0)
		{
			return "";
		}
		else
		{
			$val = substr($this->raw_data, $this->current_byte, $length); // grab the string
			$this->current_byte += $length; // move the seek head to the end of the string
			return $this->charsetHandler->transliterate($val); // return the string
		}
	} 
	
	/**
	 * readLong grabs the next 4 bytes shifts and combines them to produce an integer
	 * 
	 * @return int The resulting integer from the next 4 bytes
	 */
	function readLong() {
		return ((ord($this->raw_data[$this->current_byte++]) << 24) |
			(ord($this->raw_data[$this->current_byte++]) << 16) |
			(ord($this->raw_data[$this->current_byte++]) << 8) |
			ord($this->raw_data[$this->current_byte++])); // read the next 4 bytes, shift and add
	} 

	/**
	 * readDouble reads the floating point value from the bytes stream and properly orders
	 * the bytes depending on the system architecture.
	 * 
	 * @return float The floating point value of the next 8 bytes
	 */
	function readDouble() {
		$bytes = substr($this->raw_data, $this->current_byte, 8);
		$this->current_byte += 8;
		if ($this->isBigEndian) {
			 $bytes = strrev($bytes);
		} 
		$zz = unpack("dflt", $bytes); // unpack the bytes
		return $zz['flt']; // return the number from the associative array
	} 

	/**
	 * readLongUTF first grabs the next 4 bytes which represent the string length.
	 * Then it grabs the next (len) bytes of the resulting in the string
	 * 
	 * @return string The utf8 decoded string
	 */
	function readLongUTF() {
		$length = $this->readLong(); // get the length of the string (1st 4 bytes)
		$val = substr($this->raw_data, $this->current_byte, $length); // grab the string
		$this->current_byte += $length; // move the seek head to the end of the string
		return $this->charsetHandler->transliterate($val); // return the string
	} 
    
    function mapClass($typeIdentifier)
    {
		//Check out if class exists
		if($typeIdentifier == "")
		{
			return NULL;
		}
		$clazz = NULL;
		$mappedClass = str_replace('.', '/', $typeIdentifier);
		
		if($typeIdentifier == "flex.messaging.messages.CommandMessage")
		{
			return new CommandMessage();
		}
		if($typeIdentifier == "flex.messaging.messages.RemotingMessage")
		{
			return new RemotingMessage();
		}
		
		if(isset($GLOBALS['amfphp']['incomingClassMappings'][$typeIdentifier]))
		{
			$mappedClass = str_replace('.', '/', $GLOBALS['amfphp']['incomingClassMappings'][$typeIdentifier]);
		}
		
		$include = FALSE;
		if(file_exists($GLOBALS['amfphp']['customMappingsPath'] . $mappedClass . '.php'))
		{
			$include = $GLOBALS['amfphp']['customMappingsPath'] . $mappedClass . '.php';
		}
		elseif(file_exists($GLOBALS['amfphp']['customMappingsPath'] . $mappedClass . '.class.php'))
		{
			$include = $GLOBALS['amfphp']['customMappingsPath'] . $mappedClass . '.class.php';
		}
		
		if($include !== FALSE)
		{
			include_once($include);
			$lastPlace = strrpos('/' . $mappedClass, '/');
			$classname = substr($mappedClass, $lastPlace);
			if(class_exists($classname))
			{
				$clazz = new $classname;
			}
		}
		
		return $clazz; // return the object
    }
} 

?>