<?php

/*
 CharacterStream implementation using an array in Swift Mailer.

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.

 */

/**
 * A CharacterStream implementation which stores characters in an internal array.
 * @package Swift
 * @subpackage CharacterStream
 * @author Xavier De Cock <xdecock@gmail.com>
 */

class Swift_CharacterStream_NgCharacterStream implements Swift_CharacterStream
{
    /**
     * The char reader (lazy-loaded) for the current charset.
     * @var Swift_CharacterReader
     * @access private
     */
    private $_charReader;

    /**
     * A factory for creatiing CharacterReader instances.
     * @var Swift_CharacterReaderFactory
     * @access private
     */
    private $_charReaderFactory;

    /**
     * The character set this stream is using.
     * @var string
     * @access private
     */
    private $_charset;

    /**
     * The datas stored as is
     *
     * @var string
     */
    private $_datas = "";

    /**
     * Number of bytes in the stream
     *
     * @var int
     */
    private $_datasSize = 0;

    /**
     * Map
     *
     * @var mixed
     */
    private $_map;

    /**
     * Map Type
     *
     * @var int
     */
    private $_mapType = 0;

    /**
     * Number of characters in the stream
     *
     * @var int
     */
    private $_charCount = 0;

    /**
     * Position in the stream
     *
     * @var unknown_type
     */
    private $_currentPos = 0;

    /**
     * The constructor
     *
     * @param Swift_CharacterReaderFactory $factory
     * @param unknown_type                 $charset
     */
    public function __construct(Swift_CharacterReaderFactory $factory, $charset)
    {
        $this->setCharacterReaderFactory($factory);
        $this->setCharacterSet($charset);
    }

    /* -- Changing parameters of the stream -- */

    /**
     * Set the character set used in this CharacterStream.
     * @param string $charset
     */
    public function setCharacterSet($charset)
    {
        $this->_charset = $charset;
        $this->_charReader = null;
        $this->_mapType = 0;
    }

    /**
     * Set the CharacterReaderFactory for multi charset support.
     * @param Swift_CharacterReaderFactory $factory
     */
    public function setCharacterReaderFactory(Swift_CharacterReaderFactory $factory)
    {
        $this->_charReaderFactory = $factory;
    }

    /**
     * @see Swift_CharacterStream::flushContents()
     *
     */
    public function flushContents()
    {
        $this->_datas = null;
        $this->_map = null;
        $this->_charCount = 0;
        $this->_currentPos = 0;
        $this->_datasSize = 0;
    }

    /**
     * @see Swift_CharacterStream::importByteStream()
     *
     * @param Swift_OutputByteStream $os
     */
    public function importByteStream(Swift_OutputByteStream $os)
    {
        $this->flushContents();
        $blocks=512;
        $os->setReadPointer(0);
        while(false!==($read = $os->read($blocks)))
            $this->write($read);
    }

    /**
     * @see Swift_CharacterStream::importString()
     *
     * @param string $string
     */
    public function importString($string)
    {
        $this->flushContents();
        $this->write($string);
    }

    /**
     * @see Swift_CharacterStream::read()
     *
     * @param  int    $length
     * @return string
     */
    public function read($length)
    {
        if ($this->_currentPos>=$this->_charCount) {
          return false;
        }
        $ret=false;
        $length = ($this->_currentPos+$length > $this->_charCount)
          ? $this->_charCount - $this->_currentPos
          : $length;
          switch ($this->_mapType) {
            case Swift_CharacterReader::MAP_TYPE_FIXED_LEN:
                $len = $length*$this->_map;
                $ret = substr($this->_datas,
                        $this->_currentPos * $this->_map,
                        $len);
                $this->_currentPos += $length;
                break;

            case Swift_CharacterReader::MAP_TYPE_INVALID:
                $end = $this->_currentPos + $length;
                $end = $end > $this->_charCount
                    ?$this->_charCount
                    :$end;
                $ret = '';
                for (; $this->_currentPos < $length; ++$this->_currentPos) {
                    if (isset ($this->_map[$this->_currentPos])) {
                        $ret .= '?';
                    } else {
                        $ret .= $this->_datas[$this->_currentPos];
                    }
                }
                break;

            case Swift_CharacterReader::MAP_TYPE_POSITIONS:
                $end = $this->_currentPos + $length;
                $end = $end > $this->_charCount
                    ?$this->_charCount
                    :$end;
                $ret = '';
                $start = 0;
                if ($this->_currentPos>0) {
                    $start = $this->_map['p'][$this->_currentPos-1];
                }
                $to = $start;
                for (; $this->_currentPos < $end; ++$this->_currentPos) {
                    if (isset($this->_map['i'][$this->_currentPos])) {
                        $ret .= substr($this->_datas, $start, $to - $start).'?';
                        $start = $this->_map['p'][$this->_currentPos];
                    } else {
                        $to = $this->_map['p'][$this->_currentPos];
                    }
                }
                $ret .= substr($this->_datas, $start, $to - $start);
                break;
        }

        return $ret;
    }

    /**
     * @see Swift_CharacterStream::readBytes()
     *
     * @param  int   $length
     * @return int[]
     */
    public function readBytes($length)
    {
        $read=$this->read($length);
        if ($read!==false) {
            $ret = array_map('ord', str_split($read, 1));

            return $ret;
        }

        return false;
    }

    /**
     * @see Swift_CharacterStream::setPointer()
     *
     * @param int $charOffset
     */
    public function setPointer($charOffset)
    {
        if ($this->_charCount<$charOffset) {
            $charOffset=$this->_charCount;
        }
        $this->_currentPos = $charOffset;
    }

    /**
     * @see Swift_CharacterStream::write()
     *
     * @param string $chars
     */
    public function write($chars)
    {
        if (!isset($this->_charReader)) {
            $this->_charReader = $this->_charReaderFactory->getReaderFor(
                $this->_charset);
            $this->_map = array();
            $this->_mapType = $this->_charReader->getMapType();
        }
        $ignored='';
        $this->_datas .= $chars;
        $this->_charCount += $this->_charReader->getCharPositions(substr($this->_datas, $this->_datasSize), $this->_datasSize, $this->_map, $ignored);
        if ($ignored!==false) {
            $this->_datasSize=strlen($this->_datas)-strlen($ignored);
        } else {
            $this->_datasSize=strlen($this->_datas);
        }
    }
}
