#include <chrono>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <sstream>
#include <thread>
#include <vector>

struct Maze
{
    enum Cell
    {
        INVALID,
        EDGE,
        WALL,
        PATH,
    };
    Maze(int w, int h, Cell initial)
    :
        w{w},
        h{h},
        cells(w * h, initial)
    {
        for (auto y = 0; y < h; y += h-1)
        for (auto x = 0; x < w; x += 1)
            cells[w * y + x] = EDGE;
        for (auto y = 0; y < h; y += 1)
        for (auto x = 0; x < w; x += w-1)
            cells[w * y + x] = EDGE;
    }
    Cell & operator ()(int x, int y)
    {
        static auto invalid = INVALID; // Yes, naughty.
        if (0 <= x && x < w && 0 <= y && y < h)
            return cells[w * y + x];
        return invalid;
    }
    void print() const
    {
        std::ostringstream oss;
        for (auto y = 0; y < h; ++y, oss << '\n')
        for (auto x = 0; x < w; ++x)
            oss << (cells[w * y + x] == PATH ? "  " : "██");
        std::cout << oss.str();
    }
    int w;
    int h;
    std::vector<Cell> cells;
};

template<typename C = void(*)(Maze &)>
Maze hunt_and_kill(int w, int h, unsigned seed, C callback = [](Maze &){})
{
    auto maze = Maze(w, h, Maze::WALL);
    std::srand(seed);
    // Entrance.
    maze(1, 0) = maze(1, 1) = Maze::PATH;
    // Interior.
    auto cx = 1; auto rx = 1;
    auto cy = 1; auto ry = 1;
    auto done = false;
    while (!done)
    {
        // Call callback.
        callback(maze);
        // Find number of neighbors.
        auto n = 0;
        for (auto ny = -2; ny <= 2; ny += 2)
        for (auto nx = -2; nx <= 2; nx += 2)
            if (!nx != !ny)
            if (maze(cx+nx, cy+ny) == Maze::WALL)
                ++n;
        // If we have neighbors, carve path to random one.
        if (n != 0)
        {
            n = std::rand() % n;
            for (auto ny = -2; ny <= 2; ny += 2)
            for (auto nx = -2; nx <= 2; nx += 2)
                if (!nx != !ny)
                if (maze(cx+nx, cy+ny) == Maze::WALL && n-- == 0)
                {
                    maze(cx+nx/2, cy+ny/2) = maze(cx+nx, cy+ny) = Maze::PATH;
                    cx = cx+nx;
                    cy = cy+ny;
                }
            continue;
        }
        // No neighbors, done unless there's an uncarved cell to restart at.
        done = [&]() {
            for (; ry < h; ry += 2, rx = 1)
            for (; rx < w; rx += 2)
                if (maze(rx, ry) == Maze::WALL)
                    for (auto ny = -2; ny <= 2; ny += 2)
                    for (auto nx = -2; nx <= 2; nx += 2)
                        if (!nx != !ny)
                        if (maze(rx+nx, ry+ny) == Maze::PATH)
                        {
                            cx = rx+nx;
                            cy = ry+ny;
                            return false;
                        }
            return true;
        }();
    }
    // Exit.
    w -= (w + 1) % 2;
    maze(w-2, h-2) = maze(w-2, h-1) = Maze::PATH;
    return maze;
}

int main(int argc, char const * [])
{
    using namespace std::chrono;
    if (argc == 1)
    {
        auto seed = (unsigned)std::time(nullptr);
        auto maze = hunt_and_kill(80/2, 24-1, seed, [](Maze & maze) {
            maze.print();
            std::this_thread::sleep_for(milliseconds(20));
        });
        maze.print();
    }
    else
    {
        auto const t1 = steady_clock::now();
        hunt_and_kill(10000, 10000, 0);
        auto const t2 = steady_clock::now();
        std::cout << duration_cast<milliseconds>(t2 - t1).count() << "ms\n";
    }
}
