<?php
/**********************************************************\
|                                                          |
|                          hprose                          |
|                                                          |
| Official WebSite: http://www.hprose.com/                 |
|                   http://www.hprose.net/                 |
|                   http://www.hprose.org/                 |
|                                                          |
\**********************************************************/

/**********************************************************\
 *                                                        *
 * HproseWriter.php                                       *
 *                                                        *
 * hprose writer library for php5.                        *
 *                                                        *
 * LastModified: Nov 13, 2013                             *
 * Author: Ma Bingyao <andot@hprfc.com>                   *
 *                                                        *
\**********************************************************/

require_once('HproseCommon.php');
require_once('HproseTags.php');
require_once('HproseClassManager.php');

class HproseSimpleWriter {
    public $stream;
    private $classref;
    private $fieldsref;
    function __construct(&$stream) {
        $this->stream = &$stream;
        $this->classref = array();
        $this->fieldsref = array();
    }
    public function serialize(&$var) {
        if ((!isset($var)) || ($var === NULL)) {
            $this->writeNull();
        }
        elseif (is_scalar($var)) {
            if (is_int($var)) {
                $this->writeInteger($var);
            }
            elseif (is_bool($var)) {
                $this->writeBoolean($var);
            }
            elseif (is_float($var)) {
                $this->writeDouble($var);
            }
            elseif (is_string($var)) {
                if ($var === '') {
                    $this->writeEmpty();
                }
                elseif ((strlen($var) < 4) && is_utf8($var) && (ustrlen($var) == 1)) {
                    $this->writeUTF8Char($var);
                }
                elseif (is_utf8($var)) {
                    $this->writeString($var, true);
                }
                else {
                    $this->writeBytes($var, true);
                }
            }
        }
        elseif (is_array($var)) {
            if (is_list($var)) {
                $this->writeList($var, true);
            }
            else {
               $this->writeMap($var, true);
            }
        }
        elseif (is_object($var)) {
            if ($var instanceof stdClass) {
                $this->writeStdObject($var, true);
            }
            elseif (($var instanceof HproseDate) || ($var instanceof HproseDateTime)) {
                $this->writeDate($var, true);
            }
            elseif ($var instanceof HproseTime) {
                $this->writeTime($var, true);
            }
            else {
                $this->writeObject($var, true);
            }
        }
        else {
            throw new HproseException('Not support to serialize this data');
        }
    }
    public function writeInteger($integer) {
        if ($integer >= 0 && $integer <= 9) {
            $this->stream->write((string)$integer);
        }
        else {
            $this->stream->write(HproseTags::TagInteger . $integer . HproseTags::TagSemicolon);
        }
    }
    public function writeLong($long) {
        if ($long >= '0' && $long <= '9') {
            $this->stream->write($long);
        }
        else {
            $this->stream->write(HproseTags::TagLong . $long . HproseTags::TagSemicolon);
        }
    }
    public function writeDouble($double) {
        if (is_nan($double)) {
            $this->writeNaN();
        }
        elseif (is_infinite($double)) {
            $this->writeInfinity($double > 0);
        }
        else {
            $this->stream->write(HproseTags::TagDouble . $double . HproseTags::TagSemicolon);
        }
    }
    public function writeNaN() {
        $this->stream->write(HproseTags::TagNaN);
    }
    public function writeInfinity($positive = true) {
        $this->stream->write(HproseTags::TagInfinity . ($positive ? HproseTags::TagPos : HproseTags::TagNeg));
    }
    public function writeNull() {
        $this->stream->write(HproseTags::TagNull);
    }
    public function writeEmpty() {
        $this->stream->write(HproseTags::TagEmpty);
    }
    public function writeBoolean($bool) {
        $this->stream->write($bool ? HproseTags::TagTrue : HproseTags::TagFalse);
    }
    public function writeDate($date, $checkRef = false) {
        if ($date->utc) {
            $this->stream->write(HproseTags::TagDate . $date->toString(false));
        }
        else {
            $this->stream->write(HproseTags::TagDate . $date->toString(false) . HproseTags::TagSemicolon);
        }
    }
    public function writeTime($time, $checkRef = false) {
        if ($time->utc) {
            $this->stream->write(HproseTags::TagTime . $time->toString(false));
        }
        else {
            $this->stream->write(HproseTags::TagTime . $time->toString(false) . HproseTags::TagSemicolon);
        }
    }
    public function writeBytes($bytes, $checkRef = false) {
        $len = strlen($bytes);
        $this->stream->write(HproseTags::TagBytes);
        if ($len > 0) $this->stream->write((string)$len);
        $this->stream->write(HproseTags::TagQuote . $bytes . HproseTags::TagQuote);
    }
    public function writeUTF8Char($char) {
        $this->stream->write(HproseTags::TagUTF8Char . $char);
    }
    public function writeString($str, $checkRef = false) {
        $len = ustrlen($str);
        $this->stream->write(HproseTags::TagString);
        if ($len > 0) $this->stream->write((string)$len);
        $this->stream->write(HproseTags::TagQuote . $str . HproseTags::TagQuote);
    }
    public function writeList(&$list, $checkRef = false) {
        $count = count($list);
        $this->stream->write(HproseTags::TagList);
        if ($count > 0) $this->stream->write((string)$count); 
        $this->stream->write(HproseTags::TagOpenbrace);
        for ($i = 0; $i < $count; ++$i) {
            $this->serialize($list[$i]);
        }
        $this->stream->write(HproseTags::TagClosebrace);
    }
    public function writeMap(&$map, $checkRef = false) {
        $count = count($map);
        $this->stream->write(HproseTags::TagMap);
        if ($count > 0) $this->stream->write((string)$count); 
        $this->stream->write(HproseTags::TagOpenbrace);
        foreach ($map as $key => &$value) {
            $this->serialize($key);
            $this->serialize($value);
        }
        $this->stream->write(HproseTags::TagClosebrace);
    }
    public function writeStdObject($obj, $checkRef = false) {
        $map = (array)$obj;
        self::writeMap($map);
    }
    protected function writeObjectBegin($obj) {
        $class = get_class($obj);
        $alias = HproseClassManager::getClassAlias($class);
        $fields = array_keys((array)$obj);
        if (array_key_exists($alias, $this->classref)) {
            $index = $this->classref[$alias];
        }
        else {
            $index = $this->writeClass($alias, $fields);
        }
        return $index;
    }
    protected function writeObjectEnd($obj, $index) {
            $fields = $this->fieldsref[$index];
            $count = count($fields);
            $this->stream->write(HproseTags::TagObject . $index . HproseTags::TagOpenbrace);
            $array = (array)$obj;
            for ($i = 0; $i < $count; ++$i) {
                $this->serialize($array[$fields[$i]]);
            }
            $this->stream->write(HproseTags::TagClosebrace);
    }
    public function writeObject($obj, $checkRef = false) {
        $this->writeObjectEnd($obj, $this->writeObjectBegin($obj));
    }
    protected function writeClass($alias, $fields) {
        $len = ustrlen($alias);
        $this->stream->write(HproseTags::TagClass . $len .
                             HproseTags::TagQuote . $alias . HproseTags::TagQuote);
        $count = count($fields);
        if ($count > 0) $this->stream->write((string)$count);
        $this->stream->write(HproseTags::TagOpenbrace);
        for ($i = 0; $i < $count; ++$i) {
            $field = $fields[$i];
            if ($field{0} === "\0") {
                $field = substr($field, strpos($field, "\0", 1) + 1);
            }
            $this->writeString($field);
        }
        $this->stream->write(HproseTags::TagClosebrace);
        $index = count($this->fieldsref);
        $this->classref[$alias] = $index;
        $this->fieldsref[$index] = $fields;
        return $index;
    }
    public function reset() {
        $this->classref = array();
        $this->fieldsref = array();
    }
}
class HproseWriter extends HproseSimpleWriter {
    private $ref;
    private $arrayref;
    function __construct(&$stream) {
        parent::__construct($stream);
        $this->ref = array();
        $this->arrayref = array();
    }
    private function writeRef(&$obj, $checkRef, $writeBegin, $writeEnd) {
        if (is_string($obj)) {
            $key = 's_' . $obj;
        }
        elseif (is_array($obj)) {
            if (($i = array_ref_search($obj, $this->arrayref)) === false) {
                $i = count($this->arrayref);
                $this->arrayref[$i] = &$obj;
            }
            $key = 'a_' . $i;
        }
        else {
            $key = 'o_' . spl_object_hash($obj);
        }
        if ($checkRef && array_key_exists($key, $this->ref)) {
            $this->stream->write(HproseTags::TagRef . $this->ref[$key] . HproseTags::TagSemicolon);
        }
        else {
            $result = $writeBegin ? call_user_func_array($writeBegin, array(&$obj)) : false;
            $index = count($this->ref);
            $this->ref[$key] = $index;
            call_user_func_array($writeEnd, array(&$obj, $result));
        }
    }
    public function writeDate($date, $checkRef = false) {
        $this->writeRef($date, $checkRef, NULL, array(&$this, 'parent::writeDate'));
    }
    public function writeTime($time, $checkRef = false) {
        $this->writeRef($time, $checkRef, NULL, array(&$this, 'parent::writeTime'));
    }
    public function writeBytes($bytes, $checkRef = false) {
        $this->writeRef($bytes, $checkRef, NULL, array(&$this, 'parent::writeBytes'));
    }
    public function writeString($str, $checkRef = false) {
        $this->writeRef($str, $checkRef, NULL, array(&$this, 'parent::writeString'));
    }
    public function writeList(&$list, $checkRef = false) {
        $this->writeRef($list, $checkRef, NULL, array(&$this, 'parent::writeList'));
    }
    public function writeMap(&$map, $checkRef = false) {
        $this->writeRef($map, $checkRef, NULL, array(&$this, 'parent::writeMap'));
    }
    public function writeStdObject($obj, $checkRef = false) {
        $this->writeRef($obj, $checkRef, NULL, array(&$this, 'parent::writeStdObject'));
    }
    public function writeObject($obj, $checkRef = false) {
        $this->writeRef($obj, $checkRef, array(&$this, 'writeObjectBegin'), array(&$this, 'writeObjectEnd'));
    }
    public function reset() {
        parent::reset();
        $this->ref = array();
        $this->arrayref = array();
    }
}
?>