// SPDX-License-Identifier: MIT
//
// Copyright (c) 2009-2014 Cesar Rincon "NightFox"
// Copyright (c) 2023 Antonio Niño Díaz "AntonioND"
//
// NightFox LIB - Basic and debug functions
// http://www.nightfoxandco.com/

#ifdef BLOCKSDS
#define iprintf printf
#endif

#include <stdio.h>

#include <nds.h>

#include "nf_basic.h"

// Folder used as root by NFLib
char NF_ROOTFOLDER[64];

__attribute__((noreturn)) void NF_Error(u32 code, const char *text, unsigned int value)
{
    consoleDemoInit();      // Initialize demo text console
    setBrightness(3, 0);    // Set the brightness to the top

    switch (code)
    {
        case 101: // File not found
            iprintf("File %s not found.\n", text);
            break;

        case 102: // Not enough memory
            iprintf("Out of memory.\n");
            iprintf("%u bytes\n", value);
            iprintf("can't be allocated.\n");
            break;

        case 103: // Not enough free slots
            iprintf("Out of %s slots.\n", text);
            iprintf("All %u slots are in use.\n", value);
            break;

        case 104: // Tiled background not found
            iprintf("Tiled Bg %s\n", text);
            iprintf("not found.\n");
            break;

        case 105: // Background hasn't been created
            iprintf("Background number %u\n", value);
            iprintf("on screen %s is\n", text);
            iprintf("not created.\n");
            break;

        case 106: // Value out of range
            iprintf("%s value out\n", text);
            iprintf("of range (%u max).\n", value);
            break;

        case 107: // Not enough contiguous VRAM blocks (tiles)
            iprintf("Can't allocate %d\n", (int)value);
            iprintf("blocks of tiles for\n");
            iprintf("%s background\n", text);
            iprintf("Free %d KB of VRAM or try to\n", (value * 16384) / 1024);
            iprintf("reload all BGs again\n");
            break;

        case 108: // Not enough contiguous VRAM blocks (maps)
            iprintf("Can't allocate %d\n", (int)value);
            iprintf("blocks of maps for\n");
            iprintf("%s background\n", text);
            iprintf("Free %d KB of VRAM or try to\n", (value * 2048) / 1024);
            iprintf("reload all BGs again\n");
            break;

        case 109: // ID in use
            iprintf("%s ID %u\n", text, value);
            iprintf("is already in use.\n");
            break;

        case 110: // ID not loaded
            iprintf("%s\n", text);
            iprintf("%u not loaded.\n", value);
            break;

        case 111: // ID not in VRAM
            iprintf("%s\n", text);
            iprintf("%u not in VRAM.\n", value);
            break;

        case 112: // Sprite not created
            iprintf("Sprite number %u\n", value);
            iprintf("on screen %s is\n", text);
            iprintf("not created.\n");
            break;

        case 113: // Not enough VRAM
            iprintf("Out of VRAM.\n");
            iprintf("%u bytes for %s\n", value, text);
            iprintf("can't be allocated.\n");
            break;

        case 114: // Text layer doesn't exist
            iprintf("Text layer on screen\n");
            iprintf("%u layer don't exist.\n", value);
            break;

        case 115: // Invalid tiled background size (not a multiple of 256)
            iprintf("Tiled BG %s\n", text);
            iprintf("has wrong size.\n");
            iprintf("Your bg sizes must be\n");
            iprintf("a multiple of 256 pixels.\n");
            break;

        case 116: // File is too big
            iprintf("File %s\n", text);
            iprintf("is too big.\n");
            iprintf("Max size for\n");
            iprintf("file is %u KB.\n", value / 1024);
            break;

        case 117: // Invalid affine background size (only 256x256 and 512x512 allowed)
            iprintf("Affine BG %s\n", text);
            iprintf("has wrong size.\n");
            iprintf("Your bg sizes must be\n");
            iprintf("256x256 or 512x512 and\n");
            iprintf("with 256 tiles or less.\n");
            break;

        case 118: // Invalid affine background layer
            iprintf("Affine Bg %s\n", text);
            iprintf("only can be created\n");
            iprintf("on layers 2 or 3.\n");
            break;

        case 119: // Invalid texture size
            iprintf("Texture ID %u illegal size.\n", value);
            iprintf("Only power of 2 sizes can\n");
            iprintf("be used (8 to 1024).\n");
            break;

        case 120: // Invalid sprite size
            iprintf("Sprite ID %u illegal size.\n", value);
            iprintf("8x8 Sprites can't be used\n");
            iprintf("in 1D_128 mode.\n");
            break;
    }

    // Print error code
    iprintf("Error code %d.\n", (int)code);

    // Stop program
    while (1)
        swiWaitForVBlank();
}

void NF_SetRootFolder(const char *folder)
{
    if (strcmp(folder, "NITROFS") == 0)
    {
        // Initialize both NitroFS and FAT

        // Define NitroFS as the root folder
        snprintf(NF_ROOTFOLDER, sizeof(NF_ROOTFOLDER), "%s", "");

        // Check if NitroFS exists. NitroFS must be mounted beforehand for this
        // to work.
        if (access("nitro:/", F_OK) == 0)
        {
            // NitroFS started ok. Set it as the NFLib root folder
            chdir("nitro:/");
        }
        else
        {
            // NitroFS start error

            // Initialize text console
            consoleDemoInit();

            if (NF_GetLanguage() == 5)
            {
                iprintf("Error iniciando NitroFS.\n");
                iprintf("Programa detenido.\n\n");
                iprintf("Verifica que tu flashcard\n");
                iprintf("es compatible con Argv.\n");
                iprintf("Si no lo es, intenta usar el\n");
                iprintf("Homebrew Menu para ejecutarla.\n\n");
            }
            else
            {
                iprintf("NitroFS Init Error.\n");
                iprintf("Abnormal termination.\n\n");
                iprintf("Check if your flashcard is\n");
                iprintf("Argv compatible.\n");
                iprintf("If not, try to launch the ROM\n");
                iprintf("using the Homebrew Menu.\n\n");
            }
            iprintf("http://sourceforge.net/projects/devkitpro/files/hbmenu/");

            // Stop program
            while (1)
                swiWaitForVBlank();
        }
    }
    else
    {
        // Initialize FAT only

        // Define the root folder of NFLib
        snprintf(NF_ROOTFOLDER, sizeof(NF_ROOTFOLDER), "%s", folder);

        // Check where the NDS is running from
        bool init_ok = false;

        // First, try to detect the drive where the NDS ROM is. If argv has been
        // provided by the loader, it will contain the drive and the path of the
        // ROM.
        if (__system_argv->argvMagic == ARGV_MAGIC && __system_argv->argc >= 1)
        {
            if (strncmp(__system_argv->argv[0], "fat:", 4) == 0)
            {
                // If argv starts by "fat:", try to setup "fat:" as root
                if (access("fat:/", F_OK) == 0)
                {
                    chdir("fat:/");
                    init_ok = true;
                }
            }
            else if (strncmp(__system_argv->argv[0], "sd:", 3) == 0)
            {
                // If argv starts by "sd:", try to setup "sd:" as root
                if (access("sd:/", F_OK) == 0)
                {
                    chdir("sd:/");
                    init_ok = true;
                }
            }
        }

        // Second, try to bruteforce the detection. Check if there is access to
        // the SD card of the DSi first. If not, try with DLDI.
        if (!init_ok)
        {
            if (access("sd:/", F_OK) == 0)
            {
                chdir("sd:/");
                init_ok = true;
            }
            else if (access("fat:/", F_OK) == 0)
            {
                chdir("fat:/");
                init_ok = true;
            }
        }

        // If that didn't work, give up.
        if (!init_ok)
        {
            // NitroFS start error

            // Initialize text console
            consoleDemoInit();

            if (NF_GetLanguage() == 5)
            {
                iprintf("Error iniciando FAT.\n");
                iprintf("Programa detenido.\n\n");
                iprintf("Verifica que tu flashcard es\n");
                iprintf("compatible con DLDI y la ROM\n");
                iprintf("este parcheada correctamente.\n");
            }
            else
            {
                iprintf("FAT Init Error.\n");
                iprintf("Abnormal termination.\n\n");
                iprintf("Check if your flashcard is\n");
                iprintf("DLDI compatible and the ROM\n");
                iprintf("is correctly patched.\n");
            }

            // Stop program
            while (1)
                swiWaitForVBlank();
        }
    }
}

void NF_DmaMemCopy(void *destination, const void *source, u32 size)
{
    // Based on Coranac's function:
    // http://www.coranac.com/2009/05/dma-vs-arm9-fight/

    // Source and destination addresses
    u32 src = (u32)source;
    u32 dst = (u32)destination;

    // Check if addresses are aligned to at least 16 bits
    if ((src | dst) & 1)
    {
        // Not aligned. DMA can't be used, use memcpy()
        memcpy(destination, source, size);
    }
    else
    {
        // Addresses are aligned, use DMA

        // Wait until channel 3 is available
        while (dmaBusy(3));

        // Make sure that the data in the cache is sent to the main RAM
        DC_FlushRange(source, size);

        // Depending on the alignment use 32-bit or 16-bit copy modes
        if ((src | dst | size) & 3)
            dmaCopyHalfWords(3, source, destination, size);
        else
            dmaCopyWords(3, source, destination, size);

        // Prevent the destination from being in cache, it would corrupt the
        // data when it is flushed
        DC_InvalidateRange(destination, size);
    }
}

void NF_FileLoad(const char *path, char **buffer, size_t *size, size_t min_size)
{
    FILE *f = fopen(path, "rb");
    if (f == NULL)
        NF_Error(101, path, 0);

    // Get file size
    fseek(f, 0, SEEK_END);
    size_t size_ = ftell(f);
    rewind(f);

    // If the file size is smaller than the minimum size, pad it with zeroes
    // with calloc().
    char *buffer_;
    if (size_ < min_size)
    {
        buffer_ = calloc(min_size, sizeof(char));
        *size = min_size;
    }
    else
    {
        buffer_ = malloc(size_);
        *size = size_;
    }

    if (buffer_ == NULL)
        NF_Error(102, NULL, size_);

    *buffer = buffer_;

    // Read full file to the start of the buffer
    fread(buffer_, 1, size_, f);
    fclose(f);
}
