#include <TunnelGenerator/TunnelGenerator.hpp>
#include <WorldGenerator/SurvivalWorldGenerator/SurvivalWorldGenerator.hpp>
#include <Log/Log.hpp>
#include <cmath>

void TunnelGenerator::decodeSegments(TunnelSegment *outSegments, uint64_t word)
{
    int i;
    for (i=0; i<8; i++)
    {
        int radiusInt = word % 6;
        outSegments[i].radius = 4.0 + (double) radiusInt;

        switch ((word >> 3) & 0x7)
        {
        case 0:
            outSegments[i].curve = glm::dvec3(1.0, 0.0, 0.0);
            break;
        case 1:
            outSegments[i].curve = glm::dvec3(-1.0, 0.0, 0.0);
            break;
        case 2:
            outSegments[i].curve = glm::dvec3(0.0, 1.0, 0.0);
            break;
        case 3:
            outSegments[i].curve = glm::dvec3(0.0, -1.0, 0.0);
            break;
        case 4:
            outSegments[i].curve = glm::dvec3(0.0, 0.0, 1.0);
            break;
        case 5:
            outSegments[i].curve = glm::dvec3(0.0, 0.0, -1.0);
            break;
        case 6:
            outSegments[i].curve = glm::dvec3(1.0, 1.0, 0.0);
            break;
        case 7:
            outSegments[i].curve = glm::dvec3(-1.0, -1.0, 0.0);
            break;
        }

        word >>= 8;
    }
}

void TunnelGenerator::computeTunnelsAt(std::vector<Tunnel> &outTunnels, glm::ivec3 regionBase)
{
    regionBase.x &= ~0xFF;
    regionBase.y &= ~0xFF;
    regionBase.z &= ~0xFF;

    uint64_t key1 = ((uint64_t) (uint32_t) regionBase.x << 32) | (uint64_t) (uint32_t) regionBase.y;
    uint64_t key2 = (uint64_t) (uint32_t) regionBase.z;

    GeneratorNode regionNode = caveNode(key1, key2);
    
    int numTunnels = regionNode.seed % maxTunnelCount;
    int i;
    for (i=0; i<numTunnels; i++)
    {
        GeneratorNode tunnelNode = regionNode(i);
        uint64_t tunnelWord = tunnelNode.seed;

        Tunnel tunnel;
        tunnel.startPos = glm::ivec3(tunnelWord & 0xFF, (tunnelWord >> 8) & 0xFF, (tunnelWord >> 16) & 0xFF);
        tunnel.startPos += regionBase;
        tunnel.numSegments = 64 + (tunnelWord >> 24) % 46;

        double overallStartZ = tunnel.startPos.z + regionBase.z;
        if (overallStartZ > -16)
        {
            continue;
        }

        int startAngleInt = (tunnelWord >> 32) & 0x1F;
        double startAngle = (double) startAngleInt * 3.14 * 2.0 / 32.0;
        tunnel.startDir = glm::dvec3(std::sin(startAngle), std::cos(startAngle), 0.0);

        int j;
        for (j=0; j<tunnel.numSegments; j+=8)
        {
            decodeSegments(&tunnel.segments[j], tunnelNode[j]);
        }

        outTunnels.push_back(tunnel);
    }
}

TunnelGenerator::CaveGrid::CaveGrid() : data(256 * 256 * 256, false) {}

bool TunnelGenerator::CaveGrid::get(glm::ivec3 pos)
{
    return data[256 * 256 * pos.z + 256 * pos.y + pos.x];
}

void TunnelGenerator::CaveGrid::set(glm::ivec3 pos, bool hasCave)
{
    data[256 * 256 * pos.z + 256 * pos.y + pos.x] = hasCave;
}

void TunnelGenerator::CaveGrid::carveSphere(glm::dvec3 center, double radius)
{
    int startX = std::max(0, (int) std::floor(center.x - radius));
    int startY = std::max(0, (int) std::floor(center.y - radius));
    int startZ = std::max(0, (int) std::floor(center.z - radius));

    int endX = std::min(255, (int) std::floor(center.x + radius));
    int endY = std::min(255, (int) std::floor(center.y + radius));
    int endZ = std::min(255, (int) std::floor(center.z + radius));

    int x, y, z;
    for (z=startZ; z<=endZ; z++)
    {
        for (y=startY; y<=endY; y++)
        {
            for (x=startX; x<=endX; x++)
            {
                glm::dvec3 pos((double) x, (double) y, (double) z);
                if (glm::length(pos - center) <= radius)
                {
                    set(pos, true);
                }
            }
        }
    }
}

void TunnelGenerator::computeGridAt(CaveGrid &grid, int regionX, int regionY, int regionZ)
{
    std::vector<Tunnel> tunnels;
    tunnels.reserve(maxTunnelCount * 3 * 3 * 3);

    glm::ivec3 regionBase;
    for (regionBase.z=regionZ-256; regionBase.z!=regionZ+256+256; regionBase.z+=256)
    {
        for (regionBase.y=regionY-256; regionBase.y!=regionY+256+256; regionBase.y+=256)
        {
            for (regionBase.x=regionX-256; regionBase.x!=regionX+256+256; regionBase.x+=256)
            {
                computeTunnelsAt(tunnels, regionBase);
            }
        }
    }

    for (Tunnel &tunnel : tunnels)
    {
        glm::dvec3 regionPos((double) regionX, (double) regionY, (double) regionZ);
        glm::dvec3 currentPos = glm::dvec3(tunnel.startPos) - regionPos;
        glm::dvec3 currentDir = tunnel.startDir;
        
        int i;
        for (i=0; i<tunnel.numSegments; i++)
        {
            double overallZ = currentPos.z + regionPos.z;

            double radius = tunnel.segments[i].radius;
            if (overallZ > -8)
            {
                radius = 4.0;
            }

            grid.carveSphere(currentPos, radius);
            currentPos += 2.0 * currentDir;

            if (overallZ > -8)
            {
                currentDir.z = currentDir.y = currentDir.x;
                currentDir.z = std::abs(currentDir.z);
            }
            else
            {
                currentDir = glm::normalize(currentDir + tunnelCurvature * tunnel.segments[i].curve);
            }
        }
    }
}

TunnelGenerator::CaveGrid& TunnelGenerator::gridAt(int regionX, int regionY, int regionZ)
{
    std::shared_lock<std::shared_mutex> shlock(gridCacheMutex);
    if (gridCache.count(regionX) && gridCache[regionX].count(regionY) && gridCache[regionX][regionY].count(regionZ))
    {
        return *gridCache[regionX][regionY][regionZ];
    }

    shlock.unlock();

    std::unique_lock<std::shared_mutex> ulock(gridCacheMutex);
    if (gridCache.count(regionX) && gridCache[regionX].count(regionY) && gridCache[regionX][regionY].count(regionZ))
    {
        return *gridCache[regionX][regionY][regionZ];
    }

    std::shared_ptr<CaveGrid> grid = std::make_shared<CaveGrid>();
    std::unique_lock<std::shared_mutex> gridLock(grid->mutex);
    gridCache[regionX][regionY][regionZ] = grid;
    ulock.unlock();

    computeGridAt(*grid, regionX, regionY, regionZ);
    return *grid;
}

TunnelGenerator::TunnelGenerator(SurvivalWorldGenerator &worldgen) :
    worldgen(worldgen),
    caveNode(worldgen.getRootNode()[0x9afd3b72b3555719])
{}

bool TunnelGenerator::isInsideTunnel(glm::ivec3 pos)
{
    if (pos.z >= 32)
    {
        return false;
    }

    CaveGrid &grid = gridAt(pos.x & ~0xFF, pos.y & ~0xFF, pos.z & ~0xFF);
    std::shared_lock<std::shared_mutex> lock(grid.mutex);

    glm::ivec3 regionOffset(pos & 0xFF);

    return grid.get(regionOffset);
}

void TunnelGenerator::generate(Chunk &chunk)
{
    if (chunk.base.z > 16)
    {
        return;
    }
    
    CaveGrid &grid = gridAt(chunk.base.x & ~0xFF, chunk.base.y & ~0xFF, chunk.base.z & ~0xFF);
    std::shared_lock<std::shared_mutex> lock(grid.mutex);

    glm::ivec3 regionOffset = chunk.base & 0xFF;

    glm::ivec3 offset;
    for (offset.x=0; offset.x<16; offset.x++)
    {
        for (offset.y=0; offset.y<16; offset.y++)
        {
            int lowestTopZ = 16;
            if (chunk.base.z >= -16)
            {
                int dx, dy;
                for (dx=-1; dx<=1; dx++)
                {
                    for (dy=-1; dy<=1; dy++)
                    {
                        int heightHere = worldgen.terrain.getTopZ(offset.x+chunk.base.x+dx, offset.y+chunk.base.y+dy);
                        if (heightHere < lowestTopZ)
                        {
                            lowestTopZ = heightHere;
                        }
                    }
                }
            }

            for (offset.z=0; offset.z<16; offset.z++)
            {
                if (lowestTopZ < minTerrainForTunnel && chunk.base.z+offset.z >= lowestTopZ)
                {
                    break;
                }

                if (grid.get(offset + regionOffset))
                {
                    chunk.setBlockAndMeta(offset, 0, 0);
                }
            }
        }
    }
}