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

#include <psp2/gxm.h>

#include "kernel/fragmentShader.hpp"
#include "kernel/texture.hpp"
#include "kernel/vertexShader.hpp"
#include "render.h"

#include "../netlog.h"
#include "atlas.hpp"
#include <vectors/vec2.h>

namespace render::sprite
{

const size_t bufferSize = 0xFFF;

struct Vertex {
    vec2f position;
    vec2f uv;
};

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

    kernel::GpuBuffer *vertexBuffer, *indexBuffer;

    const SceGxmProgramParameter *textureParam, *matrixParam;

    const kernel::Texture *currentTexture = nullptr;

    uint32_t vCount, iCount;
} state;

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

    state.vshader = new kernel::VertexShader("app0:/data/shaders/sprite_v.gxp", patcher);
    state.fshader = new kernel::FragmentShader("app0:/data/shaders/sprite_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, uv);
    attrib.componentCount = 2;
    attrib.regIndex = state.vshader->GetParamIndex("texCoord");
    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.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));

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

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

    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());
    
    float texture = 0;
    
    state.fshader->SetDefaultUniform(ctx, state.textureParam, 1, &texture);

    if (state.currentTexture) {
        state.currentTexture->BindFragment(0);
    }

    int error = sceGxmSetVertexStream(ctx, 0, state.vertexBuffer->data);
    error = sceGxmDraw(ctx, SCE_GXM_PRIMITIVE_TRIANGLES, SCE_GXM_INDEX_FORMAT_U16, state.indexBuffer->data, state.iCount);

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

void TextureAtlas::draw(const vec2f &pos, Icon &p) const noexcept
{
    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};

    if (p._parent != this) {
        return;
    }

    if (p.angle != p._angle || p.center != p._center || p.size != p._size) {
        this->mathRotate(p);
    }

    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+p._rotMatrix[i]*p.scale;
        data[state.vCount + i].uv = (p._pos + offset[i] * p._isize) / _size;
    }

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

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

}

void TextureAtlas::draw(const vec2f &pos, const Icon &p) const noexcept
{
    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};

    if (p._parent != this) {
        return;
    }

    float msin, mcos;
    sincosf(p.angle, &msin, &mcos);

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

    for (int i = 0; i < 4; i++) {
        vec2f voffset = (offset[i] - p.center) * p.size * p.scale;

        data[state.vCount + i].position.x = pos.x + voffset.x * mcos - voffset.y * msin;
        data[state.vCount + i].position.y = pos.y + voffset.x * msin + voffset.y * mcos;
        data[state.vCount + i].uv = (p._pos + offset[i] * p._isize) / _size;
    }

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

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

} // namespace render::sprite