<?php
/**
 * Jingga
 *
 * PHP Version 8.2
 *
 * @package   phpOMS\Algorithm\Maze
 * @copyright Dennis Eichhorn
 * @license   OMS License 2.2
 * @version   1.0.0
 * @link      https://jingga.app
 */
declare(strict_types=1);

namespace phpOMS\Algorithm\Maze;

/**
 * Maze generator
 *
 * @package phpOMS\Algorithm\Maze
 * @license OMS License 2.2
 * @link    https://jingga.app
 * @since   1.0.0
 */
class MazeGenerator
{
    /**
     * Constructor
     *
     * @since 1.0.0
     * @codeCoverageIgnore
     */
    private function __construct()
    {
    }

    /**
     * Generate a random maze
     *
     * @param int<0, max> $width  Width
     * @param int<0, max> $height Height
     *
     * @return array
     *
     * @since 1.0.0
     */
    public static function random(int $width, int $height) : array
    {
        $n          = $height * $width - 1;
        $horizontal = \array_fill(0, $height, []);
        $vertical   = \array_fill(0, $height, []);

        $pos       = [\mt_rand(0, $height) - 1, \mt_rand(0, $width) - 1];
        $path      = [$pos];
        $unvisited = [];

        for ($i = 0; $i < $height + 2; ++$i) {
            $unvisited[] = [];

            for ($j = 0; $j < $width + 1; ++$j) {
                $unvisited[$i][] = $i > 0 && $i < $height + 1 && $j > 0 && ($i !== $pos[0] + 1 || $j != $pos[1] + 1);
            }
        }

        while ($n > 0) {
            $potential = [
                [$pos[0] + 1, $pos[1]],
                [$pos[0], $pos[1] + 1],
                [$pos[0] - 1, $pos[1]],
                [$pos[0], $pos[1] - 1],
            ];

            $neighbors = [];

            for ($i = 0; $i < 4; ++$i) {
                if ($unvisited[$potential[$i][0] + 1][$potential[$i][1] + 1] ?? false) {
                    $neighbors[] = $potential[$i];
                }
            }

            if (!empty($neighbors)) {
                --$n;

                $next                                  = $neighbors[\array_rand($neighbors, 1)];
                $unvisited[$next[0] + 1][$next[1] + 1] = false;

                if ($next[0] === $pos[0]) {
                    $horizontal[$next[0]][($next[1] + $pos[1] - 1) / 2] = true;
                } else {
                    $vertical[($next[0] + $pos[0] - 1) / 2][$next[1]] = true;
                }

                $path[] = $next;
                $pos    = $next;
            } else {
                $pos = \array_pop($path);

                if ($pos === null) {
                    break; // @codeCoverageIgnore
                }
            }
        }

        $maze = [];
        for ($i = 0; $i < $height * 2 + 1; ++$i) {
            $line = [];

            if ($i % 2 === 0) {
                for ($j = 0; $j < $width * 4 + 1; ++$j) {
                    if ($j % 4 === 0) {
                        $line[$j] = '+'; // 9
                    } else {
                        $line[$j] = $i > 0 && ($vertical[$i / 2 - 1][(int) \floor($j / 4)] ?? false) ? ' ' : '-'; // 9
                    }
                }
            } else {
                for ($j = 0; $j < $width * 4 + 1; ++$j) { // 2
                    if ($j % 4 === 0) {
                        $line[$j] = $j > 0 && ($horizontal[($i - 1) / 2][$j / 4 - 1] ?? false) ? ' ' : '|'; // 0 | 9
                    } else {
                        $line[$j] = ' '; // 0
                    }
                }
            }

            if ($i === 0) {
                $line[1] = $line[2] = $line[3] = ' '; // 0
            }

            if ($height * 2 - 1 === $i) {
                $line[4 * $width] = ' '; // 2 - 0
            }

            $maze[] = $line;
        }

        return $maze;
    }

    /**
     * Render a maze
     *
     * @param array<int, int[]> $maze Maze to render
     *
     * @return string
     *
     * @since 1.0.0
     */
    public static function render(array $maze) : string
    {
        $rendered = '';
        foreach ($maze as $row) {
            foreach ($row as $column) {
                $rendered .= $column;
            }

            $rendered .= "\n";
        }

        return $rendered;
    }
}
