<?php
/**
 * AMFSerializer manages the job of translating PHP objects into
 * the actionscript equivalent via amf.  The main method of the serializer
 * is the serialize method which takes and AMFObject as it's argument
 * and builds the resulting amf body.
 * 
 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
 * @copyright (c) 2003 amfphp.org
 * @package flashservices
 * @subpackage io
 * @version $Id: AMFSerializer.php,v 1.39 2005/07/22 10:58:11 pmineault Exp $
 */

define("MAX_STORED_OBJECTS", 1024);

include_once(AMFPHP_BASE . "amf/io/AMFBaseSerializer.php");

class AMFSerializer extends AMFBaseSerializer {

	/**
	 * Classes that are serialized as recordsets
	 */                         
   var $amf0StoredObjects = array();
   var $storedObjects = array();
   var $storedDefinitions = 0;
   var $storedStrings = array();
   var $outBuffer;
   var $encounteredStrings = array();
   
   var $native = false;

	/**
	 * AMFSerializer is the constructor function.  You must pass the
	 * method an AMFOutputStream as the single argument.
	 * 
	 * @param object $stream The AMFOutputStream
	 */
	function AMFSerializer() {
		AMFBaseSerializer::AMFBaseSerializer();
	} 

	/**
	 * writeBoolean writes the boolean code (0x01) and the data to the output stream
	 * 
	 * @param bool $d The boolean value
	 */
	function writeBoolean($d) {
		$this->writeByte(1); // write the boolean flag
		$this->writeByte($d); // write  the boolean byte
	} 

	/**
	 * writeString writes the string code (0x02) and the UTF8 encoded
	 * string to the output stream.
	 * Note: strings are truncated to 64k max length. Use XML as type 
	 * to send longer strings
	 * 
	 * @param string $d The string data
	 */
	function writeString($d) {
		$count = strlen($d);
		if($count < 65536)
		{
			$this->writeByte(2);
			$this->writeUTF($d);
		}
		else
		{
			$this->writeByte(12);
			$this->writeLongUTF($d);
		}
	}
	
	/**
	 * writeXML writes the xml code (0x0F) and the XML string to the output stream
	 * Note: strips whitespace
	 * @param string $d The XML string
	 */
	function writeXML($d) {
		if(!$this->writeReferenceIfExists($d))
		{
			$this->writeByte(15);
			$this->writeLongUTF(preg_replace('/\>(\n|\r|\r\n| |\t)*\</','><',trim($d)));
		}
	} 

	/**
	 * writeData writes the date code (0x0B) and the date value to the output stream
	 * 
	 * @param date $d The date value
	 */
	function writeDate($d) {
		$this->writeByte(11); // write  date code
		$this->writeDouble($d); //  write date (milliseconds from 1970)
		/**
		 * write timezone
		 * ?? this is wierd -- put what you like and it pumps it back into flash at the current GMT ??
		 * have a look at the amf it creates...
		 */
		$this->writeInt(0);
	}

	/**
	 * writeNumber writes the number code (0x00) and the numeric data to the output stream
	 * All numbers passed through remoting are floats.
	 * 
	 * @param int $d The numeric data
	 */
	function writeNumber($d) {
		$this->writeByte(0); // write the number code
		$this->writeDouble(floatval($d)); // write  the number as a double
	} 

	/**
	 * writeNull writes the null code (0x05) to the output stream
	 */
	function writeNull() {
		$this->writeByte(5); // null is only a  0x05 flag
	} 

	/**
	 * writeArray first deterines if the PHP array contains all numeric indexes
	 * or a mix of keys.  Then it either writes the array code (0x0A) or the
	 * object code (0x03) and then the associated data.
	 * 
	 * @param array $d The php array
	 */
	function writeArray($d) 
	{
		if($this->writeReferenceIfExists($d))
		{
			return;
		}
		
		$numeric = array(); // holder to store the numeric keys
		$string = array(); // holder to store the string keys
		$len = count($d); // get the total number of entries for the array
		$largestKey = -1;
		foreach($d as $key => $data) { // loop over each element
			if (is_int($key) && ($key >= 0)) { // make sure the keys are numeric
				$numeric[$key] = $data; // The key is an index in an array
				$largestKey = max($largestKey, $key);
			} else {
				$string[$key] = $data; // The key is a property of an object
			} 
		} 
		$num_count = count($numeric); // get the number of numeric keys
		$str_count = count($string); // get the number of string keys

		if ( ($num_count > 0 && $str_count > 0) || 
			 ($num_count > 0 && $largestKey != $num_count - 1)) { // this is a mixed array
			
			$this->writeByte(8); // write the mixed array code
			$this->writeLong($num_count); // write  the count of items in the array
			$this->writeObjectFromArray($numeric + $string); // write the numeric and string keys in the mixed array
		} else if ($num_count > 0) { // this is just an array
			
			$num_count = count($numeric); // get the new count
			
			$this->writeByte(10); // write  the array code
			$this->writeLong($num_count); // write  the count of items in the array
			for($i = 0 ; $i < $num_count ; $i++) { // write all of the array elements
				$this->writeData($numeric[$i]);
			} 
		} else if($str_count > 0) { // this is an object
			$this->writeByte(3); // this is an  object so write the object code
			$this->writeObjectFromArray($string); // write the object name/value pairs
		} else { //Patch submitted by Jason Justman
			
			$this->writeByte(10); // make this  an array still
			$this->writeInt(0); //  give it 0 elements
			$this->writeInt(0); //  give it an element pad, this looks like a bug in Flash,
											//but keeps the next alignment proper
		}
	}
	
	function writeReferenceIfExists($d)
	{
		if(count($this->amf0StoredObjects) >= MAX_STORED_OBJECTS)
		{
			return false;
		}
		if(is_array($d))
		{
			$this->amf0StoredObjects[] = "";
			return false;
		}
		if(($key = patched_array_search($d, $this->amf0StoredObjects, true)) !== FALSE)
		{
			$this->writeReference($key);
			return true;
		}
		else
		{
			$this->amf0StoredObjects[] = & $d;
			return false;
		}
	}
	
	function writeReference($num)
	{
		$this->writeByte(0x07);
		$this->writeInt($num);
	}
	
	/**
	 * Write a plain numeric array without anything fancy
	 */
	function writePlainArray($d)
	{
		if(!$this->writeReferenceIfExists($d))
		{
			$num_count = count($d);
			$this->writeByte(10); // write  the mixed array code
			$this->writeLong($num_count); // write  the count of items in the array
			for($i = 0 ; $i < $num_count ; $i++) { // write all of the array elements
				$this->writeData($d[$i]);
			}
		} 
	}

	/**
	 * writeObject handles writing a php array with string or mixed keys.  It does
	 * not write the object code as that is handled by the writeArray and this method
	 * is shared with the CustomClass writer which doesn't use the object code.
	 * 
	 * @param array $d The php array with string keys
	 */
	function writeObjectFromArray($d) {
		foreach($d as $key => $data) { // loop over each element
			$this->writeUTF($key);  // write the name of the object
			$this->writeData($data); // write the value of the object
		} 
		$this->writeInt(0); //  write the end object flag 0x00, 0x00, 0x09
		$this->writeByte(9);
	} 
	
	/**
	 * writeObject handles writing a php array with string or mixed keys.  It does
	 * not write the object code as that is handled by the writeArray and this method
	 * is shared with the CustomClass writer which doesn't use the object code.
	 * 
	 * @param array $d The php array with string keys
	 */
	function writeAnonymousObject($d) {
		if(!$this->writeReferenceIfExists($d))
		{
			$this->writeByte(3);
			$objVars = (array) $d;
			foreach($d as $key => $data) { // loop over each element
				if($key[0] != "\0")
				{
					$this->writeUTF($key);  // write the name of the object
					$this->writeData($data); // write the value of the object
				}
			} 
			$this->writeInt(0); //  write the end object flag 0x00, 0x00, 0x09
			$this->writeByte(9);
		}
	} 

	/**
	 * writePHPObject takes an instance of a class and writes the variables defined
	 * in it to the output stream.
	 * To accomplish this we just blanket grab all of the object vars with get_object_vars
	 * 
	 * @param object $d The object to serialize the properties
	 */
	function writeTypedObject($d) {
		if($this->writeReferenceIfExists($d))
		{
			return;
		}
		
		$this->writeByte(16); // write  the custom class code
		$classname = $this->getClassName($d);
		
		$this->writeUTF($classname); // write the class name
		if(AMFPHP_PHP5)
		{
			$objVars = $d;
		}
		else
		{
			$objVars = (array) $d;
		}
		foreach($objVars as $key => $data) { // loop over each element
			if($key[0] != "\0")
			{
				$this->writeUTF($key);  // write the name of the object
				$this->writeData($data); // write the value of the object
			}
		} 
		$this->writeInt(0); //  write the end object flag 0x00, 0x00, 0x09
		$this->writeByte(9);
	} 

	/**
	 * writeRecordSet is the abstracted method to write a custom class recordset object.
	 * Any recordset from any datasource can be written here, it just needs to be properly formatted
	 * beforehand.
	 *
	 * This was unrolled with at the expense of readability for a 
	 * 10 fold increase in speed in large recordsets
	 * 
	 * @param object $rs The formatted RecordSet object
	 */
	 
	function writeRecordSet(&$rs) 
	{
		//Low-level everything here to make things faster
		//This is the bottleneck of AMFPHP, hence the attention in making things faster
		if($this->writeReferenceIfExists($rs))
		{
			return;
		}
		
		$ob = "";
		$data = $rs->rows;
		
		if($GLOBALS['amfphp']['encoding'] == 'amf0')
		{
			
			$this->writeByte(16); // write  the custom class code
			$this->writeUTF("RecordSet"); // write  the class name
			$this->writeUTF("serverInfo");
			
			//Start writing inner object
			$this->writeByte(3); // this is an  object so write the object code
			
			//Write total count
			$this->writeUTF("totalCount");
			$this->writeNumber($rs->getRowCount());
			
			//Write initial data
			$this->writeUTF("initialData");
			
			//Inner numeric array
			$colnames = $rs->columns;
			
			$num_count = count($rs->rows);
			$this->writeByte(10); // write  the mixed array code
			$this->writeLong($num_count); // write  the count of items in the array
	
			//Allow recordsets to create their own serialized data, which is faster
			//since the recordset array is traversed only once
			$numcols = count($colnames);
			
			$ob = "";
			$be = $this->isBigEndian;
			$fc = pack('N', $numcols);
			
			for($i = 0 ; $i < $num_count ; $i++) 
			{ 
				// write all of the array elements
				$ob .= "\12" . $fc;

				for($j = 0; $j < $numcols; $j++) { // write all of the array elements
					
					$d = $data[$i][$j];
					if (is_string($d)) 
					{ // type as string
						$os = $this->rsCharsetHandler->transliterate($d);
						//string flag, string length, and string
						$ob .= "\2" . pack('n', strlen($os)) . $os;
					}
					elseif (is_float($d) || is_int($d)) 
					{ // type as double
						$ob .= "\0";
						$b = pack('d', $d); // pack the bytes
						if ($be) { // if we are a big-endian processor
							$r = strrev($b);
						} else { // add the bytes to the output
							$r = $b;
						} 
						$ob .= $r;
					} 
					elseif (is_bool($d)) 
					{ //type as bool
						$ob .= "\1";
						$ob .= pack('c', $d);
					} 
					elseif (is_null($d)) 
					{ // null
						$ob .= "\5";
					} 
				} 
			}
			$this->outBuffer .= $ob;
	
			//Write cursor
			$this->writeUTF("cursor");
			$this->writeNumber(1);
			
			//Write service name
			$this->writeUTF("serviceName");
			$this->writeString("PageAbleResult");
			
			//Write column names
			$this->writeUTF("columnNames");
			$this->writePlainArray($colnames, 'string');
			
			//Write version number
			$this->writeUTF("version");
			$this->writeNumber(1);
			
			//Write id
			$this->writeUTF("id");
			$this->writeString($rs->getID());
			
			//End inner serverInfo object
			$this->writeInt(0); //  write the end object flag 0x00, 0x00, 0x09
			$this->writeByte(9);
			
			//End outer recordset object
			$this->writeInt(0); //  write the end object flag 0x00, 0x00, 0x09
			$this->writeByte(9);
			
			$this->paging = -1;
		}
		else
		{	
			$numObjects= 0;
			$this->writeAmf3ArrayCollectionPreamble();
			
			//Amf3 array code
			$this->writeByte(0x09);
			$numObjects++;
			
			$numRows = count($rs->rows);
			$toPack = 2*$numRows + 1;
			
			//Write the number of rows
			$this->writeAmf3Int($toPack);
			
			//No string keys in this array
			$this->writeByte(0x01);
			
			$numCols = count($rs->columns);
			
			$columnStringOffsets = array();
			if($numRows > 0)
			{
				$j = 0;
				$colNames = array();
				$rows = $rs->rows;
				
				foreach ($rows as $key => $line) {
					
					//Usually we don't use class defs in the serializer since we don't 
					//have sealed objects in php, but for recordsets we do use them
					//since they are well suited for what we have to do (the same keys
					//across all objects)
					if($key == 0)
					{
						$this->outBuffer .= "\12";
						$this->writeAmf3Int($numCols << 4 | 3);
						$this->outBuffer .= "\1";
						foreach($rs->columns as $key => $val)
						{
							$this->writeAmf3String($val);
						}
						$defOffset = $this->getAmf3Int(
							($this->storedDefinitions) << 2 | 1
							);
						$this->storedDefinitions++;
					}
					else
					{
						$this->outBuffer .= "\12" . $defOffset;
					}
					$numObjects++;
					
					for($i = 0; $i < $numCols; $i++)
					{
						//Write the col name
						$value = $line[$i];
						if(is_string($value))
						{
							$this->outBuffer .= "\6";
							$value = $this->rsCharsetHandler->transliterate($value);
							$this->writeAmf3String($value, true);
						}
						elseif(is_int($value)) 
						{ //int
							$this->writeAmf3Number($value);
						} 
						elseif(is_float($value))
						{ //double
							$this->outBuffer .= "\5";
							$b = pack("d", $value); // pack the bytes
							if ($this->isBigEndian) { // if we are a big-endian processor
								$r = strrev($b);
							} else { // add the bytes to the output
								$r = $b;
							} 
							
							$this->outBuffer .= $r;
						}
						elseif(is_bool($value))
						{
							$this->outBuffer .= $value ? "\3" : "\2";
						}
						else
						{
							$this->outBuffer .= "\1"; //null
						}
					}
					//End object
				}
			}
			
			//Add fake objects to make sure the object counter still works
			for($i = 0; $i < $numObjects; $i++)
			{
				$this->storedObjects[] = "";
			}
		}
	}

	/**
	 * writeData checks to see if the type was declared and then either
	 * auto negotiates the type or relies on the user defined type to
	 * serialize the data into amf
	 *
	 * Note that autoNegotiateType was eliminated in order to tame the 
	 * call stack which was getting huge and was causing leaks
	 *
	 * manualType allows the developer to explicitly set the type of
	 * the returned data.  The returned data is validated for most of the
	 * cases when possible.  Some datatypes like xml and date have to
	 * be returned this way in order for the Flash client to correctly serialize them
	 * 
	 * recordsets appears top on the list because that will probably be the most
	 * common hit in this method.  Followed by the
	 * datatypes that have to be manually set.  Then the auto negotiatable types last.
	 * The order may be changed for optimization.
	 * 
	 * @param mixed $d The data
	 * @param string $type The optional type
	 */
	function writeData(& $d) {
		if (is_int($d) || is_float($d)) 
		{ // double
			$this->writeNumber($d);
			return;
		} 
		elseif (is_string($d)) 
		{ // string
			$this->writeString($d);
			return;
		} 
		elseif (is_bool($d)) 
		{ // boolean
			$this->writeBoolean($d);
			return;
		} 
		elseif (is_null($d)) 
		{ // null
			$this->writeNull();
			return;
		} 
		elseif ($GLOBALS['amfphp']['encoding'] == 'amf3')
		{
			$this->writeByte(0x11);
			$this->writeAmf3Data($d);
			return;
		}
		elseif (is_array($d)) 
		{ // array
			$this->writeArray($d);
			return;
		} 
		elseif (is_resource($d)) 
		{ // resource
			$type = get_resource_type($d);
			list($type, $subtype) = $this->sanitizeType($type);
		} 
		elseif (is_object($d))
		{
			$className = strtolower(get_class($d));
			if(array_key_exists($className, $this->resourceObjects))
			{
				$type = "__RECORDSET__";
				$subtype = $this->resourceObjects[strtolower(get_class($d))];
			}
			else if(AMFPHP_PHP5 && $className == 'domdocument')
			{
				$this->writeXML($d->saveXml());
				return;
			}
			else if(!AMFPHP_PHP5 && $className == 'domdocument')
			{
				$this->writeXML($d->dump_mem());
				return;
			}
			elseif($className == "simplexmlelement")
			{
				$this->writeXML($d->asXML());
				return;
			}
			else if($className == 'stdclass' && !isset($d->_explicitType))
			{
				$this->writeAnonymousObject($d);
				return;
			}
			elseif(is_a($d, 'ArrayAccess') || is_a($d, 'ArrayObject'))
			{
				$this->writeArray($d);
				return;
			}
			else
			{
				$this->writeTypedObject($d);
				return;
			}
		}
		else
		{
			$type = gettype($d);
		}
		
		switch ($type) {
			case "__RECORDSET__" :
				$classname = $subtype . "Adapter"; // full class name
				$includeFile = include_once(AMFPHP_BASE . "shared/adapters/" . $classname . ".php"); // try to load the recordset library from the sql folder
				if (!$includeFile) {
					trigger_error("The recordset filter class " . $classname . " was not found", E_USER_ERROR);
				} 
				$recordSet = new $classname($d); // returns formatted recordset
				$this->writeRecordSet($recordSet); // writes the recordset formatted for Flash
				break;
			default: 
				// non of the above so lets assume its a Custom Class thats defined in the client
				$this->writeTypedObject($unsanitizedType, $d);
				// trigger_error("Unsupported Datatype");
				break;
		} 
	}
	
	/********************************************************************************
	 *                             AMF3 related code
	 *******************************************************************************/
	
	function writeAmf3Data(& $d)
	{
		if (is_int($d)) 
		{ //int
			$this->writeAmf3Number($d);
			return;
		} 
		elseif(is_float($d))
		{ //double
			$this->outBuffer .= "\5";
			$this->writeDouble($d);
			return;
		}
		elseif (is_string($d)) 
		{ // string
			$this->outBuffer .= "\6";
			$this->writeAmf3String($d);
			return;
		} 
		elseif (is_bool($d)) 
		{ // boolean
			$this->writeAmf3Bool($d);
			return;
		} 
		elseif (is_null($d)) 
		{ // null
			$this->writeAmf3Null();
			return;
		} 
		elseif (is_array($d)) 
		{ // array
			$this->writeAmf3Array($d);
			return;
		} 
		elseif (is_resource($d)) 
		{ // resource
			$type = get_resource_type($d);
			list($type, $subtype) = $this->sanitizeType($type);
		} 
		elseif (is_object($d))
		{
			$className = strtolower(get_class($d));
			if(array_key_exists($className, $this->resourceObjects))
			{
				$type = "__RECORDSET__";
				$subtype = $this->resourceObjects[strtolower(get_class($d))];
			}
			else if(AMFPHP_PHP5 && $className == 'domdocument')
			{
				$this->writeAmf3Xml($d->saveXml());
				return;
			}
			else if(!AMFPHP_PHP5 && $className == 'domdocument')
			{
				$this->writeAmf3Xml($d->dump_mem());
				return;
			}
			elseif($className == "simplexmlelement")
			{
				$this->writeAmf3Xml($d->asXML());
				return;
			}
			elseif($className == 'bytearray')
			{
				$this->writeAmf3ByteArray($d->data);
				return;
			}
			elseif(is_a($d, 'ArrayAccess') || is_a($d, 'ArrayObject'))
			{
				$this->writeAmf3Array($d, true);
				return;
			}
			else
			{
				$this->writeAmf3Object($d);
				return;
			}
		}
		else
		{
			$type = gettype($d);
		}
		
		switch ($type) {
			case "__RECORDSET__" :
				$classname = $subtype . "Adapter"; // full class name
				$includeFile = include_once(AMFPHP_BASE . "shared/adapters/" . $classname . ".php"); // try to load the recordset library from the sql folder
				if (!$includeFile) {
					trigger_error("The recordset filter class " . $classname . " was not found");
				} 
				$GLOBALS['amfphp']['stringOffset'] = count($this->storedStrings);
				$recordSet = new $classname($d); // returns formatted recordset
				
				$this->writeRecordSet($recordSet); // writes the recordset formatted for Flash
				break;
			default: 
				// non of the above so lets assume its a Custom Class thats defined in the client
				//$this->writeTypedObject($unsanitizedType, $d);
				trigger_error("Unsupported Datatype: " . $type);
				break;
		} 
	}
	
	/**
	 * Write an ArrayCollection
	 */
	function writeAmf3ArrayCollectionPreamble()
	{
		$this->writeByte(0x0a);
		$this->writeByte(0x07);
		$this->writeAmf3String("flex.messaging.io.ArrayCollection");
		$this->storedDefinitions++;
		$this->storedObjects[] = "";
	}

	function writeAmf3Null()
	{
		//Write the null code (0x1) to the output stream.
		$this->outBuffer .= "\1";
	}

	function writeAmf3Bool($d)
	{
		$this->outBuffer .= $d ? "\3" : "\2";
	}
	
	function writeAmf3Int($d)
	{
		//Sign contraction - the high order bit of the resulting value must match every bit removed from the number
		//Clear 3 bits 
		$d &= 0x1fffffff;
		if($d < 0x80)
		{
			$this->outBuffer .= chr($d);
		}
		elseif($d < 0x4000)
		{
			$this->outBuffer .= chr($d >> 7 & 0x7f | 0x80) . chr($d & 0x7f);
		}
		elseif($d < 0x200000)
		{
			$this->outBuffer .= chr($d >> 14 & 0x7f | 0x80) . chr($d >> 7 & 0x7f | 0x80) . chr($d & 0x7f);
		} 
		else
		{
			$this->outBuffer .= chr($d >> 22 & 0x7f | 0x80) . chr($d >> 15 & 0x7f | 0x80) . 
				   chr($d >> 8 & 0x7f | 0x80) . chr($d & 0xff);
		}
	}

	function writeAmf3String($d, $raw = false)
	{
		if( $d == "" )
		{
			//Write 0x01 to specify the empty ctring
			$this->outBuffer .= "\1";
		}
		else
		{
			if( !isset($this->storedStrings[$d]))
			{
				if(strlen($d) < 64)
				{
					$this->storedStrings[$d] = $this->encounteredStrings;
				}
				if(!$raw)
				{
					$d = $this->charsetHandler->transliterate($d);
				}
				
				$handle = strlen($d);
				$this->writeAmf3Int($handle*2 + 1);
				$this->outBuffer .= $d;
				$this->encounteredStrings++;
				return $this->encounteredStrings - 1;
			}
			else
			{
				$key = $this->storedStrings[$d];
				$handle = $key << 1;
				$this->writeAmf3Int($handle);
				return $key;
			}
		}
	}

	function writeAmf3Array($d, $arrayCollectionable = false)
	{
		//Circular referencing is disabled in arrays
		//Because if the array contains only primitive values,
		//Then === will say that the two arrays are strictly equal
		//if they contain the same values, even if they are really distinct
		//if(($key = patched_array_search($d, $this->storedObjects, TRUE)) === FALSE )
		//{
			if(count($this->storedObjects) < MAX_STORED_OBJECTS)
			{
				$this->storedObjects[] = & $d;
			}
			
			$numeric = array(); // holder to store the numeric keys
			$string = array(); // holder to store the string keys
			$len = count($d); // get the total number of entries for the array
			$largestKey = -1;
			foreach($d as $key => $data) { // loop over each element
				if (is_int($key) && ($key >= 0)) { // make sure the keys are numeric
					$numeric[$key] = $data; // The key is an index in an array
					$largestKey = max($largestKey, $key);
				} else {
					$string[$key] = $data; // The key is a property of an object
				} 
			} 
			$num_count = count($numeric); // get the number of numeric keys
			$str_count = count($string); // get the number of string keys

			if (($str_count > 0 && $num_count == 0)  || 
			    ($num_count > 0 && $largestKey != $num_count - 1)) { // this is a mixed array
				$this->writeAmf3ObjectFromArray($numeric + $string); // write the numeric and string keys in the mixed array
			} else { // this is just an array
				if($arrayCollectionable)
				{
					$this->writeAmf3ArrayCollectionPreamble();
				}
				
				$num_count = count($numeric);
				
				$this->outBuffer .= "\11";
				$handle = $num_count * 2 + 1;
				$this->writeAmf3Int($handle);
				
				foreach($string as $key => $val)
				{
					$this->writeAmf3String($key);
					$this->writeAmf3Data($val);
				}
				$this->writeAmf3String(""); //End start hash
				
				for($i = 0; $i < $num_count; $i++)
				{
					$this->writeAmf3Data($numeric[$i]);
				}
			}
		//}
		//else
		//{
		//	$handle = $key << 1;
		//	$this->outBuffer .= "\11";
		//	$this->writeAmf3Int($handle);
		//}
	}
	
	function writeAmf3ObjectFromArray($d)
	{
		//Type this as a dynamic object
		$this->outBuffer .= "\12\13\1";
		
		foreach($d as $key => $val)
		{
			$this->writeAmf3String($key);
			$this->writeAmf3Data($val);
		}
		//Now we close the open object
		$this->outBuffer .= "\1";
	}

	/*
	public void WriteAMF3DateTime(DateTime value)
	{
		if( !_objectReferences.Contains(value) )
		{
			_objectReferences.Add(value, _objectReferences.Count);
			int handle = 1;
			WriteAMF3IntegerData(handle);

			// Write date (milliseconds from 1970).
			DateTime timeStart = new DateTime(1970, 1, 1, 0, 0, 0);

			string timezoneCompensation = System.Configuration.ConfigurationSettings.AppSettings["timezoneCompensation"];
			if( timezoneCompensation != null && ( timezoneCompensation.ToLower() == "auto" ) )
			{
				value = value.ToUniversalTime();
			}
			
			TimeSpan span = value.Subtract(timeStart);
			long milliSeconds = (long)span.TotalMilliseconds;
			long date = BitConverter.DoubleToInt64Bits((double)milliSeconds);
			this.WriteLong(date);
		}
		else
		{
			int handle = (int)_objectReferences[value];
			handle = handle << 1;
			WriteAMF3IntegerData(handle);
		}
	}
	*/
	
	function getAmf3Int($d)
	{
		$d &= 0x1fffffff;
		if($d < 0x80)
		{
			return chr($d);
		}
		elseif($d < 0x4000)
		{
			return chr($d >> 7 & 0x7f | 0x80) . chr($d & 0x7f);
		}
		elseif($d < 0x200000)
		{
			return chr($d >> 14 & 0x7f | 0x80) . chr($d >> 7 & 0x7f | 0x80) . chr($d & 0x7f);
		} 
		else
		{
			return chr($d >> 22 & 0x7f | 0x80) . chr($d >> 15 & 0x7f | 0x80) . 
				   chr($d >> 8 & 0x7f | 0x80) . chr($d & 0xff);
		}
	}

	function writeAmf3Number($d)
	{
		if($d >= -268435456 && $d <= 268435455)//check valid range for 29bits
		{
			$this->outBuffer .= "\4";
			$this->writeAmf3Int($d);
		}
		else
		{
			//overflow condition would occur upon int conversion
			$this->outBuffer .= "\5";
			$this->writeDouble($d);
		}
	}

	function writeAmf3Xml($d)
	{
		$d = preg_replace('/\>(\n|\r|\r\n| |\t)*\</','><',trim($d));
		$this->writeByte(0x07);
		$this->writeAmf3String($d);
	}
	
	function writeAmf3ByteArray($d)
	{
		$this->writeByte(0x0C);
		$this->writeAmf3String($d, true);
	}

	function writeAmf3Object($d)
	{
		//Write the object tag
		$this->outBuffer .= "\12";
		if( ($key = patched_array_search($d, $this->storedObjects, TRUE)) === FALSE && $key === FALSE)
		{
			if(count($this->storedObjects) < MAX_STORED_OBJECTS)
			{
				$this->storedObjects[] = & $d;
			}
			
			$this->storedDefinitions++;

			//Type the object as an array
			if(AMFPHP_PHP5)
			{
				$obj = $d;
			}
			else
			{
				$obj = (array) $d;
			}
			$realObj = array();
			foreach($obj as $key => $val)
			{
				if($key[0] != "\0" && $key != '_explicitType') //Don't show private members
				{
					$realObj[$key] = $val;
				}
			}
			
			//Type this as a dynamic object
			$this->outBuffer .= "\13";
			
			$classname = $this->getClassName($d);
			
			$this->writeAmf3String($classname);
			
			foreach($realObj as $key => $val)
			{
				$this->writeAmf3String($key);
				$this->writeAmf3Data($val);
			}
			//Now we close the open object
			$this->outBuffer .= "\1";
		}
		else
		{
			$handle = $key << 1;
			$this->writeAmf3Int($handle);
		}
	}
}



?>