<?php

/**
 * This file extends the Router class from the CodeIgniter 4 framework.
 *
 * It introduces the alternate-content option for a route.
 *
 * @copyright  2021 Podlibre
 * @license    https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
 * @link       https://castopod.org/
 */

namespace App\Libraries;

use Config\Services;
use CodeIgniter\Router\Exceptions\RedirectException;

class Router extends \CodeIgniter\Router\Router
{
    /**
     * Compares the uri string against the routes that the
     * RouteCollection class defined for us, attempting to find a match.
     * This method will modify $this->controller, etal as needed.
     *
     * @param string $uri The URI path to compare against the routes
     *
     * @return boolean Whether the route was matched or not.
     * @throws RedirectException
     */
    protected function checkRoutes(string $uri): bool
    {
        $routes = $this->collection->getRoutes(
            $this->collection->getHTTPVerb(),
        );

        // Don't waste any time
        if (empty($routes)) {
            return false;
        }

        $uri = $uri === '/' ? $uri : ltrim($uri, '/ ');

        // Loop through the route array looking for wildcards
        foreach ($routes as $key => $val) {
            // Reset localeSegment
            $localeSegment = null;

            $key = $key === '/' ? $key : ltrim($key, '/ ');

            $matchedKey = $key;

            // Are we dealing with a locale?
            if (strpos($key, '{locale}') !== false) {
                $localeSegment = array_search(
                    '{locale}',
                    preg_split(
                        '/[\/]*((^[a-zA-Z0-9])|\(([^()]*)\))*[\/]+/m',
                        $key,
                    ),
                    true,
                );

                // Replace it with a regex so it
                // will actually match.
                $key = str_replace('/', '\/', $key);
                $key = str_replace('{locale}', '[^\/]+', $key);
            }

            // Does the RegEx match?
            if (preg_match('#^' . $key . '$#u', $uri, $matches)) {
                $this->matchedRouteOptions = $this->collection->getRoutesOptions(
                    $matchedKey,
                );

                // Is this route supposed to redirect to another?
                if ($this->collection->isRedirect($key)) {
                    throw new RedirectException(
                        is_array($val) ? key($val) : $val,
                        $this->collection->getRedirectCode($key),
                    );
                }
                // Store our locale so CodeIgniter object can
                // assign it to the Request.
                if (isset($localeSegment)) {
                    // The following may be inefficient, but doesn't upset NetBeans :-/
                    $temp = explode('/', $uri);
                    $this->detectedLocale = $temp[$localeSegment];
                }

                // Are we using Closures? If so, then we need
                // to collect the params into an array
                // so it can be passed to the controller method later.
                if (!is_string($val) && is_callable($val)) {
                    $this->controller = $val;

                    // Remove the original string from the matches array
                    array_shift($matches);

                    $this->params = $matches;

                    $this->matchedRoute = [$matchedKey, $val];

                    return true;
                }

                // Is there an alternate content for the matchedRoute?

                // check if the alternate-content has been requested in the accept
                // header and overwrite the $val with the matching controller method
                if (
                    array_key_exists(
                        'alternate-content',
                        $this->matchedRouteOptions,
                    ) &&
                    is_array($this->matchedRouteOptions['alternate-content'])
                ) {
                    $request = Services::request();
                    $negotiate = Services::negotiator();

                    $acceptHeader = $request->getHeader('Accept')->getValue();
                    $parsedHeader = $negotiate->parseHeader($acceptHeader);

                    $supported = array_keys(
                        $this->matchedRouteOptions['alternate-content'],
                    );

                    $expectedContentType = $parsedHeader[0];
                    foreach ($supported as $available) {
                        if (
                            $negotiate->callMatch(
                                $expectedContentType,
                                $available,
                                true,
                            )
                        ) {
                            if (
                                array_key_exists(
                                    'namespace',
                                    $this->matchedRouteOptions[
                                        'alternate-content'
                                    ][$available],
                                )
                            ) {
                                $this->collection->setDefaultNamespace(
                                    $this->matchedRouteOptions[
                                        'alternate-content'
                                    ][$available]['namespace'],
                                );
                            }
                            $val =
                                $this->collection->getDefaultNamespace() .
                                $this->directory .
                                $this->matchedRouteOptions['alternate-content'][
                                    $available
                                ]['controller-method'];

                            // no need to continue loop as $val has been overwritten
                            break;
                        }
                    }
                }

                // Are we using the default method for back-references?

                // Support resource route when function with subdirectory
                // ex: $routes->resource('Admin/Admins');
                if (
                    strpos($val, '$') !== false &&
                    strpos($key, '(') !== false &&
                    strpos($key, '/') !== false
                ) {
                    $replacekey = str_replace('/(.*)', '', $key);
                    $val = preg_replace('#^' . $key . '$#u', $val, $uri);
                    $val = str_replace(
                        $replacekey,
                        str_replace('/', '\\', $replacekey),
                        $val,
                    );
                } elseif (
                    strpos($val, '$') !== false &&
                    strpos($key, '(') !== false
                ) {
                    $val = preg_replace('#^' . $key . '$#u', $val, $uri);
                } elseif (strpos($val, '/') !== false) {
                    [$controller, $method] = explode('::', $val);

                    // Only replace slashes in the controller, not in the method.
                    $controller = str_replace('/', '\\', $controller);

                    $val = $controller . '::' . $method;
                }

                $this->setRequest(explode('/', $val));

                $this->matchedRoute = [$matchedKey, $val];

                return true;
            }
        }

        return false;
    }

    //--------------------------------------------------------------------
}