// SPDX-License-Identifier: MIT
//
// Copyright (c) 2008-2022 Antonio Niño Díaz
//
// This file is part of Nitro Engine

#include "NEMain.h"

/// @file NEFAT.c

char *NE_FATLoadData(const char *filename)
{
    FILE *f = fopen(filename, "rb");
    if (f == NULL)
    {
        NE_DebugPrint("%s could't be opened", filename);
        return NULL;
    }

    if (fseek(f, 0, SEEK_END) != 0)
    {
        NE_DebugPrint("Failed to fseek: %s", filename);
        fclose(f);
        return NULL;
    }

    size_t size = ftell(f);
    rewind(f);

    char *buffer = malloc(size);
    if (buffer == NULL)
    {
        NE_DebugPrint("Not enought memory to load %s", filename);
        fclose(f);
        return NULL;
    }

    if (fread(buffer, 1, size, f) != size)
    {
        NE_DebugPrint("Failed to read data of %s", filename);
        fclose(f);
        return NULL;
    }

    fclose(f);
    return buffer;
}

size_t NE_FATFileSize(const char *filename)
{
    FILE *f = fopen(filename, "rb");
    if (f == NULL)
    {
        NE_DebugPrint("%s could't be opened", filename);
        return -1;
    }

    if (fseek(f, 0, SEEK_END) != 0)
    {
        NE_DebugPrint("Failed to fseek: %s", filename);
        fclose(f);
        return -1;
    }

    size_t size = ftell(f);
    fclose(f);
    return size;
}

static void NE_write16(u16 *address, u16 value)
{
    u8 *first = (u8 *)address;
    u8 *second = first + 1;

    *first = value & 0xff;
    *second = value >> 8;
}

static void NE_write32(u32 *address, u32 value)
{
    u8 *first = (u8 *) address;
    u8 *second = first + 1;
    u8 *third = first + 2;
    u8 *fourth = first + 3;

    *first = value & 0xff;
    *second = (value >> 8) & 0xff;
    *third = (value >> 16) & 0xff;
    *fourth = (value >> 24) & 0xff;
}

int NE_ScreenshotBMP(const char *filename)
{
    FILE *f = fopen(filename, "wb");

    if (f == NULL)
    {
        NE_DebugPrint("%s could't be opened", filename);
        return 0;
    }

    NE_SpecialEffectPause(true);

    // In normal 3D mode we need to capture the composited (3D+2D output)
    // and save it to VRAM. In dual 3D mode it already is in VRAM.
    if (NE_CurrentExecutionMode() == NE_ModeSingle3D)
    {
        // TODO: VRAM_D needs to be saved somewhere and then restored!

        vramSetBankD(VRAM_D_LCD);

        REG_DISPCAPCNT = DCAP_BANK(DCAP_BANK_VRAM_D)
                       | DCAP_SIZE(DCAP_SIZE_256x192)
                       | DCAP_MODE(DCAP_MODE_A)
                       | DCAP_SRC_A(DCAP_SRC_A_COMPOSITED)
                       | DCAP_ENABLE;

        while (REG_DISPCAPCNT & DCAP_ENABLE);
    }

    int ysize = 0;

    if (NE_CurrentExecutionMode() == NE_ModeSingle3D)
        ysize = 192;
    else
        ysize = 384;

    u8 *temp = malloc(256 * ysize * 3
                      + sizeof(NE_BMPInfoHeader)
                      + sizeof(NE_BMPHeader));

    NE_BMPHeader *header = (NE_BMPHeader *) temp;
    NE_BMPInfoHeader *infoheader =
        (NE_BMPInfoHeader *)(temp + sizeof(NE_BMPHeader));

    NE_write16(&header->type, 0x4D42);
    NE_write32(&header->size, 256 * ysize * 3 + sizeof(NE_BMPInfoHeader)
                              + sizeof(NE_BMPHeader));
    NE_write32(&header->offset,
           sizeof(NE_BMPInfoHeader) + sizeof(NE_BMPHeader));
    NE_write16(&header->reserved1, 0);
    NE_write16(&header->reserved2, 0);

    NE_write16(&infoheader->bits, 24);
    NE_write32(&infoheader->size, sizeof(NE_BMPInfoHeader));
    NE_write32(&infoheader->compression, 0);
    NE_write32(&infoheader->width, 256);
    NE_write32(&infoheader->height, ysize);
    NE_write16(&infoheader->planes, 1);
    NE_write32(&infoheader->imagesize, 256 * ysize * 3);
    NE_write32(&infoheader->xresolution, 0);
    NE_write32(&infoheader->yresolution, 0);
    NE_write32(&infoheader->importantcolors, 0);
    NE_write32(&infoheader->ncolors, 0);

    // Allow CPU to access VRAM
    uint32_t vramTemp = 0;
    if (NE_CurrentExecutionMode() != NE_ModeSingle3D)
    {
        vramTemp = vramSetPrimaryBanks(VRAM_A_LCD, VRAM_B_LCD,
                                       VRAM_C_LCD, VRAM_D_LCD);
    }

    for (int y = 0; y < ysize; y++)
    {
        for (int x = 0; x < 256; x++)
        {
            u16 color = 0;

            if (NE_CurrentExecutionMode() == NE_ModeSingle3D)
            {
                color = VRAM_D[256 * 192 - (y + 1) * 256 + x];
            }
            else
            {
                if (y > 191)
                    color = VRAM_C[256 * 192 - (y - 192 + 1) * 256 + x];
                else
                    color = VRAM_D[256 * 192 - (y + 1) * 256 + x];
            }

            u8 b = (color & 31) << 3;
            u8 g = ((color >> 5) & 31) << 3;
            u8 r = ((color >> 10) & 31) << 3;

            int index = ((y * 256) + x) * 3
                      + sizeof(NE_BMPInfoHeader)
                      + sizeof(NE_BMPHeader);

            temp[index + 0] = r;
            temp[index + 1] = g;
            temp[index + 2] = b;
        }
    }

    if (NE_CurrentExecutionMode() != NE_ModeSingle3D)
        vramRestorePrimaryBanks(vramTemp);

    fwrite(temp, 1, 256 * ysize * 3 + sizeof(NE_BMPInfoHeader)
                    + sizeof(NE_BMPHeader), f);
    fclose(f);
    free(temp);

    // TODO: Restore previous value, not just unpause
    NE_SpecialEffectPause(false);

    return 1;
}
