<?php

namespace Mnv\Http;

use Mnv\Core\Collections\Traits\Macroable;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;

/**
 * Class Response
 * @package Mnv\Http
 */
class Response extends SymfonyResponse
{

    use  ResponseTrait, Macroable {
        Macroable::__call as macroCall;
    }
    /**
     * Construct the response
     *
     * @param  mixed  $content
     * @param  int    $status
     * @param  array  $headers
     */
    public function __construct(?string $content = '', int $status = 200, array $headers = [])
    {
        $this->headers = new ResponseHeaderBag($headers);

        $this->setContent($content);
        $this->setStatusCode($status);
        $this->setProtocolVersion('1.0');
    }

    /**
     * Установим содержимое в ответе.
     *
     * @param  mixed  $content
     * @return $this
     */
    public function setContent($content): Response
    {
        $this->content = $content;

        return $this;
    }

    /**
     * Задает код состояния ответа.
     *
     * @param int $code HTTP status code
     * @param mixed $text HTTP status text
     *
     * Если текст статуса равен `null`, он будет автоматически заполнен известным кодом статуса или оставлен пустым в противном случае.
     *
     * @return Response
     *
     * @throws \InvalidArgumentException Когда код состояния HTTP недействителен
     *
     * @api
     */
    public function setStatusCode(int $code, $text = null): Response
    {
        $this->statusCode = $code;
        if ( $this->isInvalid() ) {
            throw new \InvalidArgumentException(sprintf('The HTTP status code "%s" is not valid.', $code));
        }

        if ( null === $text ) {
            $this->statusText = self::$statusTexts[$code] ?? 'unknown status';

            return $this;
        }

        if ( false === $text ) {
            $this->statusText = '';

            return $this;
        }

        $this->statusText = $text;

        return $this;
    }


    // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
    /**
     * Является ли ответ действительным?
     *
     * @return bool
     *
     * @api
     */
    public function isInvalid(): bool
    {
        return $this->statusCode < 100 || $this->statusCode >= 600;
    }


    /**
     * Установка заголовка для ответа.
     *
     * @param string $key
     * @param string $value
     * @param bool $replace
     * @return $this
     */
    public function header(string $key, string $value, bool $replace = true): Response
    {
        if ( empty($this->headers[$key]) ) {
            $this->headers[$key] = array();
        }
        if ( $replace ) {
            $this->headers[$key] = array($value);
        } else {
            $this->headers[$key][] = $value;
        }

        return $this;
    }

    public function getContent(): string
    {
        return $this->content;
    }

    /**
     * Отправляем HTTP-заголовки и содержимое.
     *
     * @return Response
     *
     * @api
     */
    public function send(): Response
    {
        $this->sendHeaders();
        $this->sendContent();

        if (\function_exists('fastcgi_finish_request')) {
            fastcgi_finish_request();
        }  elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
            static::closeOutputBuffers(0, true);
        }

        return $this;
    }


    /**
     * Отправляем содержимое для текущего ответа.
     *
     * @return Response
     */
    public function sendContent(): Response
    {
        echo $this->content;

        return $this;
    }

    /**
     * Отправляем HTTP-заголовки.
     *
     * @return Response
     */
    public function sendHeaders(): Response
    {
        // заголовки уже отправлены разработчиком
        if ( headers_sent() ) {
            return $this;
        }

        // headers
        foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) {
            $replace = 0 === strcasecmp($name, 'Content-Type');
            foreach ($values as $value) {
                header($name.': '.$value, $replace, $this->statusCode);
            }
        }

        // cookies
        foreach ($this->headers->getCookies() as $cookie) {
            header('Set-Cookie: '.$cookie, false, $this->statusCode);
        }

        // status
        header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode);

        return $this;
    }

    /**
     * Очищает или сбрасывает выходные буферы до целевого уровня.
     * Результирующий уровень может быть больше целевого уровня, если обнаружен несъемный буфер.
     *
     * @final
     */
    public static function closeOutputBuffers($targetLevel, $flush): void
    {
        $status = ob_get_status(true);
        $level = \count($status);
        $flags = \PHP_OUTPUT_HANDLER_REMOVABLE | ($flush ? \PHP_OUTPUT_HANDLER_FLUSHABLE : \PHP_OUTPUT_HANDLER_CLEANABLE);

        while ($level-- > $targetLevel && ($s = $status[$level]) && (!isset($s['del']) ? !isset($s['flags']) || ($s['flags'] & $flags) === $flags : $s['del'])) {
            if ($flush) {
                ob_end_flush();
            } else {
                ob_end_clean();
            }
        }
    }
}