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

#ifndef NE_POLYGON_H__
#define NE_POLYGON_H__

#include "NEMain.h"

/// @file   NEPolygon.h
/// @brief  Functions to draw polygons and more...

/// @defgroup other_functions Other functions
///
/// Some functions to set lights and its properties, to draw polygons, configure
/// the rear plane, etc...
///
/// @{

/// Predefined colors.
typedef enum {
    NE_Brown     = RGB15(10, 6, 1),     ///<  Brown
    NE_Red       = RGB15(31, 0, 0),     ///<  Red
    NE_Orange    = RGB15(31, 20, 0),    ///<  Orange
    NE_Yellow    = RGB15(31, 31, 0),    ///<  Yellow
    NE_LimeGreen = RGB15(15, 31, 0),    ///<  Lime green
    NE_Green     = RGB15(0, 31, 0),     ///<  Green
    NE_DarkGreen = RGB15(0, 15, 0),     ///<  Dark green
    NE_LightBlue = RGB15(7, 15, 31),    ///<  Light blue
    NE_Blue      = RGB15(0, 0, 31),     ///<  Blue
    NE_DarkBlue  = RGB15(0, 6, 15),     ///<  Dark blue
    NE_Violet    = RGB15(28, 8, 28),    ///<  Violet
    NE_Pink      = RGB15(31, 15, 22),   ///<  Pink
    NE_Purple    = RGB15(20, 4, 14),    ///<  Purple
    NE_Indigo    = RGB15(15, 15, 30),   ///<  Purple
    NE_Magenta   = RGB15(31, 0, 31),    ///<  Magenta
    NE_White     = RGB15(31, 31, 31),   ///<  White
    NE_Gray      = RGB15(20, 20, 20),   ///<  Gray
    NE_DarkGray  = RGB15(10, 10, 10),   ///<  Dark gray
    NE_Black     = RGB15(0, 0, 0)       ///<  Black
} NE_ColorEnum;

/// Supported texture formats
typedef enum {
    NE_A3PAL32    = 1, ///< 32 color palette, 3 bits of alpha
    NE_PAL4       = 2, ///< 4 color palette
    NE_PAL16      = 3, ///< 16 color palette
    NE_PAL256     = 4, ///< 256 color palette
    NE_COMPRESSED = 5, ///< @deprecated 4x4 compressed format (compatibilty name)
    NE_TEX4X4     = 5, ///< 4x4 compressed format
    NE_A5PAL8     = 6, ///< 8 color palette, 5 bits of alpha
    NE_A1RGB5     = 7, ///< Direct color (5 bits per channel), 1 bit of alpha
    NE_RGB5       = 8  ///< @deprecated Like NE_A1RGB5, but sets alpha to 1 when loading
} NE_TextureFormat;

/// Switch off a light.
///
/// @param index Index of the light to switch off (0 - 3).
void NE_LightOff(int index);

/// Switch on a light and define its color.
///
/// @param index Index of the light to switch on (0 - 3).
/// @param color Color of the light.
/// @param x (x, y, z) Vector of the light (v10).
/// @param y (x, y, z) Vector of the light (v10).
/// @param z (x, y, z) Vector of the light (v10).
void NE_LightSetI(int index, u32 color, int x, int y, int z);

/// Switch on a light and define its color.
///
/// @param i Index of the light to switch on (0 - 3).
/// @param c Color of the light.
/// @param x (x, y, z) Vector of the light (float).
/// @param y (x, y, z) Vector of the light (float).
/// @param z (x, y, z) Vector of the light (float).
#define NE_LightSet(i, c, x, y, z) \
    NE_LightSetI(i ,c, floattov10(x), floattov10(y), floattov10(z))

/// Sets the color of a light.
///
/// @param index Index of the light (0 - 3).
/// @param color Color.
void NE_LightSetColor(int index, u32 color);

/// Types of functions used to generate a shininess table.
typedef enum {
    NE_SHININESS_NONE,      ///< Fill table with zeroes
    NE_SHININESS_LINEAR,    ///< Increase values linearly
    NE_SHININESS_QUADRATIC, ///< Increase values proportionaly to x^2
    NE_SHININESS_CUBIC,     ///< Increase values proportionaly to x^3
    NE_SHININESS_QUARTIC    ///< Increase values proportionaly to x^4
} NE_ShininessFunction;

/// Generate and load a shininess table used for specular lighting.
///
/// @param function The name of the function used to generate the table.
void NE_ShininessTableGenerate(NE_ShininessFunction function);

/// Begins a polygon.
///
/// @param mode Type of polygon to draw (GL_TRIANGLE, GL_QUAD...).
static inline void NE_PolyBegin(int mode)
{
    GFX_BEGIN = mode;
}

/// Stops drawing polygons.
static inline void NE_PolyEnd(void)
{
    GFX_END = 0;
}

/// Sets the color for the following vertices.
///
/// @param color Color.
static inline void NE_PolyColor(u32 color)
{
    GFX_COLOR = color;
}

/// Set the normal vector for next group of vertices.
///
/// @param x (x, y, z) Unit vector (v10).
/// @param y (x, y, z) Unit vector (v10).
/// @param z (x, y, z) Unit vector (v10).
static inline void NE_PolyNormalI(int x, int y, int z)
{
    GFX_NORMAL = NORMAL_PACK(x, y, z);
}

/// Set the normal vector for next group of vertices.
///
/// @param x (x, y, z) Unit vector (float).
/// @param y (x, y, z) Unit vector (float).
/// @param z (x, y, z) Unit vector (float).
#define NE_PolyNormal(x, y, z) \
    NE_PolyNormalI(floattov10(x), floattov10(y), floattov10(z))

/// Send vertex to the GPU.
///
/// @param x (x, y, z) Vertex coordinates (v16).
/// @param y (x, y, z) Vertex coordinates (v16).
/// @param z (x, y, z) Vertex coordinates (v16).
static inline void NE_PolyVertexI(int x, int y, int z)
{
    GFX_VERTEX16 = (y << 16) | (x & 0xFFFF);
    GFX_VERTEX16 = (uint32_t)(uint16_t)z;
}

/// Send vertex to the GPU.
///
/// @param x (x, y, z) Vertex coordinates (float).
/// @param y (x, y, z) Vertex coordinates (float).
/// @param z (x, y, z) Vertex coordinates (float).
#define NE_PolyVertex(x, y, z) \
    NE_PolyVertexI(floattov16(x), floattov16(y), floattov16(z))

/// Set texture coordinates.
///
/// "When texture mapping, the Geometry Engine works faster if you issue commands
/// in the order TexCoord -> Normal -> Vertex."
///
/// https://problemkaputt.de/gbatek.htm#ds3dtextureattributes
///
/// @param u (u, v) Texture coordinates (0 - texturesize).
/// @param v (u, v) Texture coordinates (0 - texturesize).
static inline void NE_PolyTexCoord(int u, int v)
{
    GFX_TEX_COORD = TEXTURE_PACK(inttot16(u), inttot16(v));
}

/// Flags for NE_PolyFormat() to enable lights.
typedef enum {
    NE_LIGHT_0 = (1 << 0), ///< Light 0
    NE_LIGHT_1 = (1 << 1), ///< Light 1
    NE_LIGHT_2 = (1 << 2), ///< Light 2
    NE_LIGHT_3 = (1 << 3), ///< Light 3

    NE_LIGHT_01 = NE_LIGHT_0 | NE_LIGHT_1, ///< Lights 0 and 1
    NE_LIGHT_02 = NE_LIGHT_0 | NE_LIGHT_2, ///< Lights 0 and 2
    NE_LIGHT_03 = NE_LIGHT_0 | NE_LIGHT_3, ///< Lights 0 and 3
    NE_LIGHT_12 = NE_LIGHT_1 | NE_LIGHT_2, ///< Lights 1 and 2
    NE_LIGHT_13 = NE_LIGHT_1 | NE_LIGHT_3, ///< Lights 1 and 3
    NE_LIGHT_23 = NE_LIGHT_2 | NE_LIGHT_3, ///< Lights 2 and 3

    NE_LIGHT_012 = NE_LIGHT_0 | NE_LIGHT_1 | NE_LIGHT_2, ///< Lights 0, 1 and 2
    NE_LIGHT_013 = NE_LIGHT_0 | NE_LIGHT_1 | NE_LIGHT_2, ///< Lights 0, 1 and 3
    NE_LIGHT_023 = NE_LIGHT_0 | NE_LIGHT_1 | NE_LIGHT_2, ///< Lights 0, 2 and 3
    NE_LIGHT_123 = NE_LIGHT_0 | NE_LIGHT_1 | NE_LIGHT_2, ///< Lights 1, 2 and 3

    NE_LIGHT_0123 = NE_LIGHT_0 | NE_LIGHT_1 | NE_LIGHT_2 | NE_LIGHT_3, ///< All lights

    NE_LIGHT_ALL = NE_LIGHT_0123 ///< All lights
} NE_LightEnum;

/// Flags for NE_PolyFormat() to specify the type of culling.
typedef enum {
    NE_CULL_FRONT = (1 << 6), ///< Don't draw polygons looking at the camera
    NE_CULL_BACK  = (2 << 6), ///< Don't draw polygons not looking at the camera
    NE_CULL_NONE  = (3 << 6)  ///< Draw all polygons
} NE_CullingEnum;

/// Miscellaneous flags used in NE_PolyFormat().
typedef enum {
    NE_MODULATION             = (0 << 4), ///< Modulation (normal) shading
    NE_DECAL                  = (1 << 4), ///< Decal
    NE_TOON_HIGHLIGHT_SHADING = (2 << 4), ///< Toon or highlight shading
    NE_SHADOW_POLYGONS        = (3 << 4), ///< Shadow polygons

    NE_TRANS_DEPTH_KEEP   = (0 << 11), ///< Keep old depth for translucent pixels
    NE_TRANS_DEPTH_UPDATE = (1 << 11), ///< Set new depth for translucent pixels

    NE_HIDE_FAR_CLIPPED   = (0 << 12), ///< Hide far-plane intersecting polys
    NE_RENDER_FAR_CLIPPED = (1 << 12), ///< Draw far-plane intersecting polys

    NE_HIDE_ONE_DOT_POLYS   = (0 << 13), ///< Hide 1-dot polygons behind DISP_1DOT_DEPTH
    NE_RENDER_ONE_DOT_POLYS = (0 << 13), ///< Draw 1-dot polygons behind DISP_1DOT_DEPTH

    NE_DEPTH_TEST_LESS  = (0 << 14), ///< Depth Test: draw pixels with less depth
    NE_DEPTH_TEST_EQUAL = (0 << 14), ///< Depth Test: draw pixels with equal depth

    NE_FOG_DISABLE = (0 << 15), ///< Enable fog
    NE_FOG_ENABLE  = (1 << 15), ///< Enable fog
} NE_OtherFormatEnum;

/// Enable or disable multiple polygon-related options.
///
/// Remember that translucent polygons can only be blended on top of other
/// translucent polygons if they have different polygon IDs.
///
/// @param alpha Alpha value (0 = wireframe, 31 = opaque, 1-30 translucent).
/// @param id Polygon ID used for antialias, blending and outlining (0 - 63).
/// @param lights Lights enabled. Use the enum NE_LightEnum for this.
/// @param culling Which polygons must be drawn. Use the enum  NE_CullingEnum.
/// @param other Other parameters. All possible flags are in NE_OtherFormatEnum.
void NE_PolyFormat(u32 alpha, u32 id, NE_LightEnum lights,
                   NE_CullingEnum culling, NE_OtherFormatEnum other);

/// Enable or disable polygon outline.
///
/// For outlining to work, set up the colors with NE_OutliningSetColor().
///
/// Color 0 works with polygon IDs 0 to 7, color 1 works with IDs 8 to 15, up to
/// color 7.
///
/// It only works with opaque or wireframe polygons.
///
/// @param value True enables it, false disables it.
static inline void NE_OutliningEnable(bool value)
{
    if (value)
        GFX_CONTROL |= GL_OUTLINE;
    else
        GFX_CONTROL &= ~GL_OUTLINE;
}

/// Set outlining color for the specified index.
///
/// @param index Color index.
/// @param color Color.
void NE_OutliningSetColor(u32 index, u32 color);

/// Setup shading tables for toon shading.
///
/// For the shading to look nice, change the properties of materials affecte
/// by this to, for example:
///
/// - AMBIENT = RGB15(8, 8, 8)
/// - DIFFUSE = RGB15(24, 24, 24)
/// - SPECULAR = RGB15(0, 0, 0)
/// - EMISSION = RGB15(0, 0, 0)
///
/// @param value True sets up tables for toon shading, false clears them.
void NE_SetupToonShadingTables(bool value);

#define NE_ShadingEnable NE_SetupToonShadingTables

/// Set highlight shading or toon shading modes.
///
/// By default, toon shading is selected.
///
/// @param value True enables highlight shading, false enables toon shading.
static inline void NE_ToonHighlightEnable(bool value)
{
    if (value)
        GFX_CONTROL |= GL_TOON_HIGHLIGHT;
    else
        GFX_CONTROL &= ~GL_TOON_HIGHLIGHT;
}

/// Set color and related values of the rear plane.
///
/// @param color Color.
/// @param alpha Alpha value.
/// @param id Rear plane polygon ID.
void NE_ClearColorSet(u32 color, u32 alpha, u32 id);

/// Clear BMP scroll register.
#ifndef REG_CLRIMAGE_OFFSET
#define REG_CLRIMAGE_OFFSET (*(vu16*)0x4000356)
#endif

/// Enable or disable the clear bitmap.
///
/// The clear bitmap uses VRAM_C as color bitmap and VRAM_D as depth bitmap. You
/// have to copy data there and then use this function to enable it. Those 2
/// VRAM banks can't be used as texture banks with clear bitmap enabled, so you
/// have to call NE_TextureSystemReset(0, 0, USE_VRAM_AB) before enabling it.
///
/// The dual 3D mode needs those two banks for the display capture, so you can't
/// use a clear BMP (even if you could, you would have no space for textures).
///
/// VRAM_C: ABBBBBGGGGGRRRRR (Alpha, Blue, Green, Red)
///
/// VRAM_D: FDDDDDDDDDDDDDDD (Fog enable, Depth) [0 = near, 0x7FFF = far]
///
/// The only real use for this seems to be having a background image with
/// different depths per pixel. If you just want to display a background image
/// it's better to use a textured polygon (or the 2D hardware).
///
/// @param value True to enable it, false to disable it.
void NE_ClearBMPEnable(bool value);

/// Sets scroll of the clear BMP.
///
/// @param x Scroll on the X axis (0 - 255).
/// @param y Scroll on the Y axis (0 - 255).
static inline void NE_ClearBMPScroll(u32 x, u32 y)
{
    REG_CLRIMAGE_OFFSET = (x & 0xFF) | ((y & 0xFF) << 8);
}

/// Enables fog and sets its parameters.
///
/// The values must be determined by trial and error.
///
/// The depth is the distance to the start of the fog from the camera. Use the
/// helpers float_to_12d3() or int_to_12d3().
///
/// @param shift Distance between fog bands (1 - 15).
/// @param color Fog color.
/// @param alpha Alpha value.
/// @param mass Mass of fog.
/// @param depth Start point of fog (0 - 7FFFh)
void NE_FogEnable(u32 shift, u32 color, u32 alpha, int mass, int depth);

/// Enable or disable the background fog.
///
/// This only affects the clear plane, not polygons.
///
/// @param value True enables it, false disables it.
void NE_FogEnableBackground(bool value);

/// Disable fog.
static inline void NE_FogDisable(void)
{
    GFX_CONTROL &= ~(GL_FOG | (15 << 8));
}

/// TODO: Function to set value of DISP_1DOT_DEPTH

/// @}

#endif // NE_POLYGON_H__
