// MobileGL - MobileGL/MG_State/GLState/VertexArrayState/VertexArrayObject.h
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
//   https://www.gnu.org/licenses/gpl-3.0.txt
//   https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header

#pragma once
#include <Includes.h>
#include "../BufferState/BufferObject.h"
#include "MG_Util/Types.h"

namespace MobileGL {
    namespace MG_State {
        namespace GLState {
            struct VertexAttribute {
                Bool Enabled = false;
                int Size = 4;
                DataType Type = DataType::Float32;
                Bool Normalized = false;
                // The RESOLVED byte distance between consecutive elements, never the raw
                // glVertexAttrib*Pointer argument: a pointer call's stride 0 means "tightly
                // packed" and is resolved to the element size here, so a zero that survives
                // into this field can only have come from the binding model, where a zero
                // VERTEX_BINDING_STRIDE means the opposite - every vertex reads the SAME
                // element and the fetch address never advances (GL 4.6 core 10.3.1). Backends
                // consume this verbatim; collapsing 0 back into the element size is what made
                // KHR-GL43.vertex_attrib_binding.basic-input-case7/8 read past the buffer.
                int Stride = 0;
                SizeT Offset = 0;
                Bool IsInteger = false;
                // GL_BGRA vertex size: four components in reversed (B,G,R,A) memory order. Size stays 4.
                // Set only by the long (L) format entry points. It is NOT implied by
                // Type == Float64: VertexAttribFormat(GL_DOUBLE) also reads doubles from memory but
                // asks for them *converted to float*, while VertexAttribLFormat keeps all 64 bits
                // (GL 4.6 core 10.3.2). Backends have to tell the two apart, and it is what
                // GL_VERTEX_ATTRIB_ARRAY_LONG reports.
                Bool IsLong = false;
                Bool IsBgra = false;
                Uint Divisor = 0;
                SharedPtr<BufferObject> Buffer;

                // GL 4.6 core table 23.3: VERTEX_ATTRIB_ARRAY_STRIDE and _POINTER are the
                // arguments of the last glVertexAttrib*Pointer call on this attribute,
                // reported verbatim, and NOTHING else writes them - not glVertexAttribFormat,
                // not glBindVertexBuffer. Stride/Offset above are the *resolved* draw inputs
                // and the binding model does overwrite those, so the two views have to be
                // stored apart or the binding-model sequence reports a legacy state it never
                // set (KHR-GL4x.vertex_attrib_binding.basic-state3).
                int LegacyStride = 0;
                SizeT LegacyPointer = 0;
            };

            // ARB_vertex_attrib_binding separate binding point. Attributes configured through the
            // binding-point API are resolved eagerly into the flat VertexAttribute view above, so
            // backends keep consuming resolved attributes and never see binding points.
            struct VertexBufferBindingPoint {
                SharedPtr<BufferObject> Buffer;
                SizeT Offset = 0;
                // GL 4.6 core table 23.4: the initial VERTEX_BINDING_STRIDE is 16, not 0.
                int Stride = 16;
                Uint Divisor = 0;
            };

            struct VertexAttributeVersion {
                Uint16 FormatVersion = 0;
                Uint16 BufferVersion = 0;
                Uint16 SwitchVersion = 0;
            };

            class VertexArrayObject {
            public:
                // Storage capacity, not the GL-visible limit. GL_MAX_VERTEX_ATTRIBS is reported as
                // min(backend limit, MAX_VERTEX_ATTRIBS) and validated against that dynamic value;
                // 32 is the width of the Uint32 attribute masks the backends pass around, so it is
                // also the hard ceiling.
                static constexpr int MAX_VERTEX_ATTRIBS = 32;
                static constexpr int MAX_VERTEX_ATTRIB_BINDINGS = 32;

                VertexArrayObject(Uint externIndex);

                void EnableAttribute(Uint index);
                void DisableAttribute(Uint index);
                Bool IsAttributeEnabled(Uint index) const;

                // `stride` is the raw glVertexAttrib*Pointer argument, reported verbatim by
                // GL_VERTEX_ATTRIB_ARRAY_STRIDE. `effectiveStride` is what the fetch actually
                // advances by - the same value when the argument is non-zero, the tightly
                // packed element size when it is zero. Pass -1 to say the two are the same.
                void SetAttributeFormat(Uint index, int size, DataType type, Bool normalized, int stride, SizeT offset,
                                        Bool isInteger, Bool isBgra = false, int effectiveStride = -1);

                void BindAttributeBuffer(Uint index, const SharedPtr<BufferObject>& buffer);

                // Record what the pointer-style API implies for the binding-point view: attribute
                // `index` bound to binding point `index` with relative offset 0, and that binding
                // point carrying the buffer, the pointer offset and the effective stride.
                void MirrorPointerIntoBinding(Uint index, const SharedPtr<BufferObject>& buffer, SizeT offset,
                                              int effectiveStride);

                BindingSlot<BufferObject>& GetIndexBufferBindingSlot();
                const BindingSlot<BufferObject>& GetIndexBufferBindingSlot() const;

                const VertexAttribute& GetAttribute(Uint index) const;
                const Array<VertexAttribute, MAX_VERTEX_ATTRIBS>& GetAllAttributes() const;

                Uint GetExternalIndex() const;

                // Globally-unique, never-reused id for THIS object's lifetime - the same
                // contract as ProgramObject::GetLifetimeId(), and needed for the same
                // reason. Neither the GL name (freed to a LIFO list and handed straight
                // back by the next glGenVertexArrays) nor the heap address (freed to the
                // allocator and handed straight back by the next allocation of this size)
                // can tell a deleted-and-recreated VAO from the original, so a backend
                // memo keyed on either one silently inherits the dead object's contents.
                // That is not hypothetical: it is what let a transform-feedback capture
                // fetch a destroyed VAO's vertex buffer slice (see the VaoDrawMemo key in
                // DirectVulkan's VulkanRenderer).
                Uint64 GetLifetimeId() const { return m_lifetimeId; }

                void SetAttributeDivisor(Uint index, Uint divisor);
                Uint GetAttributeDivisor(Uint index) const;

                // ARB_vertex_attrib_binding style state. Each mutation re-resolves the affected
                // attributes into the flat VertexAttribute view.
                void SetBindingBuffer(Uint bindingIndex, const SharedPtr<BufferObject>& buffer, SizeT offset,
                                      int stride);
                void SetBindingDivisor(Uint bindingIndex, Uint divisor);
                void SetAttributeBinding(Uint attribIndex, Uint bindingIndex);
                void SetAttributeFormatSeparate(Uint attribIndex, int size, DataType type, Bool normalized,
                                                Bool isInteger, Uint relativeOffset, Bool isBgra = false,
                                                Bool isLong = false);

                // The binding-point view the attributes were resolved from. Kept queryable
                // because glGetVertexArrayIndexed[64]iv reports it verbatim, and the resolved
                // flat attribute cannot always be inverted back into it.
                Uint GetAttributeRelativeOffset(Uint attribIndex) const {
                    return attribIndex < m_attributeRelativeOffset.size() ? m_attributeRelativeOffset[attribIndex] : 0;
                }
                Uint GetAttributeBindingIndex(Uint attribIndex) const {
                    return attribIndex < m_attributeBindingIndex.size() ? m_attributeBindingIndex[attribIndex]
                                                                       : attribIndex;
                }
                const VertexBufferBindingPoint& GetBindingPoint(Uint bindingIndex) const {
                    static const VertexBufferBindingPoint kEmpty{};
                    return bindingIndex < m_bindingPoints.size() ? m_bindingPoints[bindingIndex] : kEmpty;
                }

                const VertexAttributeVersion& GetAttributeVersion(Uint index) const;
                const Array<VertexAttributeVersion, MAX_VERTEX_ATTRIBS>& GetAllAttributeVersions() const;

                // Aggregate of every per-attribute version bump; lets backends detect
                // "any vertex-input state changed" with one compare.
                Uint32 GetConfigVersion() const { return m_configVersion; }

                // Backend-owned content-hash memo, valid while the config version matches
                // (same idea as ProgramObject's hash memo — avoids re-hashing all
                // attributes on every draw).
                Bool GetBackendHashMemo(Uint64& outHash) const {
                    if (m_backendHashMemoVersion != m_configVersion) return false;
                    outHash = m_backendHashMemo;
                    return true;
                }
                void SetBackendHashMemo(Uint64 hash) const {
                    m_backendHashMemo = hash;
                    m_backendHashMemoVersion = m_configVersion;
                }

                // Backend-owned resolved-state memo: an opaque pointer into the
                // backend's vertex-input-state cache plus the cache's eviction
                // epoch, valid while the config version matches. Lets the
                // per-draw path skip the content hash AND the cache lookup; the
                // epoch guards against the cache evicting the pointee.
                Bool GetBackendStateMemo(const void*& outState, Uint64& outEpoch) const {
                    if (m_backendStateMemoVersion != m_configVersion) return false;
                    outState = m_backendStateMemo;
                    outEpoch = m_backendStateMemoEpoch;
                    return true;
                }
                void SetBackendStateMemo(const void* state, Uint64 epoch) const {
                    m_backendStateMemo = state;
                    m_backendStateMemoEpoch = epoch;
                    m_backendStateMemoVersion = m_configVersion;
                }

                // Backend-owned aux memo: two opaque VALUE words (no pointee, so unlike the
                // state memo above they need no eviction-epoch guard), valid while the config
                // version matches. They live next to m_configVersion, which every per-draw
                // path already loads, so a backend can re-read small derived facts about this
                // VAO's configuration (e.g. a layout hash and attribute masks) without
                // chasing into its own cache's heap entry - that chase is a guaranteed cache
                // miss when an app cycles hundreds of VAOs per frame.
                Bool GetBackendAuxMemo(Uint64& outAux0, Uint64& outAux1) const {
                    if (m_backendAuxMemoVersion != m_configVersion) return false;
                    outAux0 = m_backendAuxMemo0;
                    outAux1 = m_backendAuxMemo1;
                    return true;
                }
                void SetBackendAuxMemo(Uint64 aux0, Uint64 aux1) const {
                    m_backendAuxMemo0 = aux0;
                    m_backendAuxMemo1 = aux1;
                    m_backendAuxMemoVersion = m_configVersion;
                }

            private:
                void BumpAttributeFormatVersion(Uint index);
                void BumpAttributeBufferVersion(Uint index);
                void BumpAttributeSwitchVersion(Uint index);
                void ResolveAttributeFromBinding(Uint attribIndex);
                // Re-resolve every attribute currently pointed at `bindingIndex`. `adopt` turns
                // the ones that are not in the binding model yet into binding-model attributes
                // first (what glBindVertexBuffer does, GL 4.3 rules for state mixing).
                void ResolveAttributesForBinding(Uint bindingIndex, Bool adopt);

                // The default mapping is attribute i -> binding point i. Keep it an iota over
                // MAX_VERTEX_ATTRIBS rather than a literal list: a literal list silently leaves the
                // tail mapped to binding point 0 whenever the limit grows.
                static constexpr Array<Uint, MAX_VERTEX_ATTRIBS> MakeIdentityAttributeBindings() {
                    Array<Uint, MAX_VERTEX_ATTRIBS> mapping{};
                    for (Uint index = 0; index < static_cast<Uint>(MAX_VERTEX_ATTRIBS); ++index) {
                        mapping[index] = index;
                    }
                    return mapping;
                }

                static Uint64 AllocateLifetimeId();

                const Uint m_externalIndex = 0;
                const Uint64 m_lifetimeId = AllocateLifetimeId();
                Array<VertexAttribute, MAX_VERTEX_ATTRIBS> m_attributes;
                Array<VertexAttributeVersion, MAX_VERTEX_ATTRIBS> m_attributeVersions;
                BindingSlot<BufferObject> m_indexBufferBindingSlot;

                Array<VertexBufferBindingPoint, MAX_VERTEX_ATTRIB_BINDINGS> m_bindingPoints;
                Array<Uint, MAX_VERTEX_ATTRIBS> m_attributeBindingIndex = MakeIdentityAttributeBindings();
                Array<Uint, MAX_VERTEX_ATTRIBS> m_attributeRelativeOffset = {};
                // Set once an attribute (or its binding point) is touched through the
                // ARB_vertex_attrib_binding API; only such attributes are re-resolved, so the
                // classic glVertexAttribPointer path keeps its exact historical behavior.
                Array<Bool, MAX_VERTEX_ATTRIBS> m_attributeUsesBindingModel = {};

                Uint32 m_configVersion = 0;
                mutable Uint64 m_backendHashMemo = 0;
                mutable Uint32 m_backendHashMemoVersion = ~0u;
                mutable const void* m_backendStateMemo = nullptr;
                mutable Uint64 m_backendStateMemoEpoch = 0;
                mutable Uint32 m_backendStateMemoVersion = ~0u;
                mutable Uint64 m_backendAuxMemo0 = 0;
                mutable Uint64 m_backendAuxMemo1 = 0;
                mutable Uint32 m_backendAuxMemoVersion = ~0u;
            };
        } // namespace GLState
    } // namespace MG_State
} // namespace MobileGL
