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

#include "NEMain.h"
#include "NEAlloc.h"

/// @file NETexture.c

typedef struct {
    u32 param;
    // For regular textures, this is the base address in VRAM of the texture.
    // For compressed textures, this is the address in slot 0 or 2. The address
    // of the data in slot 1 can be calculated from it.
    char *address;
    int uses; // Number of materials that use this texture
    int sizex, sizey;
} ne_textureinfo_t;

static ne_textureinfo_t *NE_Texture = NULL;
static NE_Material **NE_UserMaterials = NULL;

static NEChunk *NE_TexAllocList; // See NEAlloc.h

static bool ne_texture_system_inited = false;

static int NE_MAX_TEXTURES;

// Default material properties
static u32 ne_default_diffuse_ambient;
static u32 ne_default_specular_emission;

static int ne_is_valid_tex_size(int size)
{
    for (int i = 0; i < 8; i++)
    {
        if (size <= (8 << i))
            return (8 << i);
    }
    return 0;
}

static int ne_tex_raw_size(int size)
{
    for (int i = 0; i < 8; i++)
    {
        if (size == 8)
            return i;
        size >>= 1;
    }
    return 0;
}

// The provided address must be in VRAM_A
static inline void *slot0_to_slot1(void *ptr)
{
    uintptr_t offset0 = (uintptr_t)ptr - (uintptr_t)VRAM_A;
    return (void *)((uintptr_t)VRAM_B + (offset0 / 2));
}

// The provided address must be in VRAM_B
static inline void *slot1_to_slot0(void *ptr)
{
    uintptr_t offset1 = (uintptr_t)ptr - (uintptr_t)VRAM_B;
    return (void *)((uintptr_t)VRAM_A + (offset1 * 2));
}

// The provided address must be in VRAM_C
static inline void *slot2_to_slot1(void *ptr)
{
    uintptr_t offset2 = (uintptr_t)ptr - (uintptr_t)VRAM_C;
    return (void *)((uintptr_t)VRAM_B + (64 * 1024) + (offset2 / 2));
}

// The provided address must be in VRAM_B
static inline void *slot1_to_slot2(void *ptr)
{
    uintptr_t offset1 = (uintptr_t)ptr - (uintptr_t)VRAM_B - (64 * 1024);
    return (void *)((uintptr_t)VRAM_C + (offset1 * 2));
}

static inline void ne_set_material_tex_param(NE_Material *tex,
                            int sizeX, int sizeY, uint32_t *addr,
                            GL_TEXTURE_TYPE_ENUM mode, u32 param)
{
    NE_AssertPointer(tex, "NULL pointer");
    NE_Assert(tex->texindex != NE_NO_TEXTURE, "No assigned texture");
    NE_Texture[tex->texindex].param =
            (ne_tex_raw_size(sizeX) << 20) |
            (ne_tex_raw_size(sizeY) << 23) |
            (((uint32_t)addr >> 3) & 0xFFFF) |
            (mode << 26) | param;
}

static void ne_texture_delete(int texture_index)
{
    int slot = texture_index;

    // A texture may be used by several materials
    NE_Texture[slot].uses--;

    // If the number of users is zero, delete it.
    if (NE_Texture[slot].uses == 0)
    {
        uint32_t fmt = (NE_Texture[slot].param >> 26) & 7;

        if (fmt == NE_TEX4X4)
        {
            // Check if the texture is allocated in VRAM_A or VRAM_C, and
            // calculate the corresponding address in VRAM_B.
            void *slot02 = NE_Texture[slot].address;
            void *slot1 = (slot02 < (void *)VRAM_B) ?
                          slot0_to_slot1(slot02) : slot2_to_slot1(slot02);
            NE_Free(NE_TexAllocList, slot02);
            NE_Free(NE_TexAllocList, slot1);
        }
        else
        {
            NE_Free(NE_TexAllocList, NE_Texture[slot].address);
        }

        NE_Texture[slot].address = NULL;
        NE_Texture[slot].param = 0;
    }
}

//--------------------------------------------------------------------------

NE_Material *NE_MaterialCreate(void)
{
    if (!ne_texture_system_inited)
    {
        NE_DebugPrint("System not initialized");
        return NULL;
    }

    for (int i = 0; i < NE_MAX_TEXTURES; i++)
    {
        if (NE_UserMaterials[i] != NULL)
            continue;

        NE_Material *mat = calloc(1, sizeof(NE_Material));
        if (mat == NULL)
        {
            NE_DebugPrint("Not enough memory");
            return NULL;
        }

        NE_UserMaterials[i] = mat;
        mat->texindex = NE_NO_TEXTURE;
        mat->palette = NULL;
        mat->palette_autodelete = false;
        mat->color = NE_White;
        mat->diffuse_ambient = ne_default_diffuse_ambient;
        mat->specular_emission = ne_default_specular_emission;

        return mat;
    }

    NE_DebugPrint("No free slots");

    return NULL;
}

void NE_MaterialColorSet(NE_Material *tex, u32 color)
{
    NE_AssertPointer(tex, "NULL pointer");
    tex->color = color;
}

void NE_MaterialColorDelete(NE_Material *tex)
{
    NE_AssertPointer(tex, "NULL pointer");
    tex->color = NE_White;
}

int NE_MaterialTexLoadGRF(NE_Material *tex, NE_Palette *pal,
                          NE_TextureFlags flags, const char *path)
{
#ifndef NE_BLOCKSDS
    (void)tex;
    (void)pal;
    (void)flags;
    (void)path;
    NE_DebugPrint("%s only supported in BlocksDS", __func__);
    return 0;
#else // NE_BLOCKSDS
    NE_AssertPointer(tex, "NULL material pointer");
    NE_AssertPointer(path, "NULL path pointer");

    int ret = 0;

    void *gfxDst = NULL;
    void *palDst = NULL;
    GRFHeader header = { 0 };
    GRFError err = grfLoadPath(path, &header, &gfxDst, NULL, NULL, NULL,
                               &palDst, NULL);
    if (err != GRF_NO_ERROR)
    {
        NE_DebugPrint("Couldn't load GRF file: %d", err);
        goto cleanup;
    }

    if (gfxDst == NULL)
    {
        NE_DebugPrint("No graphics found in GRF file");
        goto cleanup;
    }

    NE_TextureFormat fmt;
    switch (header.gfxAttr)
    {
        case GRF_TEXFMT_A5I3:
            fmt = NE_A5PAL8;
            break;
        case GRF_TEXFMT_A3I5:
            fmt = NE_A3PAL32;
            break;
        case GRF_TEXFMT_4x4:
            fmt = NE_TEX4X4;
            break;
        case 16:
            fmt = NE_A1RGB5;
            break;
        case 8:
            fmt = NE_PAL256;
            break;
        case 4:
            fmt = NE_PAL16;
            break;
        case 2:
            fmt = NE_PAL4;
            break;
        default:
            NE_DebugPrint("Invalid format in GRF file");
            goto cleanup;
    }

    if (NE_MaterialTexLoad(tex, fmt, header.gfxWidth, header.gfxHeight,
                           flags, gfxDst) == 0)
    {
        NE_DebugPrint("Failed to load GRF texture");
        goto cleanup;
    }

    // If there is no palette to be loaded there is nothing else to do
    if (palDst == NULL)
    {
        ret = 1; // Success
        goto cleanup;
    }

    // There is a palette to load.

    // If the user has provided a palette object, use that one to store the
    // palette. If not, create a palette object and mark it to be autodeleted
    // when the material is deleted.
    bool create_palette = false;

    if (pal == NULL)
    {
        NE_DebugPrint("GRF with a palette, but no palette object provided");
        create_palette = true;
    }

    if (create_palette)
    {
        pal = NE_PaletteCreate();
        if (pal == NULL)
        {
            NE_DebugPrint("Not enough memory for palette object");
            goto cleanup;
        }
    }

    if (NE_PaletteLoadSize(pal, palDst, header.palAttr * 2, fmt) == 0)
    {
        NE_DebugPrint("Failed to load GRF palette");
        if (create_palette)
            NE_PaletteDelete(pal);
        goto cleanup;
    }

    NE_MaterialSetPalette(tex, pal);

    if (create_palette)
        NE_MaterialAutodeletePalette(tex);

    ret = 1; // Success

cleanup:
    free(gfxDst);
    free(palDst);
    return ret;
#endif // NE_BLOCKSDS
}

int NE_MaterialTexLoadFAT(NE_Material *tex, NE_TextureFormat fmt,
                          int sizeX, int sizeY, NE_TextureFlags flags,
                          const char *path)
{
    NE_AssertPointer(tex, "NULL material pointer");
    NE_AssertPointer(path, "NULL path pointer");
    NE_Assert(sizeX > 0 && sizeY > 0, "Size must be positive");

    void *ptr = NE_FATLoadData(path);
    if (ptr == NULL)
    {
        NE_DebugPrint("Couldn't load file from FAT");
        return 0;
    }

    int ret = NE_MaterialTexLoad(tex, fmt, sizeX, sizeY, flags, ptr);
    free(ptr);

    return ret;
}

int NE_MaterialTex4x4LoadFAT(NE_Material *tex, int sizeX, int sizeY,
                             NE_TextureFlags flags, const char *path02,
                             const char *path1)
{
    NE_AssertPointer(tex, "NULL material pointer");
    NE_AssertPointer(path02, "NULL path02 pointer");
    NE_AssertPointer(path1, "NULL path1 pointer");
    NE_Assert(sizeX > 0 && sizeY > 0, "Size must be positive");

    void *texture02 = NE_FATLoadData(path02);
    if (texture02 == NULL)
    {
        NE_DebugPrint("Couldn't load file from FAT");
        return 0;
    }

    void *texture1 = NE_FATLoadData(path1);
    if (texture1 == NULL)
    {
        NE_DebugPrint("Couldn't load file from FAT");
        free(texture02);
        return 0;
    }

    int ret = NE_MaterialTex4x4Load(tex, sizeX, sizeY, flags, texture02,
                                    texture1);

    free(texture02);
    free(texture1);

    return ret;
}

// This function takes as argument the size of the chunk of the compressed
// texture chunk that goes into slots 0 or 2. The size that goes into slot 1 is
// always half of this size, so it isn't needed to provide it.
//
// It returns 0 on success, as well as pointers to the address where both chunks
// need to be copied.
static int ne_alloc_compressed_tex(size_t size, void **slot02, void **slot1)
{
    size_t size02 = size;
    size_t size1 = size / 2;

    // First, try with slot 0 + slot 1
    // -------------------------------

    // Get the first valid range in slot 0
    void *addr0 = NE_AllocFindInRange(NE_TexAllocList, VRAM_A, VRAM_B, size02);
    if (addr0 != NULL)
    {
        // Only use the first half of slot 1 for slot 0
        void *addr1;
        void *addr1_end = (void *)((uintptr_t)VRAM_B + (64 * 1024));

        while (1)
        {
            // Get the address in bank 1 assigned to the current bank 0 address
            addr1 = slot0_to_slot1(addr0);

            // Check if this address is free and has enough space
            addr1 = NE_AllocFindInRange(NE_TexAllocList, addr1, addr1_end, size1);
            if (addr1 == NULL)
                break;

            // If both addresses match, both of them are free
            if (addr1 == slot0_to_slot1(addr0))
            {
                *slot02 = addr0;
                *slot1 = addr1;
                return 0;
            }

            // Get the address in bank 0 assigned to the current bank 1 address
            addr0 = slot1_to_slot0(addr1);

            // Check if this address is free and has enough space
            addr0 = NE_AllocFindInRange(NE_TexAllocList, addr0, VRAM_B, size02);
            if (addr0 == NULL)
                break;

            // If both addresses match, both of them are free
            if (addr1 == slot0_to_slot1(addr0))
            {
                *slot02 = addr0;
                *slot1 = addr1;
                return 0;
            }
        }
    }

    // Then, try with slot 2 + slot 1
    // ------------------------------

    // Get the first valid range in slot 2
    void *addr2 = NE_AllocFindInRange(NE_TexAllocList, VRAM_C, VRAM_D, size02);
    if (addr2 == NULL)
        return -1;

    // Only use the second half of slot 1 for slot 2
    void *addr1;
    void *addr1_end = VRAM_C;

    while (1)
    {
        // Get the address in bank 1 assigned to the current bank 2 address
        addr1 = slot2_to_slot1(addr2);

        // Check if this address is free and has enough space
        addr1 = NE_AllocFindInRange(NE_TexAllocList, addr1, addr1_end, size1);
        if (addr1 == NULL)
            break;

        // If both addresses match, both of them are free
        if (addr1 == slot2_to_slot1(addr2))
        {
            *slot02 = addr2;
            *slot1 = addr1;
            return 0;
        }

        // Get the address in bank 2 assigned to the current bank 1 address
        addr2 = slot1_to_slot2(addr1);

        // Check if this address is free and has enough space
        addr2 = NE_AllocFindInRange(NE_TexAllocList, addr2, VRAM_B, size02);
        if (addr2 == NULL)
            break;

        // If both addresses match, both of them are free
        if (addr1 == slot2_to_slot1(addr2))
        {
            *slot02 = addr2;
            *slot1 = addr1;
            return 0;
        }
    }

    return -1;
}

int NE_MaterialTex4x4Load(NE_Material *tex, int sizeX, int sizeY,
                          NE_TextureFlags flags, const void *texture02,
                          const void *texture1)
{
    NE_AssertPointer(tex, "NULL material pointer");

    // For tex4x4 textures, both width and height must be valid
    if ((ne_is_valid_tex_size(sizeX) != sizeX)
        || (ne_is_valid_tex_size(sizeY) != sizeY))
    {
        NE_DebugPrint("Width and height of tex4x4 textures must be a power of 2");
        return 0;
    }

    // Check if a texture exists
    if (tex->texindex != NE_NO_TEXTURE)
        ne_texture_delete(tex->texindex);

    // Get free slot
    tex->texindex = NE_NO_TEXTURE;
    for (int i = 0; i < NE_MAX_TEXTURES; i++)
    {
        if (NE_Texture[i].address == NULL)
        {
            tex->texindex = i;
            break;
        }
    }

    if (tex->texindex == NE_NO_TEXTURE)
    {
        NE_DebugPrint("No free slots");
        return 0;
    }

    size_t size02 = (sizeX * sizeY) >> 2;
    size_t size1 = size02 >> 1;

    void *slot02, *slot1;
    int ret = ne_alloc_compressed_tex(size02, &slot02, &slot1);
    if (ret != 0)
    {
        NE_DebugPrint("Can't find space for compressed texture");
        return 0;
    }

    ret = NE_AllocAddress(NE_TexAllocList, slot02, size02);
    if (ret != 0)
    {
        NE_DebugPrint("Can't allocate slot 0/2");
        return 0;
    }

    ret = NE_AllocAddress(NE_TexAllocList, slot1, size1);
    if (ret != 0)
    {
        NE_Free(NE_TexAllocList, slot02);
        NE_DebugPrint("Can't allocate slot 1");
        return 0;
    }

    // Save information
    int slot = tex->texindex;
    NE_Texture[slot].sizex = sizeX;
    NE_Texture[slot].sizey = sizeY;
    NE_Texture[slot].address = slot02;
    NE_Texture[slot].uses = 1; // Initially only this material uses the texture

    // Unlock texture memory for writing
    // TODO: Only unlock the banks that Nitro Engine uses.
    u32 vramTemp = vramSetPrimaryBanks(VRAM_A_LCD, VRAM_B_LCD, VRAM_C_LCD,
                                        VRAM_D_LCD);

    swiCopy(texture02, slot02, (size02 >> 2) | COPY_MODE_WORD);
    swiCopy(texture1, slot1, (size1 >> 2) | COPY_MODE_WORD);

    int hardware_size_y = ne_is_valid_tex_size(sizeY);
    ne_set_material_tex_param(tex, sizeX, hardware_size_y, slot02,
                              NE_TEX4X4, flags);

    vramRestorePrimaryBanks(vramTemp);

    return 1;
}

int NE_MaterialTexLoad(NE_Material *tex, NE_TextureFormat fmt,
                       int sizeX, int sizeY, NE_TextureFlags flags,
                       const void *texture)
{
    NE_AssertPointer(tex, "NULL material pointer");
    NE_Assert(fmt != 0, "No texture format provided");

    if (fmt == NE_TEX4X4)
    {
        // Split tex4x4 texture into its two parts, that have been concatenated

        size_t size02 = (sizeX * sizeY) >> 2;

        const void *texture02 = texture;
        const void *texture1 = (const void *)((uintptr_t)texture + size02);

        return NE_MaterialTex4x4Load(tex, sizeX, sizeY, flags,
                                     texture02, texture1);
    }

    // The width of a texture must be a power of 2. The height doesn't need to
    // be a power of 2, but we will have to cheat later and make the DS believe
    // it is a power of 2.
    if (ne_is_valid_tex_size(sizeX) != sizeX)
    {
        NE_DebugPrint("Width of textures must be a power of 2");
        return 0;
    }

    // Check if a texture exists
    if (tex->texindex != NE_NO_TEXTURE)
        ne_texture_delete(tex->texindex);

    // Get free slot
    tex->texindex = NE_NO_TEXTURE;
    for (int i = 0; i < NE_MAX_TEXTURES; i++)
    {
        if (NE_Texture[i].address == NULL)
        {
            tex->texindex = i;
            break;
        }
    }

    if (tex->texindex == NE_NO_TEXTURE)
    {
        NE_DebugPrint("No free slots");
        return 0;
    }

    // All non-compressed texture types are handled here

    const int size_shift[] = {
        0, // Nothing
        1, // NE_A3PAL32
        3, // NE_PAL4
        2, // NE_PAL16
        1, // NE_PAL256
        0, // NE_TEX4X4 (This value isn't used)
        1, // NE_A5PAL8
        0, // NE_A1RGB5
        0, // NE_RGB5
    };

    uint32_t size = (sizeX * sizeY << 1) >> size_shift[fmt];

    // This pointer must be aligned to 8 bytes at least
    void *addr = NE_AllocFromEnd(NE_TexAllocList, size);
    if (!addr)
    {
        tex->texindex = NE_NO_TEXTURE;
        NE_DebugPrint("Not enough memory");
        return 0;
    }

    // Save information
    int slot = tex->texindex;
    NE_Texture[slot].sizex = sizeX;
    NE_Texture[slot].sizey = sizeY;
    NE_Texture[slot].address = addr;
    NE_Texture[slot].uses = 1; // Initially only this material uses the texture

    // Unlock texture memory for writing
    // TODO: Only unlock the banks that Nitro Engine uses.
    u32 vramTemp = vramSetPrimaryBanks(VRAM_A_LCD, VRAM_B_LCD, VRAM_C_LCD,
                                       VRAM_D_LCD);

    if (fmt == NE_RGB5)
    {
        // NE_RGB5 is NE_A1RGB5 with each alpha bit manually set to 1 during the
        // copy to VRAM.
        uint32_t *src = (uint32_t *)texture;
        uint32_t *dest = addr;
        size >>= 2; // We are going to process four bytes each iteration
        while (size--)
            *dest++ = *src++ | ((1 << 15) | (1 << 31));

        fmt = NE_A1RGB5;
    }
    else
    {
        swiCopy((u32 *)texture, addr, (size >> 2) | COPY_MODE_WORD);
    }

    int hardware_size_y = ne_is_valid_tex_size(sizeY);
    ne_set_material_tex_param(tex, sizeX, hardware_size_y, addr, fmt, flags);

    vramRestorePrimaryBanks(vramTemp);

    return 1;
}

void NE_MaterialAutodeletePalette(NE_Material *mat)
{
    NE_AssertPointer(mat, "NULL material pointer");

    mat->palette_autodelete = true;
}

void NE_MaterialClone(NE_Material *source, NE_Material *dest)
{
    NE_AssertPointer(source, "NULL source pointer");
    NE_AssertPointer(dest, "NULL dest pointer");
    NE_Assert(source->texindex != NE_NO_TEXTURE,
              "No texture asigned to source material");
    // Increase count of materials using this texture
    NE_Texture[source->texindex].uses++;
    memcpy(dest, source, sizeof(NE_Material));
}

void NE_MaterialSetPalette(NE_Material *tex, NE_Palette *pal)
{
    NE_AssertPointer(tex, "NULL material pointer");
    NE_AssertPointer(pal, "NULL palette pointer");
    NE_Assert(tex->texindex != NE_NO_TEXTURE, "No texture asigned to material");
    tex->palette = pal;
}

void NE_MaterialUse(const NE_Material *tex)
{
    if (tex == NULL)
    {
        GFX_TEX_FORMAT = 0;
        GFX_COLOR = NE_White;
        GFX_DIFFUSE_AMBIENT = ne_default_diffuse_ambient;
        GFX_SPECULAR_EMISSION = ne_default_specular_emission;
        return;
    }

    GFX_DIFFUSE_AMBIENT = tex->diffuse_ambient;
    GFX_SPECULAR_EMISSION = tex->specular_emission;

    NE_Assert(tex->texindex != NE_NO_TEXTURE, "No texture asigned to material");

    if (tex->palette)
        NE_PaletteUse(tex->palette);

    GFX_COLOR = tex->color;
    GFX_TEX_FORMAT = NE_Texture[tex->texindex].param;
}

int NE_TextureSystemReset(int max_textures, int max_palettes,
                          NE_VRAMBankFlags bank_flags)
{
    if (ne_texture_system_inited)
        NE_TextureSystemEnd();

    NE_Assert((bank_flags & 0xF) != 0, "No VRAM banks selected");

    if (max_textures < 1)
        NE_MAX_TEXTURES = NE_DEFAULT_TEXTURES;
    else
        NE_MAX_TEXTURES = max_textures;

    if (NE_PaletteSystemReset(max_palettes) != 0)
        return -1;

    NE_Texture = calloc(NE_MAX_TEXTURES, sizeof(ne_textureinfo_t));
    NE_UserMaterials = calloc(NE_MAX_TEXTURES, sizeof(NE_UserMaterials));
    if ((NE_Texture == NULL) || (NE_UserMaterials == NULL))
        goto cleanup;

    if (NE_AllocInit(&NE_TexAllocList, VRAM_A, VRAM_E) != 0)
        goto cleanup;

    // Prevent user from not selecting any bank
    if ((bank_flags & 0xF) == 0)
        bank_flags = NE_VRAM_ABCD;

    // VRAM_C and VRAM_D can't be used in dual 3D mode
    if (NE_CurrentExecutionMode() != NE_ModeSingle3D)
        bank_flags &= ~NE_VRAM_CD;

    // Now, configure allocation system. The buffer size always sees the
    // four banks of VRAM. It is needed to allocate and lock one chunk per bank
    // that isn't allocated to Nitro Engine.

    if (bank_flags & NE_VRAM_A)
    {
        vramSetBankA(VRAM_A_TEXTURE_SLOT0);
    }
    else
    {
        NE_AllocAddress(NE_TexAllocList, VRAM_A, 128 * 1024);
        NE_Lock(NE_TexAllocList, VRAM_A);
    }

    if (bank_flags & NE_VRAM_B)
    {
        vramSetBankB(VRAM_B_TEXTURE_SLOT1);
    }
    else
    {
        NE_AllocAddress(NE_TexAllocList, VRAM_B, 128 * 1024);
        NE_Lock(NE_TexAllocList, VRAM_B);
    }

    if (bank_flags & NE_VRAM_C)
    {
        vramSetBankC(VRAM_C_TEXTURE_SLOT2);
    }
    else
    {
        NE_AllocAddress(NE_TexAllocList, VRAM_C, 128 * 1024);
        NE_Lock(NE_TexAllocList, VRAM_C);
    }

    if (bank_flags & NE_VRAM_D)
    {
        vramSetBankD(VRAM_D_TEXTURE_SLOT3);
    }
    else
    {
        NE_AllocAddress(NE_TexAllocList, VRAM_D, 128 * 1024);
        NE_Lock(NE_TexAllocList, VRAM_D);
    }

    GFX_TEX_FORMAT = 0;

    ne_texture_system_inited = true;
    return 0;

cleanup:
    NE_DebugPrint("Not enough memory");
    NE_PaletteSystemEnd();
    free(NE_Texture);
    free(NE_UserMaterials);
    return -1;
}

void NE_MaterialDelete(NE_Material *tex)
{
    NE_AssertPointer(tex, "NULL pointer");

    // Delete the palette if it has been flagged to be autodeleted
    if (tex->palette_autodelete)
        NE_PaletteDelete(tex->palette);

    // If there is an asigned texture
    if (tex->texindex != NE_NO_TEXTURE)
        ne_texture_delete(tex->texindex);

    for (int i = 0; i < NE_MAX_TEXTURES; i++)
    {
        if (NE_UserMaterials[i] == tex)
        {
            NE_UserMaterials[i] = NULL;
            free(tex);
            return;
        }
    }

    NE_DebugPrint("Object not found");
}

int NE_TextureFreeMem(void)
{
    if (!ne_texture_system_inited)
        return 0;

    NEMemInfo info;
    NE_MemGetInformation(NE_TexAllocList, &info);

    return info.free;
}

int NE_TextureFreeMemPercent(void)
{
    if (!ne_texture_system_inited)
        return 0;

    NEMemInfo info;
    NE_MemGetInformation(NE_TexAllocList, &info);

    return info.free_percent;
}

void NE_TextureDefragMem(void)
{
    NE_Assert(0, "This function doesn't work");
    return;
    /*
    // REALLY OLD CODE -- DOESN'T WORK

    if (!ne_texture_system_inited)
        return;

    uint32_t vramTemp = vramSetMainBanks(VRAM_A_LCD, VRAM_B_LCD, VRAM_C_LCD,
                                       VRAM_D_LCD);

    bool ok = false;
    while (!ok)
    {
        ok = true;
        int i;
        for (i = 0; i < NE_MAX_TEXTURES; i++)
        {
            int size = NE_GetSize(NE_TexAllocList, (void*)NE_Texture[i].address);
            NE_Free(NE_TexAllocList,(void*)NE_Texture[i].address);
            void *pointer = NE_Alloc(NE_TexAllocList, size);
            // Aligned to 8 bytes

            NE_AssertPointer(pointer, "Couldn't reallocate texture");

            if (pointer != NE_Texture[i].address)
            {
                dmaCopy((void *)NE_Texture[i].address, pointer, size);
                NE_Texture[i].address = pointer;
                NE_Texture[i].param &= 0xFFFF0000;
                NE_Texture[i].param |= ((uint32_t)pointer >> 3) & 0xFFFF;
                ok = false;
            }
        }
    }
    vramRestoreMainBanks(vramTemp);
    */
}

void NE_TextureSystemEnd(void)
{
    if (!ne_texture_system_inited)
        return;

    NE_AllocEnd(&NE_TexAllocList);

    free(NE_Texture);

    for (int i = 0; i < NE_MAX_TEXTURES; i++)
    {
        if (NE_UserMaterials[i])
            free(NE_UserMaterials[i]);
    }

    free(NE_UserMaterials);

    NE_Texture = NULL;

    NE_PaletteSystemEnd();

    ne_texture_system_inited = false;
}

// Internal use
int __NE_TextureGetRawX(const NE_Material *tex)
{
    NE_AssertPointer(tex, "NULL pointer");
    NE_Assert(tex->texindex != NE_NO_TEXTURE,
          "No texture asigned to material");
    return (NE_Texture[tex->texindex].param & (0x7 << 20)) >> 20;
}

// Internal use
int __NE_TextureGetRawY(const NE_Material *tex)
{
    NE_AssertPointer(tex, "NULL pointer");
    NE_Assert(tex->texindex != NE_NO_TEXTURE, "No texture asigned to material");
    return (NE_Texture[tex->texindex].param & (0x7 << 23)) >> 23;
}

int NE_TextureGetRealSizeX(const NE_Material *tex)
{
    NE_AssertPointer(tex, "NULL pointer");
    NE_Assert(tex->texindex != NE_NO_TEXTURE, "No texture asigned to material");
    return 8 << __NE_TextureGetRawX(tex);
}

int NE_TextureGetRealSizeY(const NE_Material *tex)
{
    NE_AssertPointer(tex, "NULL pointer");
    NE_Assert(tex->texindex != NE_NO_TEXTURE, "No texture asigned to material");
    return 8 << __NE_TextureGetRawY(tex);
}

int NE_TextureGetSizeX(const NE_Material *tex)
{
    NE_AssertPointer(tex, "NULL pointer");
    NE_Assert(tex->texindex != NE_NO_TEXTURE, "No texture asigned to material");
    return NE_Texture[tex->texindex].sizex;
}

int NE_TextureGetSizeY(const NE_Material *tex)
{
    NE_AssertPointer(tex, "NULL pointer");
    NE_Assert(tex->texindex != NE_NO_TEXTURE, "No texture asigned to material");
    return NE_Texture[tex->texindex].sizey;
}

void NE_MaterialSetProperties(NE_Material *tex, u32 diffuse,
                              u32 ambient, u32 specular, u32 emission,
                              bool vtxcolor, bool useshininess)
{
    NE_AssertPointer(tex, "NULL pointer");
    tex->diffuse_ambient = diffuse | (ambient << 16) | (vtxcolor << 15);
    tex->specular_emission = specular | (emission << 16) | (useshininess << 15);
}

void NE_MaterialSetDefaultProperties(u32 diffuse, u32 ambient,
                                     u32 specular, u32 emission,
                                     bool vtxcolor, bool useshininess)
{
    ne_default_diffuse_ambient = diffuse | (ambient << 16) | (vtxcolor << 15);
    ne_default_specular_emission = specular | (emission << 16)
                                 | (useshininess << 15);

    GFX_DIFFUSE_AMBIENT = ne_default_diffuse_ambient;
    GFX_SPECULAR_EMISSION = ne_default_specular_emission;
}

static u16 *drawingtexture_address = NULL;
static int drawingtexture_x, drawingtexture_y;
static int drawingtexture_type;
static int drawingtexture_realx;
static u32 ne_vram_saved;

void *NE_TextureDrawingStart(const NE_Material *tex)
{
    NE_AssertPointer(tex, "NULL pointer");
    NE_Assert(tex->texindex != NE_NO_TEXTURE, "No texture asigned to material");

    NE_Assert(drawingtexture_address == NULL,
              "Another texture is already active");

    drawingtexture_x = NE_TextureGetSizeX(tex);
    drawingtexture_realx = NE_TextureGetRealSizeX(tex);
    drawingtexture_y = NE_TextureGetSizeY(tex);
    drawingtexture_address = (u16 *) ((uintptr_t)VRAM_A
                          + ((NE_Texture[tex->texindex].param & 0xFFFF) << 3));
    drawingtexture_type = ((NE_Texture[tex->texindex].param >> 26) & 0x7);

    ne_vram_saved = vramSetPrimaryBanks(VRAM_A_LCD, VRAM_B_LCD, VRAM_C_LCD,
                                        VRAM_D_LCD);

    return drawingtexture_address;
}

void NE_TexturePutPixelRGBA(u32 x, u32 y, u16 color)
{
    NE_AssertPointer(drawingtexture_address,
                     "No texture active for drawing");
    NE_Assert(drawingtexture_type == NE_A1RGB5,
              "Ative texture isn't NE_A1RGB5");

    if (x >= drawingtexture_x || y >= drawingtexture_y)
        return;

    drawingtexture_address[x + (y * drawingtexture_realx)] = color;
}

void NE_TexturePutPixelRGB256(u32 x, u32 y, u8 palettecolor)
{
    NE_AssertPointer(drawingtexture_address,
                     "No texture active for drawing.");
    NE_Assert(drawingtexture_type == NE_PAL256,
              "Active texture isn't NE_PAL256");

    if (x >= drawingtexture_x || y >= drawingtexture_y)
        return;

    int position = (x + (y * drawingtexture_realx)) >> 1;
    int desp = (x & 1) << 3;

    drawingtexture_address[position] &= 0xFF00 >> desp;
    drawingtexture_address[position] |= ((u16) palettecolor) << desp;
}

void NE_TextureDrawingEnd(void)
{
    NE_Assert(drawingtexture_address != NULL, "No active texture");

    vramRestorePrimaryBanks(ne_vram_saved);

    drawingtexture_address = NULL;
}
