/*
Crafter®.Graphics
Copyright (C) 2026 Catcrafts®
catcrafts.net

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License version 3.0 as published by the Free Software Foundation;

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
*/
module;
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
#include "vulkan/vulkan.h"
#endif
export module Crafter.Graphics:FontAtlas;
import std;
import :Font;
import :GraphicsTypes;
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
import :ImageVulkan;
import :Device;
#else
import :WebGPU;
#endif

export namespace Crafter {
    // Per-glyph metrics. UVs are 0..1 in atlas space; on-screen sizes /
    // offsets / advance are in *atlas pixels at the base size* and scale
    // linearly with the requested font size at draw time.
    struct Glyph {
        float u0 = 0, v0 = 0;
        float u1 = 0, v1 = 0;
        float w  = 0, h  = 0;
        float xoff = 0, yoff = 0;
        float advance = 0;
    };

    class FontAtlas {
    public:
        static constexpr int   kAtlasSize     = 1024;
        static constexpr float kBaseSize      = 32.0f;
        static constexpr int   kPadding       = 4;
        static constexpr int   kOnEdgeValue   = 128;
        static constexpr float kPixelDistScale = 32.0f;

        // Bounding box of the atlas pixels touched since the last upload,
        // used to copy only the sub-rect that actually changed instead of
        // the whole 1 MiB atlas. Half-open: covers [minX, maxX) × [minY,
        // maxY). `active` distinguishes "nothing dirty" from a zero-origin
        // rect.
        struct DirtyRect {
            int  minX = 0, minY = 0, maxX = 0, maxY = 0;
            bool active = false;

            bool Empty() const noexcept { return !active; }
            void Reset() noexcept { active = false; }

            // Grow the box to include [x, x+w) × [y, y+h). No-op for an
            // empty rect (a glyph that rasterized to nothing marks nothing).
            void Add(int x, int y, int w, int h) noexcept {
                if (w <= 0 || h <= 0) return;
                if (!active) {
                    minX = x; minY = y; maxX = x + w; maxY = y + h;
                    active = true;
                } else {
                    minX = std::min(minX, x);
                    minY = std::min(minY, y);
                    maxX = std::max(maxX, x + w);
                    maxY = std::max(maxY, y + h);
                }
            }

            // Clamp the box to [0, limit] on both axes and emit it as an
            // (origin, extent) pair for a GPU copy. Returns false when
            // empty, leaving the outputs untouched.
            bool UploadBox(int limit, std::uint32_t& x, std::uint32_t& y, std::uint32_t& w, std::uint32_t& h) const noexcept {
                if (!active) return false;
                const int x0 = std::clamp(minX, 0, limit);
                const int y0 = std::clamp(minY, 0, limit);
                const int x1 = std::clamp(maxX, 0, limit);
                const int y1 = std::clamp(maxY, 0, limit);
                x = static_cast<std::uint32_t>(x0);
                y = static_cast<std::uint32_t>(y0);
                w = static_cast<std::uint32_t>(x1 - x0);
                h = static_cast<std::uint32_t>(y1 - y0);
                return true;
            }
        };

#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
        ImageVulkan<std::uint8_t> image;
#else
        WebGPUTextureRef textureHandle = 0;
        std::vector<std::uint8_t> staging;
#endif
        // `dirty` stays the cheap "is there anything to flush?" flag the
        // renderer polls each frame; `dirtyRect` carries the bounds Update
        // copies. The two are always set and cleared together (dirty ==
        // !dirtyRect.Empty()).
        bool dirty = false;
        DirtyRect dirtyRect;

        void Initialize(GraphicsCommandBuffer cmd);

        // Returns the row-major byte pointer the CPU writes pixels into.
        // Same shape on both backends.
        std::uint8_t* PixelPtr() noexcept;

        bool Ensure(Font& font, std::uint32_t codepoint);
        // Ensure the glyph is in the atlas and return a pointer to it in one
        // hash+probe. Returns nullptr only when a brand-new glyph cannot be
        // placed (ShelfPlace failure); a glyph with no SDF coverage (e.g.
        // space) still returns a valid pointer.
        const Glyph* EnsureAndGet(Font& font, std::uint32_t codepoint);
        const Glyph* Lookup(Font& font, std::uint32_t codepoint) const;
        void Update(GraphicsCommandBuffer cmd);

        // Flag the [x, x+w) × [y, y+h) atlas region as needing re-upload,
        // keeping `dirty` in lockstep with the accumulated `dirtyRect`.
        void MarkDirty(int x, int y, int w, int h) noexcept {
            dirtyRect.Add(x, y, w, h);
            dirty = !dirtyRect.Empty();
        }

    private:
        struct Shelf { int y = 0; int height = 0; int cursorX = 0; };
        std::vector<Shelf> shelves_;
        int  nextShelfY_ = 0;

        struct Key {
            const Font* font;
            std::uint32_t cp;
            bool operator==(const Key&) const = default;
        };
        struct KeyHash {
            std::size_t operator()(const Key& k) const noexcept {
                std::size_t h1 = std::hash<const void*>{}(k.font);
                std::size_t h2 = std::hash<std::uint32_t>{}(k.cp);
                return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2));
            }
        };
        std::unordered_map<Key, Glyph, KeyHash> cache_;

        bool ShelfPlace(int w, int h, int& outX, int& outY);
    };
}
