#include "../Log.h"
#include "Dither.h"

void DitherGen(DitherData& data, int32_t recursion)
{
    data.Points[0] = {0.0f, 0.0f};
    data.Points[1] = {0.5f, 0.5f};
    data.Points[2] = {0.5f, 0.0f};
    data.Points[3] = {0.0f, 0.5f};
    data.PointCount = 4;
    for (int32_t i = 0; i < data.BrightnessBucketCount; ++i)
        data.BrightnessBuckets[i] = 0;

    for (int32_t recursionLevel = 0; recursionLevel < recursion; ++recursionLevel)
    {
        int32_t startCount = data.PointCount;
        float offset = bx::pow(0.5f, recursionLevel + 1);
        for (int32_t i = 0; i < 4; ++i)
        {
            for (int32_t j = 0; j < startCount; ++j)
            {
                data.Points[data.PointCount] = data.Points[j] + data.Points[i] * offset;
                data.PointCount++;
            }
        }
    }

    uint64_t dotsPerSide = bx::round(bx::pow(2, recursion));
    data.DitherTexDepth = dotsPerSide * dotsPerSide;
    data.DitherTexWH = 16 * dotsPerSide;
    uint64_t texPixelCount = data.DitherTexWH * data.DitherTexWH * data.DitherTexDepth;

    if (BX_COUNTOF(DitherData::DitherTex) < texPixelCount)
    {
        LOG_ERROR("Too many pixels: %llu", texPixelCount);
        return;
    }

    float invRes = 1.0f / data.DitherTexWH;
    for (int32_t z = 0; z < data.DitherTexDepth; ++z)
    {
        int32_t dotCount = z + 1;
        float dotArea = 0.5f / dotCount;
        float dotRadius = bx::sqrt(dotArea / bx::kPi);

        int zOffset = z * data.DitherTexWH * data.DitherTexWH;
        for (int32_t y = 0; y < data.DitherTexWH; ++y)
        {
            int32_t yOffset = y * data.DitherTexWH;
            for (int32_t x = 0; x < data.DitherTexWH; ++x)
            {
                Vec2 point{(x + 0.5f) * invRes, (y + 0.5f) * invRes};
                float dist = bx::kFloatInfinity;
                for (int32_t i = 0; i < dotCount; ++i)
                {
                    Vec2 vec = point - data.Points[i];
                    Vec2 wrappedVec{bx::mod(vec.x + 0.5f, 1.0f) - 0.5f, bx::mod(vec.y + 0.5f, 1.0f) - 0.5f};
                    float curDist = wrappedVec.Magnitude();
                    dist = bx::min(dist, curDist);
                }

                dist = dist / (dotRadius * 2.4f);
                float val = bx::clamp(1.0f - dist, 0.0f, 1.0f);
                data.DitherTex[x + yOffset + zOffset] = Vec4{val, val, val, 1.0f};
                // data.DitherTex[x + yOffset + zOffset] = Vec4{1.0, 0.0f, 0.0f, 1.0f};
                int32_t bucket = bx::clamp(
                    uint32_t(val * DitherData::BrightnessBucketCount), 0, DitherData::BrightnessBucketCount - 1);
                data.BrightnessBuckets[bucket] += 1;
            }
        }
    }

    // Brightness ramp
    int32_t sum = 0;
    for (int32_t i = 0; i < data.BrightnessBucketCount; ++i)
    {
        sum += data.BrightnessBuckets[data.BrightnessBucketCount - 1 - i];
        data.BrightnessRamp[i + 1] = sum / (float)texPixelCount;
    }

    // Upload textures
    if (isValid(data.PreviewTex))
    {
        bgfx::destroy(data.PreviewTex);
        data.PreviewTex = BGFX_INVALID_HANDLE;
    }
    if (isValid(data.FinalTex))
    {
        bgfx::destroy(data.FinalTex);
        data.FinalTex = BGFX_INVALID_HANDLE;
    }
    if (isValid(data.RampTex))
    {
        bgfx::destroy(data.RampTex);
        data.RampTex = BGFX_INVALID_HANDLE;
    }
    const bgfx::Memory* memPreview = bgfx::makeRef(data.DitherTex, texPixelCount * sizeof(Vec4));
    const bgfx::Memory* memFinal = bgfx::makeRef(data.DitherTex, texPixelCount * sizeof(Vec4));
    const bgfx::Memory* memRamp = bgfx::makeRef(data.BrightnessRamp, sizeof(data.BrightnessRamp));
    data.PreviewTex = bgfx::createTexture2D(data.DitherTexWH,
                                            data.DitherTexWH * data.DitherTexDepth,
                                            false,
                                            false,
                                            bgfx::TextureFormat::RGBA32F,
                                            0,
                                            memPreview);
    data.FinalTex = bgfx::createTexture3D(
        data.DitherTexWH, data.DitherTexWH, data.DitherTexDepth, false, bgfx::TextureFormat::RGBA32F, 0, memFinal);
    data.RampTex =
        bgfx::createTexture2D(BX_COUNTOF(data.BrightnessRamp), 1, false, 1, bgfx::TextureFormat::R32F, 0, memRamp);

    if (!isValid(data.DitherSampler))
    {
        data.DitherSampler = bgfx::createUniform("s_ditherSampler", bgfx::UniformType::Sampler);
    }
    if (!isValid(data.RampSampler))
    {
        data.RampSampler = bgfx::createUniform("s_rampSampler", bgfx::UniformType::Sampler);
    }
}
