<?php

/** @cond */
if( !defined("phpgats_defined") )
{
define("phpgats_defined",1);
/** @endcond */

/** @mainpage phpgats.php PHP Gats Library
 * @section Notes
 *
 * This library requires that php be built with the The GNU Multiple Precision Arithmetic Library (GMP)
 *
 * @section Usage
 *
 * function phpgats_parseGats( $str_data (string) );
 * returns phpgats_Element
 *
 * function phpgats_writeGats( $elem (phpgats_Element) );
 * returns binary encoded gats string
 *
 * @section Types
 *
 * phpgats_Element - Base Class for all phpgats types\n
 * phpgats_Boolean\n
 * phpgats_Dictionary\n
 * phpgats_Float\n
 * phpgats_Integer\n
 * phpgats_List\n
 * phpgats_String\n
 */

/**
 * The base class for all phpgats types
 */
class phpgats_Element
{
	/**
	 * Get the binary representation of the gats type
	 * @returns (string) binary gats data
	 */
	function encode()
	{
		return "";
	}

	/**
	 * Get a string representation of this type
	 * @returns (string)
	 */
	function getStr()
	{
		return "";
	}

	/**
	 * Get the gats type of the current element
	 * @returns (string) one of: ("boolean"|"dictionary"|"float"|"integer"|"list"|"string")
	 */
	function getGatsType()
	{
		return "";
	}
}

/**
 * Write a gats packed integer
 * @param $iIn (either a string representing a number, a gmp_int, or an int) The integer to be converted
 * @returns (string) packed int binary data
 */
function phpgats_writeInt( $iIn )
{
	$ret = "";
	if( gmp_cmp( $iIn, 0 ) < 0 )
	{
		$iIn = gmp_mul( $iIn, -1 );
		$b = gmp_intval( gmp_and( $iIn, 0x3f ) );
		if( gmp_cmp( $iIn, $b ) > 0 )
			$b |= 0x80 | 0x40;
		else
			$b |= 0x40;
	}
	else
	{
		$b = gmp_intval( gmp_and( $iIn, 0x3f ) );
		if( gmp_cmp( $iIn, $b ) > 0 )
			$b |= 0x80;
	}

	$ret .= chr( $b );
	$iIn = gmp_div( $iIn, 64 );

	while( gmp_cmp( $iIn, 0 ) > 0 )
	{
		$b = gmp_intval( gmp_and( $iIn, 0x7f ) );
		if( gmp_cmp( $iIn, $b ) > 0 )
			$b |= 0x80;
		$ret .= chr( $b );
		$iIn = gmp_div( $iIn, 128 );
	}

	return $ret;
}

/**
 * Read a gats packed integer into a gmp_int
 * @param $sIn (string) the input stream
 * @param &$pos (integer) the current position in the input stream (will be modified)
 * @returns (gmp_int) the integer
 */
function phpgats_readInt( $sIn, &$pos )
{
	$neg = false;

	$b = ord($sIn[$pos++]);
	if( ($b&0x40) == 0x40 )
		$neg = true;
	$iOut = gmp_init( $b&0x3f );
	$mult = gmp_init( 64 );
	while( ($b&0x80) )
	{
		$b = ord($sIn[$pos++]);
		$iOut = gmp_or( $iOut, gmp_mul( $b&0x7f, $mult ) );
		$mult = gmp_mul( $mult, 128 );
	}
	if( $neg == true )
		$iOut = gmp_mul( $iOut, -1 );

	return $iOut;
}

/**
 * Boolean Gats type (true or false)
 */
class phpgats_Boolean extends phpgats_Element
{
	public $elem = false;

	/**
	 * Creates a phpgats_Boolean out of a php type
	 * @param $_elem (mixed) the php type to convert to a gats boolean
	 */
	function __construct( $_elem )
	{
		$this->elem = ($_elem==true)?true:false;
	}

	/**
	 * Sets this phpgats_Boolean type based on php type $_elem
	 * @param $_elem (mixed) the php type to convert to a gats boolean
	 */
	function set( $_elem )
	{
		$this->elem = ($_elem==true)?true:false;
	}

	/**
	 * Get the php type of this element
	 * @returns (bool)
	 */
	function get()
	{
		return $this->elem;
	}

	function encode()
	{
		return ($this->elem==true)?"1":"0";
	}

	function getStr()
	{
		return ($this->elem==true)?"true":"false";
	}

	/**
	 * Get the gats type of the current element
	 * @returns "boolean"
	 */
	function getGatsType()
	{
		return "boolean";
	}
}

/**
 * String Gats type
 */
class phpgats_String extends phpgats_Element
{
	public $elem = "";

	/**
	 * Creates a phpgats_String out of a php type
	 * @param $_elem (mixed) the php type to convert to a gats string
	 */
	function __construct( $_elem )
	{
		$this->elem = $_elem . "";
	}

	/**
	 * Sets this phpgats_String type based on php type $_elem
	 * @param $_elem (mixed) the php type to convert to a gats string
	 */
	function set( $_elem )
	{
		$this->elem = $_elem . "";
	}

	/**
	 * Get the php type of this element
	 * @returns (string)
	 */
	function get()
	{
		return $this->elem;
	}

	function encode()
	{
		return 's' . phpgats_writeInt(strlen($this->elem)) . $this->elem;
	}

	function getStr()
	{
		return $this->get();
	}

	/**
	 * Get the gats type of the current element
	 * @returns "string"
	 */
	function getGatsType()
	{
		return "string";
	}
}

/**
 * Integer Gats type (gmp_int)
 */
class phpgats_Integer extends phpgats_Element
{
	public $elem = 0;

	/**
	 * Creates a phpgats_Integer out of a php type
	 * @param $_elem (mixed) the php type to convert to a gats integer
	 */
	function __construct( $_elem )
	{
		if( $_elem === '' )
			$this->elem = gmp_init(0);
		else if( getType($_elem) == "resource" )
			$this->elem = $_elem;
		else if( getType($_elem) == "string" )
			$this->elem = gmp_init($_elem);
		else if( getType($_elem) == "integer" )
			$this->elem = gmp_init($_elem+0);
		else if( getType($_elem) == "double" )
			$this->elem = gmp_init(intval($_elem));
		else
			throw new Exception("Bad phpgats_Integer type: '" .
				getType($_elem) . "'.");
	}

	/**
	 * Sets this phpgats_Integer type based on php type $_elem
	 * @param $_elem (mixed) the php type to convert to a gats integer
	 */
	function set( $_elem )
	{
		$this->elem = gmp_init($_elem);
	}

	/**
	 * Get the php type of this element
	 * @returns (int)
	 */
	function get()
	{
		return gmp_intval($this->elem);
	}

	/**
	 * Get the php type of this element
	 * @returns (gmp_int)
	 */
	function get64()
	{
		return $this->elem;
	}

	function encode()
	{
		return "i" . phpgats_writeInt($this->elem);
	}

	function getStr()
	{
		return gmp_strval($this->elem);
	}

	/**
	 * Get the gats type of the current element
	 * @returns "integer"
	 */
	function getGatsType()
	{
		return "integer";
	}
}

/**
 * Float Gats type
 */
class phpgats_Float extends phpgats_Element
{
	public $elem = 0;

	/**
	 * Creates a phpgats_Float out of a php type
	 * @param $_elem (mixed) the php type to convert to a gats float
	 */
	function __construct( $_elem )
	{
		$this->elem = $_elem+0.0;
	}

	/**
	 * Sets this phpgats_Float type based on php type $_elem
	 * @param $_elem (mixed) the php type to convert to a gats float
	 */
	function set( $_elem )
	{
		$this->elem = $_elem+0.0;
	}

	/**
	 * Get the php type of this element
	 * @returns (float)
	 */
	function get()
	{
		return $this->elem;
	}

	function encode()
	{
		if( $this->elem == 0.0 )
		{
			return 'Fz';
		}
		else if( is_nan( $this->elem ) )
		{
			return 'Fn';
		}
		else if( is_infinite( $this->elem ) )
		{
			if( $this->elem < 0.0 )
				return 'FI';
			else
				return 'Fi';
		}
		else
		{
			$e = $this->elem;
			$neg = false;
			if( $e < 0.0 )
			{
				$e = -$e;
				$neg = true;
			}
			$iScale = (int)(floor(log($e)/log(256.0)));
			$e = $e/pow(256.0, $iScale);
			$s = chr((int)($e));
			$e = $e - (int)($e);
			for( $j = 0; $j < 150 && $e > 0.0; $j++ )
			{
				$e = $e * 256.0;
				$s .= chr((int)($e));
				$e -= (int)($e);
			}
			$ilen = strlen($s);
			if( $neg ) $ilen = -$ilen;
			return "f" . phpgats_writeInt($ilen) . $s .
			   	phpgats_writeInt($iScale);
		}
	}

	function getStr()
	{
		return $this->elem+"";
	}

	/**
	 * Get the gats type of the current element
	 * @returns "float"
	 */
	function getGatsType()
	{
		return "float";
	}
}

/**
 * List Gats type
 */
class phpgats_List extends phpgats_Element
{
	public $elems = array();

	/**
	 * Append an element to this phpgats_List
	 * @param $_elem (phpgats_Element) the element to append
	 */
	function append( $_elem )
	{
		if( !is_subclass_of( $_elem, "phpgats_Element" ) )
			throw new Exception( 
				"can only call phpgats_List::append with phpgats_Element." );
		array_push( $this->elems, $_elem );
	}

	/**
	 * Returns the php array that is the core of this element
	 * @returns (array)
	 */
	function get()
	{
		return $this->elems;
	}

	/**
	 * Returns the current size of this list
	 * @returns (int) the number of elements
	 */
	function size()
	{
		return count($this->elems);
	}

	function encode()
	{
		$s_out = "l";
		foreach( $this->elems as $val )
		{
			$s_out .= $val->encode();
		}
		$s_out .= "e";
		return $s_out;
	}

	/**
	 * Get the gats type of the current element
	 * @returns "list"
	 */
	function getGatsType()
	{
		return "list";
	}
}

/**
 * Dictionary Gats type
 */
class phpgats_Dictionary extends phpgats_Element
{
	public $elems = array();

	/**
	 * Append an element to the phpgats_Dictionary
	 * @param $_name (string) the key under which to place this element
	 * @param $_val (phpgats_Element) the phpgats_Element to place in the dictionary
	 */
	function append( $_name, $_val )
	{
		if( !is_subclass_of( $_val, "phpgats_Element" ) || 
			gettype($_name)!="string")
			throw new Exception( 
				"can only call phpgats_Dictionary::append with str,phpgats_Element." );
		$this->elems[$_name] = $_val;
	}

	/**
	 * Returns the php associative array that is the core of this element
	 * @returns (array)
	 */
	function get()
	{
		return $this->elems;
	}

	/**
	 * Returns the current size of the dictionary
	 * @returns (int)
	 */
	function size()
	{
		return count($this->elems);
	}

	function encode()
	{
		$s_out = "d";
		foreach( $this->elems as $key => $val )
		{
			$n = new phpgats_String( $key );
			$s_out .= $n->encode();
			$s_out .= $val->encode();
		}
		$s_out .= "e";
		return $s_out;
	}

	/**
	 * Get the gats type of the current element
	 * @returns "dictionary"
	 */
	function getGatsType()
	{
		return "dictionary";
	}
}

/**
 * Make sure we can read a character off the input stream
 * @param $str_data (string) the input stream
 * @param $offset (integer) the current position in the input stream
 */
function phpgats_pC( $str_data, $offset )
{
	if($offset>strlen($str_data))
		throw new Exception("Not enough data");
}

/**
 * Parse a string element out of the input stream
 * @param $str_data (string) the input stream
 * @param &$offset (integer) the current position in the input stream (will be updated)
 * @param $dbg (integer) the current depth (for pretty printing)
 * @returns (phpgats_String) the element read
 */
function phpgats_pS( $str_data, &$offset, $dbg )
{
	$str_tmp = "";
	$gmpSize = phpgats_readInt( $str_data, $offset );
	if( gmp_cmp( $gmpSize, 2147483647 ) > 0 )
	{
		throw new Exception(
			"size (" . gmp_strval($gmpSize) . ") > phpgats can handle\n");
	}
	$iSize = gmp_intval($gmpSize);
	$i=0;
	$str_tmp = "";
	while( $i<$iSize )
	{
		phpgats_pC( $str_data, $offset+1 );
		$str_tmp .= $str_data[$offset++];
		++$i;
	}
	return new phpgats_String($str_tmp);
}

/**
 * Parse an integer element out of the input stream
 * @param $str_data (string) the input stream
 * @param &$offset (integer) the current position in the input stream (will be updated)
 * @param $dbg (integer) the current depth (for pretty printing)
 * @returns (phpgats_Integer) the element read
 */
function phpgats_pI( $str_data, &$offset, $dbg )
{
	return new phpgats_Integer(phpgats_readInt($str_data, $offset));
}

/**
 * Parse a float element out of the input stream
 * @param $str_data (string) the input stream
 * @param &$offset (integer) the current position in the input stream (will be updated)
 * @param $dbg (integer) the current depth (for pretty printing)
 * @returns (phpgats_Float) the element read
 */
function phpgats_pF( $str_data, &$offset, $dbg )
{
	$str_tmp = "";
	$iSize = gmp_intval(phpgats_readInt( $str_data, $offset ));
	$neg = false;
	if( $iSize < 0.0 )
	{
		$iSize = -$iSize;
		$neg = true;
	}
	$i=0;
	$str_tmp = "";
	while( $i<$iSize )
	{
		phpgats_pC( $str_data, $offset+1 );
		$str_tmp .= $str_data[$offset++];
		++$i;
	}
	$iScale = gmp_intval(phpgats_readInt( $str_data, $offset ));
	$e = 0.0;
	for( $j = $iSize-1; $j > 0; $j-- )
	{
		$e = ($e+ord($str_tmp[$j]))*.00390625;
	}
	$e = ($e+ord($str_tmp[0])) * pow( 256.0, $iScale );
	if( $neg ) $e = -$e;
	return new phpgats_Float( $e );
}

/**
 * Parse a list element out of the input stream
 * @param $str_data (string) the input stream
 * @param &$offset (integer) the current position in the input stream (will be updated)
 * @param $dbg (integer) the current depth (for pretty printing)
 * @returns (phpgats_List) the element read
 */
function phpgats_pL( $str_data, &$offset, $dbg )
{
	phpgats_pC( $str_data, $offset );
	$c = $str_data[$offset];
	$l_out = new phpgats_List();
	while( $c != "e" )
	{
		$obj = phpgats_pM( $str_data, $offset, $dbg );
		$l_out->append( $obj );
		phpgats_pC( $str_data, $offset );
		$c = $str_data[$offset];
	}
	$offset++;
	return $l_out;
}

/**
 * Parse a dictionary element out of the input stream
 * @param $str_data (string) the input stream
 * @param &$offset (integer) the current position in the input stream (will be updated)
 * @param $dbg (integer) the current depth (for pretty printing)
 * @returns (phpgats_Dictionary) the element read
 */
function phpgats_pD( $str_data, &$offset, $dbg )
{
	phpgats_pC( $str_data, $offset );
	$c = $str_data[$offset];
	$d_out = new phpgats_Dictionary();
	while( $c != "e" )
	{
		$obj1 = phpgats_pM( $str_data, $offset, $dbg );
		$obj2 = phpgats_pM( $str_data, $offset, $dbg );
		$d_out->append( $obj1->get(), $obj2 );
		phpgats_pC( $str_data, $offset );
		$c = $str_data[$offset];
	}
	$offset++;
	return $d_out;
}

/**
 * The internal master recursive parse function
 * @param $str_data (string) the input stream
 * @param &$offset (integer) the current position in the input stream (will be updated)
 * @param $dbg (integer) the current depth (for pretty printing)
 * @returns (phpgats_Element) the element read
 */
function phpgats_pM( $str_data, &$offset, $dbg=0 )
{
	phpgats_pC( $str_data, $offset );
	$c = $str_data[$offset++];
	//for( $meme=0; $meme<$dbg; $meme++ )
	//	echo "  ";
	switch( $c )
	{
		case 'i':
			//echo "int:";
			$obj = phpgats_pI( $str_data, $offset, $dbg );
			//echo gmp_strval($obj->get()) . "\n";
			return $obj;
			break;
		case 'l':
			//echo "list:\n";
			$obj = phpgats_pL( $str_data, $offset, $dbg+1 );
			return $obj;
			break;
		case 'd':
			//echo "dic:\n";
			$obj = phpgats_pD( $str_data, $offset, $dbg+1 );
			return $obj;
			break;
		case 'f':
			//echo "float:\n";
			$obj = phpgats_pF( $str_data, $offset, $dbg );
			return $obj;
			break;
		case 'F':
			phpgats_pC( $str_data, $offset );
			switch( $str_data[$offset++] )
			{
				case 'Z': return new phpgats_Float( -0.0 );
				case 'z': return new phpgats_Float( 0.0 );
				case 'N': return new phpgats_Float( -NAN );
				case 'n': return new phpgats_Float( NAN );
				case 'I': return new phpgats_Float( -INF );
				case 'i': return new phpgats_Float( INF );
			}
			break;
		case '1':
			//echo "true\n";
			return new phpgats_Boolean( true );
			break;
		case '0':
			//echo "false\n";
			return new phpgats_Boolean( false );
			break;
		default:
			//echo "str:";
			$obj = phpgats_pS( $str_data, $offset, $dbg );
			//echo $obj->get() . "\n";
			return $obj;
			break;
	}
}

/**
 * Call this function to parse a gats binary string into a phpgats_Element object
 * @param $str_data (string) the binary gats data to be parsed
 * @returns (phpgats_Element)
 */
function phpgats_parseGats( $str_data )
{
	//print "parsing\n";
	$offset = 0;
	$data_size = strlen( $str_data );
	if( $data_size < 5 )
	{
		throw new Exception( "invalid size (< 5)\n" );
		return false;
	}
	if( ord($str_data) != 1 ) //version
	{
		throw new Exception( "invalid gats version" );
		return false;
	}
	$size = "" . $str_data[1] . $str_data[2] . $str_data[3] . $str_data[4];
	$size = unpack( "Nsize", $size );
	$size = $size["size"];
	if( $data_size < $size )
	{
		throw new Exception( "Not enough data" );
		return false;
	}
	$offset+=5;
	return phpgats_pM( $str_data, $offset );
}

/**
 * Call this function to generate a binary gats stream from the given phpgats_Element object
 * @param $elem (phpgats_Element) the gats element object from which to generate a binary blob
 * @returns (string) binary gats data
 */
function phpgats_writeGats( $elem )
{
	$str_out = $elem->encode();
	$str_out = "\x01" . pack( "N", strlen($str_out)+5 ) . $str_out;
	return $str_out;
}

/** @cond */
} //defined("phpgats_defined");
/** @endcond */

/*
$l = new phpgats_List();
$l->append( new phpgats_Float( 123000000000.0 ) );
$l->append( new phpgats_Float( -0.000087687 ) );
$l->append( new phpgats_Float( -INF ) );
$l->append( new phpgats_Float( INF ) );
$l->append( new phpgats_Float( -NAN ) );
$l->append( new phpgats_Float( NAN ) );
$l->append( new phpgats_Float( -0.0 ) );
$l->append( new phpgats_Float( 0.0 ) );
$l->append( new phpgats_Integer("27") );
$l->append( new phpgats_Boolean(true) );
$l->append( new phpgats_String("testing your monkey") );

$d = new phpgats_Dictionary();
$d->append( "theList", $l );
$d->append( "theFalse", new phpgats_Boolean(false) );

$f = fopen("testinggats.gats", "w");
$theStr = phpgats_writeGats($d);
fwrite( $f, $theStr );
fclose( $f );

$obj = phpgats_parseGats( $theStr );

print_r( $obj );
 */

?>