#include <SDL3/SDL.h>
#include <SDL3_image/SDL_image.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <rapidjson/document.h>
#include <rapidjson/istreamwrapper.h>
#include "Tileset.hpp"
#include "Tilemap.hpp"
#include "Context.hpp"

#define WIDTH 800
#define HEIGHT 600
    

int main(int argc, char* argv[])
{
    SDL_Init(SDL_INIT_VIDEO);
    
    BasePath = SDL_GetBasePath();

    // Create SDL Window
    SDL_Window* window = SDL_CreateWindow("SDL3 Tilemap Game", WIDTH, HEIGHT, SDL_WINDOW_VULKAN  | SDL_WINDOW_RESIZABLE);
    if (!window) {
        std::cerr << "Failed to create window: " << SDL_GetError() << std::endl;
        return -1;
    }

    // Create Renderer
    SDL_Renderer* renderer = SDL_CreateRenderer(window, NULL);
    if (!renderer) {
        std::cerr << "Failed to create renderer: " << SDL_GetError() << std::endl;
        SDL_DestroyWindow(window);
        return -1;
    }
    
    SDL_GPUDevice* device = SDL_CreateGPUDevice(
                                                SDL_GPU_SHADERFORMAT_SPIRV | SDL_GPU_SHADERFORMAT_DXIL | SDL_GPU_SHADERFORMAT_MSL,
                                                false,
                                                NULL);

    if (!device)
    {
        printf("SDL_CreateGPUDevice failed: %s\n", SDL_GetError());
        SDL_DestroyWindow(window);
        SDL_Quit();
        return -1;
    }
    
    if (!SDL_ClaimWindowForGPUDevice(device, window))
    {
        SDL_Log("GPUClaimWindow failed");
        return -1;
    }
    
    SDL_GPUPresentMode presentMode = SDL_GPU_PRESENTMODE_VSYNC;
    if (SDL_WindowSupportsGPUPresentMode(
        device,
        window,
        SDL_GPU_PRESENTMODE_IMMEDIATE
    )) {
        presentMode = SDL_GPU_PRESENTMODE_IMMEDIATE;
    }
    else if (SDL_WindowSupportsGPUPresentMode(
        device,
        window,
        SDL_GPU_PRESENTMODE_MAILBOX
    )) {
        presentMode = SDL_GPU_PRESENTMODE_MAILBOX;
    }

    SDL_SetGPUSwapchainParameters(
        device,
        window,
        SDL_GPU_SWAPCHAINCOMPOSITION_SDR,
        presentMode
    );

    // Create the shaders
    SDL_GPUShader* vertShader = LoadShader(
        device,
        "../assets/PullSpriteBatch.vert",
        0,
        1,
        1,
        0
    );

    SDL_GPUShader* fragShader = LoadShader(
        device,
        "../assets/TexturedQuadColor.frag",
        1,
        0,
        0,
        0
    );
    
    SDL_GPUColorTargetDescription target_desc[] {{
        .format = SDL_GetGPUSwapchainTextureFormat(device, window),
        .blend_state = {
            .enable_blend = true,
            .color_blend_op = SDL_GPU_BLENDOP_ADD,
            .alpha_blend_op = SDL_GPU_BLENDOP_ADD,
            .src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA,
            .dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
            .src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA,
            .dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
        }
    }};
        
    SDL_GPUGraphicsPipelineTargetInfo pipeline_target_info{
        .num_color_targets = 1,
        .color_target_descriptions = target_desc
    };
    
    SDL_GPUGraphicsPipelineCreateInfo pipeline_info{
        .target_info = pipeline_target_info,
        .primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST,
        .vertex_shader = vertShader,
        .fragment_shader = fragShader
    };

    // Create the sprite render pipeline
    RenderPipeline = SDL_CreateGPUGraphicsPipeline(
        device,
        &pipeline_info
    );

    SDL_ReleaseGPUShader(device, vertShader);
    SDL_ReleaseGPUShader(device, fragShader);
    
    Tileset tileset;
    tileset.parseTileset("../assets/alienor.json");

    TileMap tilemap;
    tilemap.parseTilemap("../assets/map1_test1.json");

    // Load the image data
    SDL_Surface *imageData = LoadImage("../assets/alienor.png", 4);
    if (imageData == NULL)
    {
        SDL_Log("Could not load image data!");
        return -1;
    }
    
    SDL_GPUTransferBufferCreateInfo buffer_info {
        .usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
        .size = static_cast<Uint32>(imageData->w * imageData->h * 4)
    };

    SDL_GPUTransferBuffer* textureTransferBuffer = SDL_CreateGPUTransferBuffer(
        device,
        &buffer_info
    );

    Uint8 *textureTransferPtr = (Uint8*) SDL_MapGPUTransferBuffer(
        device,
        textureTransferBuffer,
        false
    );
    SDL_memcpy(textureTransferPtr, imageData->pixels, imageData->w * imageData->h * 4);
    SDL_UnmapGPUTransferBuffer(device, textureTransferBuffer);

    SDL_GPUTextureCreateInfo texture_info{
        .type = SDL_GPU_TEXTURETYPE_2D,
        .format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM,
        .width = static_cast<Uint32>(imageData->w),
        .height = static_cast<Uint32>(imageData->h),
        .layer_count_or_depth = 1,
        .num_levels = 1,
        .usage = SDL_GPU_TEXTUREUSAGE_SAMPLER
    };
    
    // Create the GPU resources
    Texture = SDL_CreateGPUTexture(
        device,
        &texture_info
    );
    
    SDL_GPUSamplerCreateInfo sampler_info {
        .min_filter = SDL_GPU_FILTER_NEAREST,
        .mag_filter = SDL_GPU_FILTER_NEAREST,
        .mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST,
        .address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE,
        .address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE,
        .address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE
    };
    
    Sampler = SDL_CreateGPUSampler(
        device,
        &sampler_info
    );
    
    SDL_GPUTransferBufferCreateInfo transfer_buffer_info {
        .usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
        .size = SPRITE_COUNT * sizeof(SpriteInstance)
    };
    
    SpriteDataTransferBuffer = SDL_CreateGPUTransferBuffer(
        device,
        &transfer_buffer_info
    );

    SDL_GPUBufferCreateInfo buffer_info_read {
        .usage = SDL_GPU_BUFFERUSAGE_GRAPHICS_STORAGE_READ,
        .size = SPRITE_COUNT * sizeof(SpriteInstance)
    };
    
    SpriteDataBuffer = SDL_CreateGPUBuffer(
        device,
        &buffer_info_read
    );

    // Transfer the up-front data
    SDL_GPUCommandBuffer* uploadCmdBuf = SDL_AcquireGPUCommandBuffer(device);
    SDL_GPUCopyPass* copyPass = SDL_BeginGPUCopyPass(uploadCmdBuf);

    SDL_GPUTextureTransferInfo transfer_info {
        .transfer_buffer = textureTransferBuffer,
        .offset = 0, /* Zeroes out the rest */
    };

    SDL_GPUTextureRegion texture_region {
        .texture = Texture,
        .w = static_cast<Uint32>(imageData->w),
        .h = static_cast<Uint32>(imageData->h),
        .d = 1
    };
    
    SDL_UploadToGPUTexture(
        copyPass,
        &transfer_info,
        &texture_region,
        false
    );
    
    SDL_GPUTextureSamplerBinding sampler_binding {
        .texture = Texture,
        .sampler = Sampler
    };

    SDL_EndGPUCopyPass(copyPass);
    SDL_SubmitGPUCommandBuffer(uploadCmdBuf);

    SDL_DestroySurface(imageData);
    SDL_ReleaseGPUTransferBuffer(device, textureTransferBuffer);

    bool running = true;
    SDL_Event event;
    
    static float uCoords[4] = { 0.0f, 0.5f, 0.0f, 0.5f };
    static float vCoords[4] = { 0.0f, 0.0f, 0.5f, 0.5f };
    
    float factorX = 1;
    float factorY = 1;

    while (running)
    {
        // Handle events
        while (SDL_PollEvent(&event))
        {
            if (event.type == SDL_EVENT_QUIT)
            {
                running = false;
            }
            if(event.type == SDL_EVENT_KEY_DOWN) {
                if(event.key.key == SDLK_A) {
                    factorX=factorX-1.f;
                }
                if(event.key.key == SDLK_Z) {
                    factorX=factorX+1.f;
                }
                if(event.key.key == SDLK_Q) {
                    factorY=factorY-1.f;
                }
                if(event.key.key == SDLK_S) {
                    factorY=factorY+1.f;
                }
            }
        }

        render(device, tilemap, tileset, window);
    }

    // Cleanup
    SDL_ReleaseGPUGraphicsPipeline(device, RenderPipeline);
    SDL_ReleaseGPUSampler(device, Sampler);
    SDL_ReleaseGPUTexture(device, Texture);
    SDL_ReleaseGPUTransferBuffer(device, SpriteDataTransferBuffer);
    SDL_ReleaseGPUBuffer(device, SpriteDataBuffer);
    
    return 0;
}

