<?php
/**
 * Zend Framework (http://framework.zend.com/)
 *
 * @see       http://github.com/zendframework/zend-diactoros for the canonical source repository
 * @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
 * @license   https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
 */

namespace Zend\Diactoros;

use OutOfBoundsException;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * "Serve" incoming HTTP requests
 *
 * Given a callback, takes an incoming request, dispatches it to the
 * callback, and then sends a response.
 */
class Server
{
    /**
     * @var callable
     */
    private $callback;

    /**
     * Response emitter to use; by default, uses Response\SapiEmitter.
     *
     * @var Response\EmitterInterface
     */
    private $emitter;

    /**
     * @var ServerRequestInterface
     */
    private $request;

    /**
     * @var ResponseInterface
     */
    private $response;

    /**
     * Constructor
     *
     * Given a callback, a request, and a response, we can create a server.
     *
     * @param callable $callback
     * @param ServerRequestInterface $request
     * @param ResponseInterface $response
     */
    public function __construct(
        callable $callback,
        ServerRequestInterface $request,
        ResponseInterface $response
    ) {
        $this->callback = $callback;
        $this->request  = $request;
        $this->response = $response;
    }

    /**
     * Allow retrieving the request, response and callback as properties
     *
     * @param string $name
     * @return mixed
     * @throws OutOfBoundsException for invalid properties
     */
    public function __get($name)
    {
        if (! property_exists($this, $name)) {
            throw new OutOfBoundsException('Cannot retrieve arbitrary properties from server');
        }
        return $this->{$name};
    }

    /**
     * Set alternate response emitter to use.
     *
     * @param Response\EmitterInterface $emitter
     */
    public function setEmitter(Response\EmitterInterface $emitter)
    {
        $this->emitter = $emitter;
    }

    /**
     * Create a Server instance
     *
     * Creates a server instance from the callback and the following
     * PHP environmental values:
     *
     * - server; typically this will be the $_SERVER superglobal
     * - query; typically this will be the $_GET superglobal
     * - body; typically this will be the $_POST superglobal
     * - cookies; typically this will be the $_COOKIE superglobal
     * - files; typically this will be the $_FILES superglobal
     *
     * @param callable $callback
     * @param array $server
     * @param array $query
     * @param array $body
     * @param array $cookies
     * @param array $files
     * @return static
     */
    public static function createServer(
        callable $callback,
        array $server,
        array $query,
        array $body,
        array $cookies,
        array $files
    ) {
        $request  = ServerRequestFactory::fromGlobals($server, $query, $body, $cookies, $files);
        $response = new Response();
        return new static($callback, $request, $response);
    }

    /**
     * Create a Server instance from an existing request object
     *
     * Provided a callback, an existing request object, and optionally an
     * existing response object, create and return the Server instance.
     *
     * If no Response object is provided, one will be created.
     *
     * @param callable $callback
     * @param ServerRequestInterface $request
     * @param null|ResponseInterface $response
     * @return static
     */
    public static function createServerFromRequest(
        callable $callback,
        ServerRequestInterface $request,
        ResponseInterface $response = null
    ) {
        if (! $response) {
            $response = new Response();
        }
        return new static($callback, $request, $response);
    }

    /**
     * "Listen" to an incoming request
     *
     * If provided a $finalHandler, that callable will be used for
     * incomplete requests.
     *
     * Output buffering is enabled prior to invoking the attached
     * callback; any output buffered will be sent prior to any
     * response body content.
     *
     * @param null|callable $finalHandler
     */
    public function listen(callable $finalHandler = null)
    {
        $callback = $this->callback;

        ob_start();
        $bufferLevel = ob_get_level();

        $response = $callback($this->request, $this->response, $finalHandler);
        if (! $response instanceof ResponseInterface) {
            $response = $this->response;
        }
        $this->getEmitter()->emit($response, $bufferLevel);
    }

    /**
     * Retrieve the current response emitter.
     *
     * If none has been registered, lazy-loads a Response\SapiEmitter.
     *
     * @return Response\EmitterInterface
     */
    private function getEmitter()
    {
        if (! $this->emitter) {
            $this->emitter = new Response\SapiEmitter();
        }

        return $this->emitter;
    }
}
