<?php

namespace GuzzleHttp\Psr7;

use BadMethodCallException;
use Exception;
use Psr\Http\Message\StreamInterface;
use UnexpectedValueException;

/**
 * Stream decorator trait
 * @property StreamInterface stream
 */
trait StreamDecoratorTrait {
	/**
	 * @param StreamInterface $stream Stream to decorate
	 */
	public function __construct( StreamInterface $stream ) {
		$this->stream = $stream;
	}

	/**
	 * Magic method used to create a new stream if streams are not added in
	 * the constructor of a decorator (e.g., LazyOpenStream).
	 *
	 * @param string $name Name of the property (allows "stream" only).
	 *
	 * @return StreamInterface
	 */
	public function __get( $name ) {
		if ( $name == 'stream' ) {
			$this->stream = $this->createStream();

			return $this->stream;
		}

		throw new UnexpectedValueException( "$name not found on class" );
	}

	/**
	 * Implement in subclasses to dynamically create streams when requested.
	 *
	 * @return StreamInterface
	 * @throws BadMethodCallException
	 */
	protected function createStream() {
		throw new BadMethodCallException( 'Not implemented' );
	}

	public function __toString() {
		try {
			if ( $this->isSeekable() ) {
				$this->seek( 0 );
			}

			return $this->getContents();
		} catch ( Exception $e ) {
			// Really, PHP? https://bugs.php.net/bug.php?id=53648
			trigger_error( 'StreamDecorator::__toString exception: '
			               . (string) $e, E_USER_ERROR );

			return '';
		}
	}

	public function isSeekable() {
		return $this->stream->isSeekable();
	}

	public function seek( $offset, $whence = SEEK_SET ) {
		$this->stream->seek( $offset, $whence );
	}

	public function getContents() {
		return copy_to_string( $this );
	}

	/**
	 * Allow decorators to implement custom methods
	 *
	 * @param string $method Missing method name
	 * @param array $args Method arguments
	 *
	 * @return mixed
	 */
	public function __call( $method, array $args ) {
		$result = call_user_func_array( [ $this->stream, $method ], $args );

		// Always return the wrapped object if the result is a return $this
		return $result === $this->stream ? $this : $result;
	}

	public function close() {
		$this->stream->close();
	}

	public function getMetadata( $key = null ) {
		return $this->stream->getMetadata( $key );
	}

	public function detach() {
		return $this->stream->detach();
	}

	public function getSize() {
		return $this->stream->getSize();
	}

	public function eof() {
		return $this->stream->eof();
	}

	public function tell() {
		return $this->stream->tell();
	}

	public function isReadable() {
		return $this->stream->isReadable();
	}

	public function isWritable() {
		return $this->stream->isWritable();
	}

	public function rewind() {
		$this->seek( 0 );
	}

	public function read( $length ) {
		return $this->stream->read( $length );
	}

	public function write( $string ) {
		return $this->stream->write( $string );
	}
}
