#include <cmath>

#include <Perlin2D/Perlin2D.hpp>
#include <Utilities/Utilities.hpp>

float Perlin2D::getAngleAt(uint32_t chunkX, uint32_t chunkY, uint32_t order)
{
    uint64_t key = (((uint64_t) chunkX) << 32) | ((uint64_t) chunkY) | order;
    if (angleMap.count(key) != 0)
    {
        return angleMap[key];
    }

    uint16_t noiseWord = genNode[key];
    double value = (double) noiseWord * (2.0 * 3.14) / (double) 0xFFFF;

    angleMap[key] = value;
    
    return value;
}

float Perlin2D::interpolate(float a, float b, float t)
{
    return std::lerp(a, b, smoothstep(t));
}

float Perlin2D::evaluateVector(float offsetX, float offsetY, float angle)
{
    return offsetX * std::sin(angle) + offsetY * std::cos(angle);
}

Perlin2D::Perlin2D(GeneratorNode node) : genNode(node) {}

float Perlin2D::sample(int x, int y, uint32_t order)
{
    uint32_t length = 32 << order;
    uint32_t mask = length - 1;
    
    float offsetX = (float) (x & mask) / (float) length;
    float offsetY = (float) (y & mask) / (float) length;

    uint32_t chunkX = (uint32_t) (x & ~mask);
    uint32_t chunkY = (uint32_t) (y & ~mask);

    float topLeftSample = evaluateVector(-offsetX, -offsetY, getAngleAt(chunkX, chunkY, order));
    float topRightSample = evaluateVector(1.0f - offsetX, -offsetY, getAngleAt(chunkX+length, chunkY, order));
    float bottomLeftSample = evaluateVector(-offsetX, 1.0f - offsetY, getAngleAt(chunkX, chunkY+length, order));
    float bottomRightSample = evaluateVector(1.0f - offsetX, 1.0f - offsetY, getAngleAt(chunkX+length, chunkY+length, order));

    float topMerged = interpolate(topLeftSample, topRightSample, offsetX);
    float bottomMerged = interpolate(bottomLeftSample, bottomRightSample, offsetX);

    return interpolate(topMerged, bottomMerged, offsetY);
}

float Perlin2D::sampleFractal(int x, int y, int numOctaves, float amplitude, float persistence)
{
    float result = 0.0f;

    for (uint32_t order=0; order<numOctaves; order++)
    {
        result += amplitude * sample(x, y, order);
        amplitude *= persistence;
    }

    return result;
}