#pragma once

/*
	VulkanFramework
	by John Ryland
	Copyright (c) 2023
*/

////////////////////////////////////////////////////////////////////////////////////
//	Vulkan Attribute Binding

#include "VulkanDevice.h"

namespace Vulkan {

// decorate the value type with a format description
template <typename T, enum VkFormat fmt>
struct Attribute
{
    T                     value;
    static const VkFormat format = fmt;
};

// extract the format and member offsets of the struct in to VkVertexInputAttributeDescriptions
template <typename ObjT>
struct AttributeVisitor
{
    template <typename T>
    void Visit(T& member)
    {
        VkVertexInputAttributeDescription attribDesc {};
        attribDesc.binding  = 0;
        attribDesc.location = ret.size();
        attribDesc.format   = T::format;
        attribDesc.offset   = size_t(&member) - size_t(&obj);
        ret.push_back(attribDesc);
    }
    ObjT obj;
    std::vector<VkVertexInputAttributeDescription> ret;
};

template <typename ObjT>
std::vector<VkVertexInputAttributeDescription> GetAttributes()
{
    AttributeVisitor<ObjT> visitor;
    visitor.obj.Visit(visitor);
    return visitor.ret;
}

template <typename ObjT>
std::vector<VkVertexInputBindingDescription> GetBindings()
{
    std::vector<VkVertexInputBindingDescription> ret;
    VkVertexInputBindingDescription bindingDescription {};
    bindingDescription.binding   = 0;
    bindingDescription.stride    = sizeof(ObjT);
    bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
    ret.push_back(bindingDescription);
    return ret;
}

template <typename ObjT>
struct EqualityVisitor
{
    template <typename T>
    void Visit(T& member)
    {
        T* otherMember = (T*)(size_t(&other) + size_t(&member) - size_t(&self));
        ret = ret && (member.value == otherMember->value);
    }
    const ObjT& self;
    const ObjT& other;
    bool ret = true;
};

template <typename ObjT>
struct IsEqual
{
    bool operator()(const ObjT& self, const ObjT& other) const
    {
        EqualityVisitor<ObjT> visitor{ self, other };
        self.Visit(visitor);
        return visitor.ret;
    }
};

template <typename ObjT>
struct HashVisitor
{
    template <typename T>
    void Visit(T& member)
    {
        ret = ret ^ std::hash<decltype(T::value)>()(member.value);
    }
    size_t ret = 0;
};

template <typename ObjT>
struct Hash
{
    size_t operator()(const ObjT& obj) const
    {
        HashVisitor<ObjT> visitor;
        obj.Visit(visitor);
        return visitor.ret;
    }
};

} // Vulkan namespace
