#include "GeometryWriterNode.hpp"

#include "MayaFlux/Journal/Archivist.hpp"

namespace MayaFlux::Nodes::GpuSync {

GeometryWriterNode::GeometryWriterNode(uint32_t initial_capacity)
{
    m_node_capability = NodeCapability::STRUCTURED;

    if (initial_capacity > 0 && m_vertex_stride == 0) {
        m_vertex_stride = sizeof(glm::vec3);
    }

    if (initial_capacity > 0) {
        m_vertex_buffer.resize(initial_capacity * m_vertex_stride / sizeof(float));
    }
}

void GeometryWriterNode::resize_vertex_buffer(uint32_t vertex_count, bool preserve_data)
{
    if (m_vertex_stride == 0) {
        MF_ERROR(Journal::Component::Nodes, Journal::Context::NodeProcessing,
            "Cannot resize vertex buffer: stride is 0. Call set_vertex_stride() first");
        return;
    }

    size_t new_size_bytes = static_cast<size_t>(vertex_count) * m_vertex_stride;

    if (!preserve_data) {
        m_vertex_buffer.clear();
        m_vertex_buffer.resize(new_size_bytes, 0.0F);
    } else if (m_vertex_buffer.size() < new_size_bytes) {
        m_vertex_buffer.resize(new_size_bytes, 0.0F);
    } else if (m_vertex_buffer.size() > new_size_bytes) {
        m_vertex_buffer.resize(new_size_bytes);
    }

    m_vertex_count = vertex_count;
    m_needs_layout_update = true;
    m_vertex_data_dirty = true;

    MF_DEBUG(Journal::Component::Nodes, Journal::Context::NodeProcessing,
        "GeometryWriterNode: Resized vertex buffer to {} vertices ({} bytes total)",
        vertex_count, new_size_bytes * sizeof(float));
}

void GeometryWriterNode::set_vertex_data(const void* data, size_t size_bytes)
{
    if (!data || size_bytes == 0) {
        MF_ERROR(Journal::Component::Nodes, Journal::Context::NodeProcessing,
            "Cannot set vertex data: null data or zero size");
        return;
    }

    if (m_vertex_stride == 0) {
        MF_ERROR(Journal::Component::Nodes, Journal::Context::NodeProcessing,
            "Cannot set vertex data: stride is 0");
        return;
    }

    auto expected_vertex_count = static_cast<uint32_t>(size_bytes / m_vertex_stride);

    if (size_bytes % m_vertex_stride != 0) {
        MF_WARN(Journal::Component::Nodes, Journal::Context::NodeProcessing,
            "Vertex data size {} is not multiple of stride {}",
            size_bytes, m_vertex_stride);
    }

    size_t required_floats = size_bytes / sizeof(float);
    if (m_vertex_buffer.size() < required_floats) {
        m_vertex_buffer.resize(required_floats);
    }

    std::memcpy(m_vertex_buffer.data(), data, size_bytes);
    m_vertex_count = expected_vertex_count;
    m_needs_layout_update = true;
    m_vertex_data_dirty = true;

    MF_DEBUG(Journal::Component::Nodes, Journal::Context::NodeProcessing,
        "GeometryWriterNode: Set vertex data ({} vertices, {} bytes)",
        m_vertex_count, size_bytes);
}

void GeometryWriterNode::set_vertex(uint32_t vertex_index, const void* data, size_t size_bytes)
{
    if (!data || size_bytes == 0) {
        MF_ERROR(Journal::Component::Nodes, Journal::Context::NodeProcessing,
            "Cannot set vertex: null data or zero size");
        return;
    }

    if (m_vertex_stride == 0) {
        MF_ERROR(Journal::Component::Nodes, Journal::Context::NodeProcessing,
            "Cannot set vertex: stride is 0");
        return;
    }

    if (vertex_index >= m_vertex_count) {
        MF_ERROR(Journal::Component::Nodes, Journal::Context::NodeProcessing,
            "Vertex index {} out of range (count: {})",
            vertex_index, m_vertex_count);
        return;
    }

    if (size_bytes > m_vertex_stride) {
        MF_WARN(Journal::Component::Nodes, Journal::Context::NodeProcessing,
            "Vertex data size {} exceeds stride {}; truncating",
            size_bytes, m_vertex_stride);
        size_bytes = m_vertex_stride;
    }

    size_t offset_floats = (static_cast<size_t>(vertex_index) * m_vertex_stride) / sizeof(float);

    std::memcpy(
        reinterpret_cast<uint8_t*>(m_vertex_buffer.data()) + (offset_floats * sizeof(float)),
        data,
        size_bytes);

    MF_TRACE(Journal::Component::Nodes, Journal::Context::NodeProcessing,
        "GeometryWriterNode: Set vertex {} ({} bytes)", vertex_index, size_bytes);

    m_vertex_data_dirty = true;
}

void GeometryWriterNode::set_indices(std::span<const uint32_t> indices)
{
    m_index_buffer.assign(indices.begin(), indices.end());
    m_vertex_data_dirty = true;

    MF_DEBUG(Journal::Component::Nodes, Journal::Context::NodeProcessing,
        "GeometryWriterNode: Set index buffer ({} indices, {} bytes)",
        m_index_buffer.size(), m_index_buffer.size() * sizeof(uint32_t));
}

size_t GeometryWriterNode::get_vertex_buffer_size_bytes() const
{
    return m_vertex_buffer.size();
}

void GeometryWriterNode::set_vertex_stride(size_t stride)
{
    m_vertex_stride = stride;
    m_needs_layout_update = true;
}

std::span<const uint8_t> GeometryWriterNode::get_vertex(uint32_t vertex_index) const
{
    if (m_vertex_stride == 0 || m_vertex_count == 0) {
        return {};
    }

    if (vertex_index >= m_vertex_count) {
        MF_WARN(Journal::Component::Nodes, Journal::Context::NodeProcessing,
            "Vertex index {} out of range (count: {})",
            vertex_index, m_vertex_count);
        return {};
    }

    size_t offset_floats = (static_cast<size_t>(vertex_index) * m_vertex_stride) / sizeof(float);
    size_t stride_floats = m_vertex_stride / sizeof(float);

    if (offset_floats + stride_floats > m_vertex_buffer.size()) {
        return {};
    }

    return { m_vertex_buffer.data() + offset_floats, stride_floats };
}

std::span<const uint32_t> GeometryWriterNode::get_index_data() const
{
    return std::span<const uint32_t> { m_index_buffer.data(), m_index_buffer.size() };
}

void GeometryWriterNode::clear()
{
    std::ranges::fill(m_vertex_buffer, 0);

    if (!m_index_buffer.empty()) {
        std::ranges::fill(m_index_buffer, 0);
    }

    m_vertex_data_dirty = true;
}

void GeometryWriterNode::clear_and_resize(uint32_t vertex_count)
{
    resize_vertex_buffer(vertex_count, false);
}

std::vector<double> GeometryWriterNode::process_batch(unsigned int num_samples)
{
    compute_frame();

    return std::vector<double>(num_samples, 0.0);
}

void GeometryWriterNode::save_state()
{
    GeometryState state;
    state.vertex_buffer = m_vertex_buffer;
    state.vertex_count = m_vertex_count;
    state.vertex_stride = m_vertex_stride;
    state.vertex_layout = m_vertex_layout;
    state.index_buffer = m_index_buffer;

    m_saved_state = std::move(state);

    MF_DEBUG(Journal::Component::Nodes, Journal::Context::NodeProcessing,
        "GeometryWriterNode: Saved state ({} vertices, {} bytes)",
        m_vertex_count, m_vertex_buffer.size() * sizeof(uint8_t));
}

void GeometryWriterNode::restore_state()
{
    if (!m_saved_state.has_value()) {
        MF_WARN(Journal::Component::Nodes, Journal::Context::NodeProcessing,
            "GeometryWriterNode: No saved state to restore");
        return;
    }

    m_vertex_buffer = m_saved_state->vertex_buffer;
    m_vertex_count = m_saved_state->vertex_count;
    m_vertex_stride = m_saved_state->vertex_stride;
    m_vertex_layout = m_saved_state->vertex_layout;
    m_index_buffer = m_saved_state->index_buffer;

    m_needs_layout_update = true;

    MF_DEBUG(Journal::Component::Nodes, Journal::Context::NodeProcessing,
        "GeometryWriterNode: Restored state ({} vertices, {} bytes)",
        m_vertex_count, m_vertex_buffer.size() * sizeof(uint8_t));
}

void GeometryWriterNode::update_context(double value)
{
    m_context.value = value;
    m_context.vertex_count = m_vertex_count;
    m_context.vertex_stride = m_vertex_stride;
    m_context.m_gpu_data = std::span<const uint8_t>(m_vertex_buffer.data(), m_vertex_buffer.size());
    m_context.m_element_size = m_vertex_stride;
    m_context.m_element_count = m_vertex_count;
}

NodeContext& GeometryWriterNode::get_last_context()
{
    return m_context;
}

#ifdef MAYAFLUX_PLATFORM_MACOS
std::vector<LineVertex> GeometryWriterNode::expand_lines_to_triangles(
    const std::vector<LineVertex>& line_segment)
{
    if (line_segment.size() < 2) {
        return {};
    }

    std::vector<LineVertex> triangles;
    triangles.reserve((line_segment.size() - 1) * 6);

    for (size_t i = 0; i + 1 < line_segment.size(); ++i) {
        const auto& v0 = line_segment[i];
        const auto& v1 = line_segment[i + 1];

        glm::vec2 p0_2d = glm::vec2(v0.position.x, v0.position.y);
        glm::vec2 p1_2d = glm::vec2(v1.position.x, v1.position.y);

        glm::vec2 dir = p1_2d - p0_2d;
        float len = glm::length(dir);

        if (len < 1e-6F) {
            continue;
        }

        dir = dir / len;
        glm::vec2 normal = glm::vec2(-dir.y, dir.x);

        float t0 = v0.thickness * 0.005F;
        float t1 = v1.thickness * 0.005F;

        glm::vec3 p0_top = glm::vec3(p0_2d + normal * t0, v0.position.z);
        glm::vec3 p0_bot = glm::vec3(p0_2d - normal * t0, v0.position.z);
        glm::vec3 p1_top = glm::vec3(p1_2d + normal * t1, v1.position.z);
        glm::vec3 p1_bot = glm::vec3(p1_2d - normal * t1, v1.position.z);

        triangles.push_back({ p0_top, v0.color, v0.thickness,
            glm::vec2(0.0F, 0.0F) });
        triangles.push_back({ p0_bot, v0.color, v0.thickness,
            glm::vec2(0.0F, 1.0F) });
        triangles.push_back({ p1_top, v1.color, v1.thickness,
            glm::vec2(1.0F, 0.0F) });

        triangles.push_back({ p0_bot, v0.color, v0.thickness,
            glm::vec2(0.0F, 1.0F) });
        triangles.push_back({ p1_bot, v1.color, v1.thickness,
            glm::vec2(1.0F, 1.0F) });
        triangles.push_back({ p1_top, v1.color, v1.thickness,
            glm::vec2(1.0F, 0.0F) });
    }

    return triangles;
}
#endif

} // namespace MayaFlux::Nodes
