<?php

namespace Dipper\Console\Commands;

use Dipper\Console\Finder;
use Dipper\Console\Command;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Laravel\Lumen\Routing\Router;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Command\Command as SymfonyCommand;

/**
 * @author HollyTeng <n.haoyuan@gmail.com>
 */
class RouteListCommand extends SymfonyCommand
{
    use Finder;
    use Command;

    /**
     * The console command name.
     *
     * @var string
     */
    protected $name = 'list:routes';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'List the routes.';

    /**
     * The router instance.
     *
     * @var \Illuminate\Routing\Router
     */
    protected $router;

    /**
     * An array of all the registered routes.
     *
     * @var \Illuminate\Routing\RouteCollection
     */
    protected $routes;

    /**
     * The table headers for the command.
     *
     * @var array
     */
    protected $headers = ['Domain', 'Method', 'URI', 'Name', 'Action', 'Middleware'];

    /**
     * Execute the console command.
     *
     * @return bool|null
     */
    public function fire()
    {
        $this->router = app(Router::class);
        $this->routes = $this->router->getRoutes();

        if (count($this->routes) == 0) {
            return $this->error("Your application doesn't have any routes.");
        }

        $this->displayRoutes($this->getRoutes());
    }

    /**
     * Compile the routes into a displayable format.
     *
     * @return array
     */
    protected function getRoutes()
    {
        $routes = collect($this->routes)->map(function ($route) {
            return $this->getRouteInformation($route);
        })->all();

        if ($sort = $this->option('sort')) {
            $routes = $this->sortRoutes($sort, $routes);
        }

        if ($this->option('reverse')) {
            $routes = array_reverse($routes);
        }

        return array_filter($routes);
    }

    /**
     * Get the route information for a given route.
     *
     * @param  \Illuminate\Routing\Route  $route
     * @return array
     */
    protected function getRouteInformation(array $route)
    {
        $pattern = "/^".$this->findRootNamespace()."\\\\Domains\\\\(\w+)\\\\Http\\\\Controllers$/";
        preg_match($pattern, $this->getNamespace($route['action']['uses']), $matches);
        $domain = $matches ? $matches[1] : '';

        return $this->filterRoute([
            'domain' => $domain,
            'method' => $route['method'],
            'uri'    => $route['uri'],
            'name'   => $route['action']['as'],
            'action' => $route['action']['uses'],
            'middleware' => implode($route['action']['middleware'] ?? []),
        ]);
    }

    /**
     * Sort the routes by a given element.
     *
     * @param  string  $sort
     * @param  array  $routes
     * @return array
     */
    protected function sortRoutes($sort, $routes)
    {
        return Arr::sort($routes, function ($route) use ($sort) {
            return $route[$sort];
        });
    }

    /**
     * Display the route information on the console.
     *
     * @param  array  $routes
     * @return void
     */
    protected function displayRoutes(array $routes)
    {
        $this->table($this->headers, $routes);
    }

    /**
     * Filter the route by URI and / or name.
     *
     * @param  array  $route
     * @return array|null
     */
    protected function filterRoute(array $route)
    {
        if ($filter = $this->option('filter')) {
            if (!Str::contains($route['name'], $filter) && !Str::contains($route['path'], $filter) && !Str::contains($route['method'], $filter)) {
                return;
            }
        }

        return $route;
    }

    /**
     * Get the console command options.
     *
     * @return array
     */
    protected function getOptions()
    {
        return [
            ['filter', 'f', InputOption::VALUE_OPTIONAL, 'Filter the routes.'],
            ['reverse', 'r', InputOption::VALUE_NONE, 'Reverse the ordering of the routes.'],
            ['sort', null, InputOption::VALUE_OPTIONAL, 'The column (host, method, uri, name, action, middleware) to sort by.', 'uri'],
        ];
    }
}
