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

#ifndef NE_TEXTURE_H__
#define NE_TEXTURE_H__

#include <nds.h>

#include "NEPalette.h"
#include "NEPolygon.h"

/// @file   NETexture.h
/// @brief  Texture and material functions.

/// @defgroup material_system Material system
///
/// Material manipulation system. A material is composed of a texture and,
/// optionally, a palette. It also has diffuse, ambient, specular and emission
/// properties.
///
/// @{

#define NE_DEFAULT_TEXTURES 128 ///< Default max number of materials

#define NE_NO_PALETTE       -1 ///< Value that represents not having a palette

/// Holds information of one material.
typedef struct {
    int texindex;            ///< Index to internal texture object
    NE_Palette *palette;     ///< Palette used by this material
    u32 color;               ///< Color of this material when lights aren't used
    u32 diffuse_ambient;     ///< Diffuse and ambient lighting material color
    u32 specular_emission;   ///< Specular and emission lighting material color
    bool palette_autodelete; ///< Set to true for the palette to be deleted with the material.
} NE_Material;

/// Supported texture options
typedef enum {
    NE_TEXTURE_WRAP_S = (1U << 16), ///< Wrap/repeat texture on S axis
    NE_TEXTURE_WRAP_T = (1U << 17), ///< Wrap/repeat texture on T axis
    NE_TEXTURE_FLIP_S = (1U << 18), ///< Flip texture on S axis when wrapping
    NE_TEXTURE_FLIP_T = (1U << 19), ///< Flip texture on T axis when wrapping
    NE_TEXTURE_COLOR0_TRANSPARENT = (1U << 29), ///< Make palette index 0 transparent
    NE_TEXGEN_OFF      = (0U << 30), ///< Don't modify texture coordinates
    NE_TEXGEN_TEXCOORD = (1U << 30), ///< Multiply coordinates by texture matrix
    NE_TEXGEN_NORMAL   = (2U << 30), ///< Texcoords = Normal * texture matrix (spherical reflection)
    NE_TEXGEN_POSITION = (3U << 30)  ///< Texcoords = Vertex * texture matrix
} NE_TextureFlags;

/// Creates a new material object.
///
/// @return Pointer to the newly created material.
NE_Material *NE_MaterialCreate(void);

/// Applies a color to a material.
///
/// Note that the color will only be displayed if no normal commands are used.
/// Any model with normals will ignore this color.
///
/// @param tex Material.
/// @param color Color.
void NE_MaterialColorSet(NE_Material *tex, u32 color);

/// Removes the color of a material (sets it back to white).
///
/// @param tex Material.
void NE_MaterialColorDelete(NE_Material *tex);

/// Loads a texture from the filesystem and assigns it to a material object.
///
/// The height doesn't need to be a power of two, but he width must be a power
/// of two.
///
/// Textures with width that isn't a power of two need to be resized manually,
/// which is very slow, and they don't save any VRAM when loaded compared to a
/// texture with the full width. The only advantage is that they need less
/// storage space, but you can achieve the same effect by compressing them.
///
/// Textures with a height that isn't a power of two don't need to be resized,
/// and they actually save VRAM space (you tell the GPU that the texture is
/// bigger, but then you ignore the additional space, as it will be used by
/// other textures).
///
/// Textures with the 4x4 Texel format (NE_TEX4X4) are normally split into two
/// parts: one that goes into texture slots 0 or 2 and another one that goes
/// into texture slot 1. This function expects the two parts to be concatenated
/// (with the slot 1 part after the other part).
///
/// @param tex Material.
/// @param fmt Texture format.
/// @param sizeX (sizeX, sizeY) Texture size.
/// @param sizeY (sizeX, sizeY) Texture size.
/// @param flags Parameters of the texture.
/// @param path Path of the texture file.
/// @return It returns 1 on success, 0 on error.
int NE_MaterialTexLoadFAT(NE_Material *tex, NE_TextureFormat fmt,
                          int sizeX, int sizeY, NE_TextureFlags flags,
                          const char *path);

/// Loads a texture in Texel 4x4 format from the filesystem and assigns it to a
/// material object.
///
/// Width and height need to be powers of two.
///
/// @param tex Material.
/// @param sizeX (sizeX, sizeY) Texture size.
/// @param sizeY (sizeX, sizeY) Texture size.
/// @param flags Parameters of the texture.
/// @param path02 Path of the texture file (part that goes in slot 0/2).
/// @param path1 Path of the texture file (part that goes in slot 1).
/// @return It returns 1 on success, 0 on error.
int NE_MaterialTex4x4LoadFAT(NE_Material *tex, int sizeX, int sizeY,
                             NE_TextureFlags flags, const char *path02,
                             const char *path1);

/// Loads a texture in any format from a GRF file to a material and palette.
///
/// The size and format are obtained from the GRF header.
///
/// @param tex Material.
/// @param pal Palette. If the format is 16 bit, nothing will be loaded here.
/// @param flags Parameters of the texture.
/// @param path Path of the GRF file.
/// @return It returns 1 on success, 0 on error.
int NE_MaterialTexLoadGRF(NE_Material *tex, NE_Palette *pal,
                          NE_TextureFlags flags, const char *path);

/// Loads a texture from RAM and assigns it to a material object.
///
/// Textures with width that isn't a power of two need to be resized manually,
/// which is very slow, and they don't save any VRAM when loaded compared to a
/// texture with the full width. The only advantage is that they need less
/// storage space, but you can achieve the same effect by compressing them.
///
/// Textures with a height that isn't a power of two don't need to be resized,
/// and they actually save VRAM space (you tell the GPU that the texture is
/// bigger, but then you ignore the additional space, as it will be used by
/// other textures).
///
/// Textures with the 4x4 Texel format (NE_TEX4X4) are normally split into two
/// parts: one that goes into texture slots 0 or 2 and another one that goes
/// into texture slot 1. This function expects the two parts to be concatenated
/// (with the slot 1 part after the other part).
///
/// @param tex Material.
/// @param fmt Texture format.
/// @param sizeX (sizeX, sizeY) Texture size.
/// @param sizeY (sizeX, sizeY) Texture size.
/// @param flags Parameters of the texture.
/// @param texture Pointer to the texture data.
/// @return It returns 1 on success, 0 on error.
int NE_MaterialTexLoad(NE_Material *tex, NE_TextureFormat fmt,
                       int sizeX, int sizeY, NE_TextureFlags flags,
                       const void *texture);

/// Loads a texture from RAM and assigns it to a material object.
///
/// Width and height need to be powers of two.
///
/// @param tex Material.
/// @param sizeX (sizeX, sizeY) Texture size.
/// @param sizeY (sizeX, sizeY) Texture size.
/// @param flags Parameters of the texture.
/// @param texture02 Pointer to the texture data (part that goes in slot 0/2).
/// @param texture1 Pointer to the texture data (part that goes in slot 1).
/// @return It returns 1 on success, 0 on error.
int NE_MaterialTex4x4Load(NE_Material *tex, int sizeX, int sizeY,
                          NE_TextureFlags flags, const void *texture02,
                          const void *texture1);

/// Tell a material that it has to delete its palette on deletion.
///
/// Normally, when a material is deleted, the palette isn't deleted with it.
/// This function will tell the material to delete its palette when
/// NE_MaterialDelete() is called. This is helpful because it lets the developer
/// stop caring about the palette.
///
/// @param mat Material.
void NE_MaterialAutodeletePalette(NE_Material *mat);

/// Copies the texture of a material into another material.
///
/// Unlike with models, you can delete the source and destination materials as
/// desired. Nitro Engine will keep track of how many materials use any specific
/// texture and palette and it will remove them when no more materials are using
/// them.
///
/// @param source Source.
/// @param dest Destination.
void NE_MaterialClone(NE_Material *source, NE_Material *dest);

/// Alias of NE_MaterialClone
///
/// @deprecated This definition is only present for backwards compatibility and
/// it will be removed.
#define NE_MaterialTexClone NE_MaterialClone

/// Assigns a palette to a material.
///
/// @param tex Material.
/// @param pal Palette.
void NE_MaterialSetPalette(NE_Material *tex, NE_Palette *pal);

/// Alias of NE_MaterialSetPalette().
///
/// @deprecated This definition is only present for backwards compatibility and
/// it will be removed.
#define NE_MaterialTexSetPal NE_MaterialSetPalette

/// Set active material to use when drawing polygons.
///
/// If the pointer passed is NULL the function will disable textures and new
/// polygons won't be affected by them until this function is called again with
/// a valid material.
///
/// @param tex Material to be used.
void NE_MaterialUse(const NE_Material *tex);

/// Flags to choose which VRAM banks Nitro Engine can use to allocate textures.
typedef enum {
    NE_VRAM_A = (1 << 0), ///< Bank A
    NE_VRAM_B = (1 << 1), ///< Bank B
    NE_VRAM_C = (1 << 2), ///< Bank C
    NE_VRAM_D = (1 << 3), ///< Bank D

    NE_VRAM_AB = NE_VRAM_A | NE_VRAM_B, ///< Banks A and B
    NE_VRAM_AC = NE_VRAM_A | NE_VRAM_C, ///< Banks A and C
    NE_VRAM_AD = NE_VRAM_A | NE_VRAM_D, ///< Banks A and D
    NE_VRAM_BC = NE_VRAM_B | NE_VRAM_C, ///< Banks B and C
    NE_VRAM_BD = NE_VRAM_B | NE_VRAM_D, ///< Banks B and D
    NE_VRAM_CD = NE_VRAM_C | NE_VRAM_D, ///< Banks C and D

    NE_VRAM_ABC = NE_VRAM_A | NE_VRAM_B | NE_VRAM_C, ///< Banks A, B and C
    NE_VRAM_ABD = NE_VRAM_A | NE_VRAM_B | NE_VRAM_D, ///< Banks A, B and D
    NE_VRAM_ACD = NE_VRAM_A | NE_VRAM_C | NE_VRAM_D, ///< Banks A, C and D
    NE_VRAM_BCD = NE_VRAM_B | NE_VRAM_C | NE_VRAM_D, ///< Banks B, C and D

    NE_VRAM_ABCD = NE_VRAM_A | NE_VRAM_B | NE_VRAM_C | NE_VRAM_D, ///< All main banks
} NE_VRAMBankFlags;

/// Resets the material system and sets the new max number of objects.
///
/// In Dual 3D mode, only VRAM A and B are available for textures.
///
/// If no VRAM banks are specified in this function, all VRAM banks A to D will
/// be used for textures (or just A and B in dual 3D mode).
///
/// @param max_textures Max number of textures. If lower than 1, it will
///                     create space for NE_DEFAULT_TEXTURES.
/// @param max_palettes Max number of palettes. If lower than 1, it will
///                     create space for NE_DEFAULT_PALETTES.
/// @param bank_flags VRAM banks where Nitro Engine can allocate textures.
/// @return Returns 0 on success.
int NE_TextureSystemReset(int max_textures, int max_palettes,
                          NE_VRAMBankFlags bank_flags);

/// Deletes a material object.
///
/// @param tex Pointer to the material object.
void NE_MaterialDelete(NE_Material *tex);

/// Returns the available free memory for textures.
///
/// Note that, even if it is all available, it may not be contiguous, so you may
/// not be able to load a texture because there isn't enough space in any free
/// gap.
///
/// @return Returns the available memory in bytes.
int NE_TextureFreeMem(void);

/// Returns the percentage of available free memory for textures.
///
/// @return Returns the percentage of available memory (0-100).
int NE_TextureFreeMemPercent(void);

/// Defragment memory used for textures.
///
/// WARNING: This function is currently not working.
void NE_TextureDefragMem(void);

/// End texture system and free all memory used by it.
void NE_TextureSystemEnd(void);

/// Returns the width of a texture.
///
/// This is the size given when the texture was loaded.
///
/// @param tex Material.
/// @return Returns the size in pixels.
int NE_TextureGetSizeX(const NE_Material *tex);

/// Returns the height of a texture.
///
/// This is the size given when the texture was loaded.
///
/// @param tex Material.
/// @return Returns the size in pixels.
int NE_TextureGetSizeY(const NE_Material *tex);

/// Returns the real width of a texture.
///
/// This is the internal size given to the GPU when the texture is used, not the
/// size used to load the texture, which may have been smaller.
///
/// @param tex Material.
/// @return Returns the size in pixels.
int NE_TextureGetRealSizeX(const NE_Material *tex);

/// Returns the real height size of a texture.
///
/// This is the internal size given to the GPU when the texture is used, not the
/// size used to load the texture, which may have been smaller.
///
/// @param tex Material.
/// @return Returns the size in pixels.
int NE_TextureGetRealSizeY(const NE_Material *tex);

/// Sets lighting properties of this material.
///
/// @param tex Material to modify.
/// @param diffuse Set diffuse color: lights that directly hits the polygon.
/// @param ambient Set ambient color: lights that indirectly hit the polygon
///                (reflections from the walls, etc).
/// @param specular Set specular color: lights reflected towards the camera,
///                 like a mirror.
/// @param emission Set emission color: light emitted by the polygon.
/// @param vtxcolor If true, diffuse reflection will work as a color command.
/// @param useshininess If true, specular reflection will use the shininess
///                     table.
void NE_MaterialSetProperties(NE_Material *tex, u32 diffuse, u32 ambient,
                              u32 specular, u32 emission, bool vtxcolor,
                              bool useshininess);

/// Alias of NE_MaterialSetProperties
///
/// @deprecated This definition is only present for backwards compatibility and
/// it will be removed.
#define NE_MaterialSetPropierties NE_MaterialSetProperties

/// Sets default lighting properties of materials when they are created.
///
/// @param diffuse Set diffuse color: lights that directly hits the polygon.
/// @param ambient Set ambient color: lights that indirectly hit the polygon
///                (reflections from the walls, etc).
/// @param specular Set specular color: lights reflected towards the camera,
///                 like a mirror.
/// @param emission Set emission color: light emitted by the polygon.
/// @param vtxcolor If true, diffuse reflection will work as a color command.
/// @param useshininess If true, specular reflection will use the shininess
///                     table.
void NE_MaterialSetDefaultProperties(u32 diffuse, u32 ambient, u32 specular,
                                     u32 emission, bool vtxcolor,
                                     bool useshininess);

/// Alias of NE_MaterialSetDefaultProperties
///
/// @deprecated This definition is only present for backwards compatibility and
/// it will be removed.
#define NE_MaterialSetDefaultPropierties NE_MaterialSetDefaultProperties

/// Enables modification of the specified texture.
///
/// Use this during VBL. Remember to use NE_TextureDrawingEnd() when you finish.
/// If you don't, the GPU won't be able to render textures to the screen.
///
/// @param tex Texture to modify.
/// @return Returns a pointer to the base address of the texture in VRAM.
void *NE_TextureDrawingStart(const NE_Material *tex);

/// Sets the specified pixel to the specified color.
///
/// This only works for textures in RGBA/RGB format.
///
/// Use this during VBL.
///
/// @param x (x, y) Pixel coordinates.
/// @param y (x, y) Pixel coordinates.
/// @param color Color in RGB15. Bit 15 must be set to make the pixel visible.
void NE_TexturePutPixelRGBA(u32 x, u32 y, u16 color);

/// Sets the specified pixel to the specified palette color index.
///
/// This only works for textures in RGB256 format.
///
/// Use this during VBL.
///
/// @param x (x,y) Pixel coordinates.
/// @param y (x,y) Pixel coordinates.
/// @param palettecolor New palette color index.
void NE_TexturePutPixelRGB256(u32 x, u32 y, u8 palettecolor);

/// Disables modification of textures.
///
/// Use this during VBL.
void NE_TextureDrawingEnd(void);

/// @}

#endif // NE_TEXTURE_H__
