<?php

namespace WellRESTed\Routing\Route;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use WellRESTed\Dispatching\DispatcherInterface;

class MethodMap
{
    private $dispatcher;
    private $map;

    // ------------------------------------------------------------------------

    public function __construct(DispatcherInterface $dispatcher)
    {
        $this->map = [];
        $this->dispatcher = $dispatcher;
    }

    // ------------------------------------------------------------------------
    // MethodMapInterface

    /**
     * Register a dispatchable (handler or middleware) with a method.
     *
     * $method may be:
     * - A single verb ("GET"),
     * - A comma-separated list of verbs ("GET,PUT,DELETE")
     * - "*" to indicate any method.
     *
     * $dispatchable may be anything a Dispatcher can dispatch.
     * @see DispatcherInterface::dispatch
     *
     * $dispatchable may also be null, in which case any previously set
     * handlers and middle for that method or methods will be unset.
     *
     * @param string $method
     * @param mixed $dispatchable
     */
    public function register($method, $dispatchable)
    {
        $methods = explode(",", $method);
        $methods = array_map("trim", $methods);
        foreach ($methods as $method) {
            $this->map[$method] = $dispatchable;
        }
    }

    // ------------------------------------------------------------------------
    // MiddlewareInterface

    /**
     * @param ServerRequestInterface $request
     * @param ResponseInterface $response
     * @param callable $next
     * @return ResponseInterface
     */
    public function __invoke(
        ServerRequestInterface $request,
        ResponseInterface $response,
        $next
    ) {
        $method = $request->getMethod();
        // Dispatch middleware registered with the explicitly matching method.
        if (isset($this->map[$method])) {
            $middleware = $this->map[$method];
            return $this->dispatchMiddleware($middleware, $request, $response, $next);
        }
        // For HEAD, dispatch GET by default.
        if ($method === "HEAD" && isset($this->map["GET"])) {
            $middleware = $this->map["GET"];
            return $this->dispatchMiddleware($middleware, $request, $response, $next);
        }
        // Dispatch * middleware, if registered.
        if (isset($this->map["*"])) {
            $middleware = $this->map["*"];
            return $this->dispatchMiddleware($middleware, $request, $response, $next);
        }
        // Respond describing the allowed methods, either as a 405 response or
        // in response to an OPTIONS request.
        if ($method === "OPTIONS") {
            $response = $response->withStatus(200);
        } else {
            $response = $response->withStatus(405);
        }
        return $this->addAllowHeader($response);
    }

    // ------------------------------------------------------------------------

    private function addAllowHeader(ResponseInterface $response)
    {
        $methods = join(",", $this->getAllowedMethods());
        return $response->withHeader("Allow", $methods);
    }

    private function getAllowedMethods()
    {
        $methods = array_keys($this->map);
        // Add HEAD if GET is allowed and HEAD is not present.
        if (in_array("GET", $methods) && !in_array("HEAD", $methods)) {
            $methods[] = "HEAD";
        }
        // Add OPTIONS if not already present.
        if (!in_array("OPTIONS", $methods)) {
            $methods[] = "OPTIONS";
        }
        return $methods;
    }

    /**
     * @param $middleware
     * @param ServerRequestInterface $request
     * @param ResponseInterface $response
     * @param $next
     * @return ResponseInterface
     */
    private function dispatchMiddleware(
        $middleware,
        ServerRequestInterface $request,
        ResponseInterface &$response,
        $next
    ) {
        return $this->dispatcher->dispatch($middleware, $request, $response, $next);
    }
}
