// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3

#include "noise_texture.h"
#include "texture_loader.h"
#include <cstdlib>
#include <cstring>

namespace dragonx {
namespace util {

static constexpr int kNoiseSize = 256;

ImTextureID GetNoiseTexture(int* texSize)
{
    static ImTextureID s_tex = 0;
    static bool        s_init = false;

    if (texSize) *texSize = kNoiseSize;

    if (s_init) return s_tex;
    s_init = true;

    // Generate subtle white-noise RGBA pixels.
    // Each pixel is white (RGB=255) with alpha in a narrow band
    // around 128 so the grain is uniform and gentle.
    const int numPixels = kNoiseSize * kNoiseSize;
    unsigned char* pixels = (unsigned char*)malloc(numPixels * 4);
    if (!pixels) return 0;

    // Simple LCG PRNG (deterministic, fast, no need for crypto quality)
    unsigned int seed = 0xDEADBEEF;
    for (int i = 0; i < numPixels; ++i) {
        seed = seed * 1664525u + 1013904223u;
        // Range: 16–240 (centered on 128, ±112) for visible, high-contrast grain
        unsigned char v = 16 + (unsigned char)((seed >> 24) % 225);
        pixels[i * 4 + 0] = 255;   // R
        pixels[i * 4 + 1] = 255;   // G
        pixels[i * 4 + 2] = 255;   // B
        pixels[i * 4 + 3] = v;     // A — high contrast variation
    }

    bool ok = CreateRawTexture(pixels, kNoiseSize, kNoiseSize,
                               true /* repeat/tile */, &s_tex);
    free(pixels);

    if (!ok) {
        s_tex = 0;
    }
    return s_tex;
}

// ============================================================================
// Pre-tiled viewport-sized noise texture
// ============================================================================

// Generate the base 256x256 noise pattern into a caller-allocated buffer.
// Same deterministic LCG as GetNoiseTexture().
static void GenerateBaseNoise(unsigned char* out, int sz)
{
    unsigned int seed = 0xDEADBEEF;
    const int numPixels = sz * sz;
    for (int i = 0; i < numPixels; ++i) {
        seed = seed * 1664525u + 1013904223u;
        unsigned char v = 16 + (unsigned char)((seed >> 24) % 225);
        out[i * 4 + 0] = 255;
        out[i * 4 + 1] = 255;
        out[i * 4 + 2] = 255;
        out[i * 4 + 3] = v;
    }
}

ImTextureID GetTiledNoiseTexture(int viewportW, int viewportH,
                                  int* outW, int* outH)
{
    static ImTextureID s_tex   = 0;
    static int         s_texW  = 0;
    static int         s_texH  = 0;

    if (outW) *outW = s_texW;
    if (outH) *outH = s_texH;

    // Re-use if viewport hasn't changed
    if (s_tex && s_texW == viewportW && s_texH == viewportH) {
        return s_tex;
    }

    // Destroy old texture
    if (s_tex) {
        DestroyTexture(s_tex);
        s_tex = 0;
        s_texW = s_texH = 0;
    }

    if (viewportW <= 0 || viewportH <= 0) return 0;

    // Generate base tile
    unsigned char* base = (unsigned char*)malloc(kNoiseSize * kNoiseSize * 4);
    if (!base) return 0;
    GenerateBaseNoise(base, kNoiseSize);

    // Allocate viewport-sized buffer and tile the base pattern
    const size_t rowBytes = (size_t)viewportW * 4;
    unsigned char* tiled = (unsigned char*)malloc(rowBytes * viewportH);
    if (!tiled) { free(base); return 0; }

    const int basePitch = kNoiseSize * 4;
    for (int y = 0; y < viewportH; ++y) {
        int sy = y % kNoiseSize;
        unsigned char* dst = tiled + y * rowBytes;
        const unsigned char* srcRow = base + sy * basePitch;
        int x = 0;
        // Copy full tile-width strips
        while (x + kNoiseSize <= viewportW) {
            memcpy(dst + x * 4, srcRow, basePitch);
            x += kNoiseSize;
        }
        // Partial remaining strip
        if (x < viewportW) {
            memcpy(dst + x * 4, srcRow, (viewportW - x) * 4);
        }
    }
    free(base);

    bool ok = CreateRawTexture(tiled, viewportW, viewportH,
                               false /* no repeat needed */, &s_tex);
    free(tiled);

    if (ok) {
        s_texW = viewportW;
        s_texH = viewportH;
    } else {
        s_tex = 0;
    }

    if (outW) *outW = s_texW;
    if (outH) *outH = s_texH;
    return s_tex;
}

void DrawTiledNoiseRect(ImDrawList* dl, const ImVec2& pMin, const ImVec2& pMax,
                         ImU32 tintColor)
{
    if (!dl || (tintColor & IM_COL32_A_MASK) == 0) return;

    ImGuiViewport* vp = ImGui::GetMainViewport();
    int vpW = (int)vp->Size.x;
    int vpH = (int)vp->Size.y;
    if (vpW <= 0 || vpH <= 0) return;

    int texW = 0, texH = 0;
    ImTextureID tex = GetTiledNoiseTexture(vpW, vpH, &texW, &texH);
    if (!tex || texW <= 0 || texH <= 0) return;

    // Compute UVs: map screen-space rect to texture coordinates.
    // The tiled texture covers the entire viewport, so UV = screenPos / vpSize.
    // Subtract viewport origin for multi-viewport support.
    float u0 = (pMin.x - vp->Pos.x) / (float)texW;
    float v0 = (pMin.y - vp->Pos.y) / (float)texH;
    float u1 = (pMax.x - vp->Pos.x) / (float)texW;
    float v1 = (pMax.y - vp->Pos.y) / (float)texH;

    dl->AddImage(tex, pMin, pMax, ImVec2(u0, v0), ImVec2(u1, v1), tintColor);
}

} // namespace util
} // namespace dragonx
