#include "primitives.hpp"
#include <cmath>
#include <memory.h>

#include <psp2/gxm.h>

#include "kernel/fragmentShader.hpp"
#include "kernel/vertexShader.hpp"

#include "../netlog.h"

#define M_PI 3.1415

namespace render::primitives
{

struct Vertex {
    vec2f position;
    vec4f color;
};

const size_t bufferSize = 0xFFF;

struct PrimState {
    kernel::VertexShader *vshader;
    kernel::FragmentShader *fshader;

    kernel::GpuBuffer *vertexBuffer, *indexBuffer;

    const SceGxmProgramParameter *matrixParam;

    uint32_t vCount, iCount;
} state;

void Init()
{
    kernel::ShaderPatcher *patcher = render::GetShaderPatcher();

    state.vshader = new kernel::VertexShader("app0:/data/shaders/primitive_v.gxp", patcher);
    state.fshader = new kernel::FragmentShader("app0:/data/shaders/primitive_f.gxp", patcher);

    SceGxmVertexAttribute attrib;
    attrib.streamIndex = 0;
    attrib.offset = offsetof(Vertex, position);
    attrib.format = SCE_GXM_ATTRIBUTE_FORMAT_F32;
    attrib.componentCount = 2;
    attrib.regIndex = state.vshader->GetParamIndex("position");
    state.vshader->attribs.push_back(attrib);

    attrib.offset = offsetof(Vertex, color);
    attrib.componentCount = 4;
    attrib.regIndex = state.vshader->GetParamIndex("color");
    state.vshader->attribs.push_back(attrib);

    state.vshader->stream.stride = sizeof(Vertex);
    state.vshader->stream.indexSource = SCE_GXM_INDEX_SOURCE_INDEX_16BIT;

    state.vshader->Assembly();

    state.matrixParam = state.vshader->GetParam("projView");

    state.vertexBuffer = new kernel::GpuBuffer(SCE_KERNEL_MEMBLOCK_TYPE_USER_RW_UNCACHE, SCE_GXM_MEMORY_ATTRIB_READ, bufferSize * sizeof(Vertex));
    state.indexBuffer = new kernel::GpuBuffer(SCE_KERNEL_MEMBLOCK_TYPE_USER_RW_UNCACHE, SCE_GXM_MEMORY_ATTRIB_READ, bufferSize * sizeof(uint16_t));

    state.vCount = 0;
    state.iCount = 0;
}

void Free()
{
}

void Render()
{
    SceGxmContext *ctx = render::GetContext();

    render::Camera *cam = render::GetCurrentCamera();

    state.vshader->Bind(ctx);
    state.fshader->Bind(ctx);

    state.vshader->SetDefaultUniform(ctx, state.matrixParam, 16, cam->GetProjView());

    int error = sceGxmSetVertexStream(ctx, 0, state.vertexBuffer->data);
    // netlog("stream %x", error);
    error = sceGxmDraw(ctx, SCE_GXM_PRIMITIVE_TRIANGLES, SCE_GXM_INDEX_FORMAT_U16, state.indexBuffer->data, state.iCount);

    // netlog("draw %x", error);

    state.vCount = 0;
    state.iCount = 0;
}

void Quad(const vec2f &pos, const vec2f &size, const color::rgba &color)
{
    constexpr vec2f offset[4] = {
        vec2f{0, 0}, vec2f{1, 0},
        vec2f{0, 1}, vec2f{1, 1}};

    constexpr uint16_t indecies[6] = {0, 1, 2, 2, 1, 3};

    Vertex *data = (Vertex *)state.vertexBuffer->data;
    uint16_t *index = (uint16_t *)state.indexBuffer->data;

    for (int i = 0; i < 4; i++) {
        data[state.vCount + i].position = pos + size * offset[i];
        data[state.vCount + i].color = color;
    }

    for (int i = 0; i < 6; i++) {
        index[state.iCount + i] = state.vCount + indecies[i];
    }

    state.vCount += 4;
    state.iCount += 6;
}

void Circle(const vec2f &center, const vec2f &length, const color::rgba &color, float angle, uint32_t edgeCount)
{
    if (!edgeCount) {
        edgeCount = ceil((length.x + length.y) * M_PI);
    }

    Vertex *data = (Vertex *)state.vertexBuffer->data;
    uint16_t *index = (uint16_t *)state.indexBuffer->data;

    uint16_t centerIndex = state.vCount;

    data[state.vCount].position = center;
    data[state.vCount].color = color;

    state.vCount++;

    for (int i = 0; i <= edgeCount; i++) {
        vec2f position;
        float currentAngle;

        if (i == edgeCount) {
            currentAngle = angle * DTOR;
        } else {
            currentAngle = ((float)i * 360.0f / (float)edgeCount + angle) * DTOR;
        }

        sincosf(currentAngle, &position.y, &position.x);
        position *= length;
        position += center;

        data[state.vCount + i].position = position;
        data[state.vCount + i].color = color;
    }

    for (int i = 0; i < edgeCount; i++) {
        index[state.iCount + i * 3] = centerIndex;
        index[state.iCount + i * 3 + 1] = state.vCount + i;
        index[state.iCount + i * 3 + 2] = state.vCount + i + 1;
    }

    state.vCount += edgeCount + 1;
    state.iCount += edgeCount * 3;
}

} // namespace render::primitives