#include "render.h"
#include <color.h>
#include <cstddef>
#include <cstring>
#include <mutex>

#include <psp2/display.h>
#include <psp2/gxm.h>
#include <psp2/kernel/sysmem.h>

#include "kernel/gpuMemory.hpp"
#include "kernel/renderTarget.hpp"
#include "kernel/shaderPatcher.hpp"
#include "kernel/surface.hpp"
#include "kernel/depthSurface.hpp"

#include "background.hpp"
#include "sprite.hpp"

#define DISPLAY_WIDTH 960
#define DISPLAY_HEIGHT 544
#define DISPLAY_STRIDE 1024
#define DISPLAY_COLOR_FORMAT SCE_GXM_COLOR_FORMAT_A8B8G8R8
#define DISPLAY_PIXEL_FORMAT SCE_DISPLAY_PIXELFORMAT_A8B8G8R8
#define ALIGN(x, a) (((x) + ((a)-1)) & ~((a)-1))

namespace render
{

struct renderCallbackData {
    void *addr;
};

struct bufferInfo {
    SceUID uid;
    void *data;
};

struct usseBufferInfo {
    SceUID uid;
    void *data;
    unsigned int offset;
};

struct RenderState {
    SceGxmContext *ctx;

    bufferInfo vdmRingBuffer;
    bufferInfo vertexRingBuffer;
    bufferInfo fragmentRingBuffer;
    usseBufferInfo fragmentUsseRingBuffer;

    kernel::RenderTarget *renderTarget;
    kernel::Surface *surfaces[2];

    kernel::DepthSurface *depthSurface;

    kernel::ShaderPatcher *patcher;

    int backBufferIndex = 0, frontBufferIndex = 0;

    Camera *currentCamera = nullptr;
};

RenderState renderState;

static void renderCallback(const void *data);

void Init()
{
    SceGxmInitializeParams initParams;
    memset(&initParams, 0, sizeof(SceGxmInitializeParams));
    initParams.flags = 0;
    initParams.displayQueueMaxPendingCount = 1;
    initParams.displayQueueCallback = renderCallback;
    initParams.displayQueueCallbackDataSize = sizeof(renderCallbackData);
    initParams.parameterBufferSize = SCE_GXM_DEFAULT_PARAMETER_BUFFER_SIZE;

    sceGxmInitialize(&initParams);

    renderState.vdmRingBuffer.data = kernel::gpuAllocator(
        SCE_KERNEL_MEMBLOCK_TYPE_USER_CDRAM_RW,
        SCE_GXM_MEMORY_ATTRIB_READ, SCE_GXM_DEFAULT_VDM_RING_BUFFER_SIZE,
        &renderState.vdmRingBuffer.uid);

    renderState.vertexRingBuffer.data = kernel::gpuAllocator(
        SCE_KERNEL_MEMBLOCK_TYPE_USER_CDRAM_RW,
        SCE_GXM_MEMORY_ATTRIB_READ, SCE_GXM_DEFAULT_VERTEX_RING_BUFFER_SIZE,
        &renderState.vertexRingBuffer.uid);

    renderState.fragmentRingBuffer.data = kernel::gpuAllocator(
        SCE_KERNEL_MEMBLOCK_TYPE_USER_CDRAM_RW,
        SCE_GXM_MEMORY_ATTRIB_READ, SCE_GXM_DEFAULT_FRAGMENT_RING_BUFFER_SIZE,
        &renderState.fragmentRingBuffer.uid);

    renderState.fragmentUsseRingBuffer.data = kernel::gpuFragmentUsseAllocator(
        SCE_GXM_DEFAULT_FRAGMENT_USSE_RING_BUFFER_SIZE,
        &renderState.fragmentUsseRingBuffer.uid,
        &renderState.fragmentUsseRingBuffer.offset);

    SceGxmContextParams ctxParams;
    memset(&ctxParams, 0, sizeof(SceGxmContextParams));
    ctxParams.hostMem = malloc(SCE_GXM_MINIMUM_CONTEXT_HOST_MEM_SIZE);
    ctxParams.hostMemSize = SCE_GXM_MINIMUM_CONTEXT_HOST_MEM_SIZE;
    ctxParams.vdmRingBufferMem = renderState.vdmRingBuffer.data;
    ctxParams.vdmRingBufferMemSize = SCE_GXM_DEFAULT_VDM_RING_BUFFER_SIZE;
    ctxParams.vertexRingBufferMem = renderState.vertexRingBuffer.data;
    ctxParams.vertexRingBufferMemSize = SCE_GXM_DEFAULT_VERTEX_RING_BUFFER_SIZE;
    ctxParams.fragmentRingBufferMem = renderState.fragmentRingBuffer.data;
    ctxParams.fragmentRingBufferMemSize = SCE_GXM_DEFAULT_FRAGMENT_RING_BUFFER_SIZE;
    ctxParams.fragmentUsseRingBufferMem = renderState.fragmentUsseRingBuffer.data;
    ctxParams.fragmentUsseRingBufferMemSize = SCE_GXM_DEFAULT_FRAGMENT_USSE_RING_BUFFER_SIZE;
    ctxParams.fragmentUsseRingBufferOffset = renderState.fragmentUsseRingBuffer.offset;

    sceGxmCreateContext(&ctxParams, &renderState.ctx);

    renderState.renderTarget = new kernel::RenderTarget(DISPLAY_WIDTH, DISPLAY_HEIGHT);

    for (int i = 0; i < 2; i++) {
        renderState.surfaces[i] = new kernel::Surface(DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_STRIDE, DISPLAY_COLOR_FORMAT);
    }

    renderState.depthSurface = new kernel::DepthSurface(DISPLAY_WIDTH, DISPLAY_HEIGHT);

    renderState.patcher = new kernel::ShaderPatcher();

    primitives::Init();
    background::Init();
    sprite::Init();

    puts("init");
}

void Free() {}

void StartFrame()
{

    kernel::Surface *currentSurface = renderState.surfaces[renderState.backBufferIndex];

    sceGxmBeginScene(renderState.ctx, 0, renderState.renderTarget->GetTarget(), nullptr, nullptr,
                     currentSurface->GetObject(), currentSurface->GetSurface(), renderState.depthSurface->GetSurface());
}
void EndFrame()
{
    kernel::Surface *currentSurface = renderState.surfaces[renderState.backBufferIndex];

    sceGxmEndScene(renderState.ctx, nullptr, nullptr);

    sceGxmPadHeartbeat(currentSurface->GetSurface(), currentSurface->GetObject());

    renderCallbackData cbData{
        .addr = currentSurface->GetData()};

    sceGxmDisplayQueueAddEntry(
        renderState.surfaces[renderState.frontBufferIndex]->GetObject(),
        currentSurface->GetObject(), &cbData);

    renderState.frontBufferIndex = renderState.backBufferIndex;

    renderState.backBufferIndex = (renderState.backBufferIndex + 1) % 2;
}

kernel::ShaderPatcher *GetShaderPatcher()
{
    return renderState.patcher;
}

SceGxmContext *GetContext()
{
    return renderState.ctx;
}

void BindCamera(Camera *camera)
{
    renderState.currentCamera = camera;
}

Camera *GetCurrentCamera()
{
    return renderState.currentCamera;
}

static void renderCallback(const void *data)
{
    SceDisplayFrameBuf frameBuffer;
    memset(&frameBuffer, 0, sizeof(SceDisplayFrameBuf));

    frameBuffer.size = sizeof(SceDisplayFrameBuf);
    frameBuffer.base = ((renderCallbackData *)data)->addr;
    frameBuffer.pitch = DISPLAY_STRIDE;
    frameBuffer.pixelformat = DISPLAY_PIXEL_FORMAT;
    frameBuffer.width = DISPLAY_WIDTH;
    frameBuffer.height = DISPLAY_HEIGHT;

    sceDisplaySetFrameBuf(&frameBuffer, SCE_DISPLAY_SETBUF_NEXTFRAME);

    sceDisplayWaitVblankStart();
}

} // namespace render
