#include <Bitmap/Bitmap.hpp>
#include <DevCommand/TerrainDumpCommand/TerrainDumpCommand.hpp>
#include <Log/Log.hpp>
#include <Utilities/Async.hpp>
#include <World/LocalWorld/SinglePlayerWorld/SinglePlayerWorld.hpp>
#include <WorldGenerator/SurvivalWorldGenerator/SurvivalWorldGenerator.hpp>
#include <ChunkWindow/ChunkWindow.hpp>

int TerrainDumpCommand::execute(const std::vector<std::string> &args)
{
    if (args.size() != 4)
    {
        Log::error("SYNTAX: terraindump <survival-world-file> <output-png>");
        return 1;
    }

    std::string worldName = args[2];
    std::string outputFileName = args[3];

    SinglePlayerWorld localWorld(worldName);
    SurvivalWorldGenerator &worldgen = dynamic_cast<SurvivalWorldGenerator&>(*localWorld.worldGenerator);

    struct
    {
        std::atomic<int> numWater;
        std::atomic<int> numFlat;
        std::atomic<int> num100;
        std::atomic<int> num200;
        std::atomic<int> num300;
        std::atomic<int> num400;
        std::atomic<int> num500;
        std::atomic<int> num600;
        std::atomic<int> numExtreme;

        std::mutex mutex;
        int minPeak = 0;
        int maxPeak = 0;
    } stats;

    const int mapWidthInTiles = 64;
    const int mapHeightInTiles = 16;

    Bitmap map(glm::ivec2(mapWidthInTiles << 8, mapHeightInTiles << 8));
    glm::ivec2 gridPos;

    for (gridPos.y = 0; gridPos.y < mapHeightInTiles; gridPos.y++)
    {
        for (gridPos.x = 0; gridPos.x < mapWidthInTiles; gridPos.x++)
        {
            dispatchTask([&map, &worldgen, &stats, gridPos]()
            {
                TerrainGenerator::TerrainGrid &grid = worldgen.terrain.gridAt(gridPos.x << 8, gridPos.y << 8);
                glm::ivec2 offset;

                int minPeak = 0;
                int maxPeak = 0;

                for (offset.y = 0; offset.y < 256; offset.y++)
                {
                    for (offset.x = 0; offset.x < 256; offset.x++)
                    {
                        TerrainGenerator::Cell &cell = grid.data[(offset.y << 8) + offset.x];
                        int topZ = cell.height;

                        minPeak = std::min(minPeak, topZ);
                        maxPeak = std::max(maxPeak, topZ);

                        RGBABytes color;

                        if (topZ < 0)
                        {
                            color = {{0x00, 0x44, 0xDD, 0xFF}};
                            stats.numWater++;
                        }
                        else if (topZ < 5)
                        {
                            color = {{0x98, 0xFB, 0x98, 0xFF}};
                            stats.numFlat++;
                        }
                        else if (topZ < 100)
                        {
                            color = {{0xFF, 0xFF, 0x00, 0xFF}};
                            stats.num100++;
                        }
                        else if (topZ < 200)
                        {
                            color = {{0xFF, 0xA5, 0x00, 0xFF}};
                            stats.num200++;
                        }
                        else if (topZ < 300)
                        {
                            color = {{0xFF, 0x45, 0x00, 0xFF}};
                            stats.num300++;
                        }
                        else if (topZ < 400)
                        {
                            color = {{0x8B, 0x45, 0x13, 0xFF}};
                            stats.num400++;
                        }
                        else if (topZ < 500)
                        {
                            color = {{0xD2, 0xB4, 0x8C, 0xFF}};
                            stats.num500++;
                        }
                        else if (topZ < 600)
                        {
                            color = {{0xA9, 0xA9, 0xA9, 0xFF}};
                            stats.num600++;
                        }
                        else
                        {
                            color = {{0xD3, 0xD3, 0xD3, 0xFF}};
                            stats.numExtreme++;
                        }

                        map.setColor((gridPos << glm::ivec2(8)) + offset, color);
                    }
                }

                std::unique_lock<std::mutex> lock(stats.mutex);
                stats.minPeak = std::min(stats.minPeak, minPeak);
                stats.maxPeak = std::max(stats.maxPeak, maxPeak);
            });
        }
    }

    Log::info("Waiting for generation.");
    Uint32 startTicks = SDL_GetTicks();

    volatile bool done = false;
    afterAsyncBarrier(AsyncBarrier::CurrentlyDispatched, [&done]()
    {
        done = true;
    });

    while (!done)
    {
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }

    int seconds = (SDL_GetTicks() - startTicks) / 1000;

    map.writeToPNG(outputFileName);
    
    int totalBlocks = 256 * 256 * mapWidthInTiles * mapHeightInTiles;

    Log::info(formatString("Terrain dump generated, took $+ seconds", seconds));
    Log::info(formatString("  Generated size:   $+x$+", 256 * mapWidthInTiles, 256 * mapHeightInTiles));
    Log::info(formatString("  Water:            $+%", (double) stats.numWater / (double) totalBlocks * 100.0));
    Log::info(formatString("  Flat (<5):        $+%", (double) stats.numFlat / (double) totalBlocks * 100.0));
    Log::info(formatString("  Elevation (<100): $+%", (double) stats.num100 / (double) totalBlocks * 100.0));
    Log::info(formatString("  Elevation (<200): $+%", (double) stats.num200 / (double) totalBlocks * 100.0));
    Log::info(formatString("  Elevation (<300): $+%", (double) stats.num300 / (double) totalBlocks * 100.0));
    Log::info(formatString("  Elevation (<400): $+%", (double) stats.num400 / (double) totalBlocks * 100.0));
    Log::info(formatString("  Elevation (<500): $+%", (double) stats.num500 / (double) totalBlocks * 100.0));
    Log::info(formatString("  Elevation (<600): $+%", (double) stats.num600 / (double) totalBlocks * 100.0));
    Log::info(formatString("  Elevation (>600): $+%", (double) stats.numExtreme / (double) totalBlocks * 100.0));
    Log::info(formatString("  Min peak:         $+", stats.minPeak));
    Log::info(formatString("  Max peak:         $+", stats.maxPeak));
    return 0;
}