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

#include "NEMain.h"

/// @file NE2D.c

static NE_Sprite **NE_spritepointers = NULL;

static int NE_MAX_SPRITES;

static bool ne_sprite_system_inited = false;

NE_Sprite *NE_SpriteCreate(void)
{
    if (!ne_sprite_system_inited)
    {
        NE_DebugPrint("System not initialized");
        return NULL;
    }

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

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

        sprite->visible = true;
        sprite->scale = inttof32(1);
        sprite->color = NE_White;
        sprite->mat = NULL;
        sprite->alpha = 31;

        NE_spritepointers[i] = sprite;

        return sprite;
    }

    NE_DebugPrint("No free slots");
    return NULL;
}

void NE_SpriteSetPos(NE_Sprite *sprite, int x, int y)
{
    NE_AssertPointer(sprite, "NULL pointer");
    sprite->x = x;
    sprite->y = y;
}

void NE_SpriteSetSize(NE_Sprite *sprite, int w, int h)
{
    NE_AssertPointer(sprite, "NULL pointer");
    sprite->w = w;
    sprite->h = h;
}

void NE_SpriteSetRot(NE_Sprite *sprite, int angle)
{
    NE_AssertPointer(sprite, "NULL pointer");
    sprite->rot_angle = angle;
}

void NE_SpriteSetScaleI(NE_Sprite *sprite, int scale)
{
    NE_AssertPointer(sprite, "NULL pointer");
    sprite->scale = scale;
}

void NE_SpriteSetMaterial(NE_Sprite *sprite, NE_Material *mat)
{
    NE_AssertPointer(sprite, "NULL sprite pointer");
    NE_AssertPointer(mat, "NULL material pointer");
    sprite->mat = mat;

    int mat_w = NE_TextureGetSizeX(mat);
    int mat_h = NE_TextureGetSizeY(mat);

    sprite->w = mat_w;
    sprite->h = mat_h;

    sprite->tl = 0;
    sprite->tr = mat_w;
    sprite->tt = 0;
    sprite->tb = mat_h;
}

void NE_SpriteSetMaterialCanvas(NE_Sprite *sprite, int tl, int tt, int tr, int tb)
{
    NE_AssertPointer(sprite, "NULL sprite pointer");
    NE_AssertPointer(sprite->mat, "Sprite doesn't have a material");

    sprite->tl = tl;
    sprite->tr = tr;
    sprite->tt = tt;
    sprite->tb = tb;
}

void NE_SpriteSetPriority(NE_Sprite *sprite, int priority)
{
    NE_AssertPointer(sprite, "NULL pointer");
    sprite->priority = priority;
}

void NE_SpriteVisible(NE_Sprite *sprite, bool visible)
{
    NE_AssertPointer(sprite, "NULL pointer");
    sprite->visible = visible;
}

void NE_SpriteSetParams(NE_Sprite *sprite, u8 alpha, u8 id, u32 color)
{
    NE_AssertPointer(sprite, "NULL pointer");
    NE_AssertMinMax(0, alpha, 31, "Invalid alpha value %d", alpha);
    NE_AssertMinMax(0, id, 63, "Invalid polygon ID %d", id);

    sprite->alpha = alpha;
    sprite->id = id;
    sprite->color = color;
}

void NE_SpriteDelete(NE_Sprite *sprite)
{
    if (!ne_sprite_system_inited)
        return;

    NE_AssertPointer(sprite, "NULL pointer");

    for (int i = 0; i < NE_MAX_SPRITES; i++)
    {
        if (NE_spritepointers[i] != sprite)
            continue;

        NE_spritepointers[i] = NULL;
        free((void *)sprite);

        return;
    }

    NE_DebugPrint("Object not found");
    return;
}

void NE_SpriteDeleteAll(void)
{
    if (!ne_sprite_system_inited)
        return;

    for (int i = 0; i < NE_MAX_SPRITES; i++)
        NE_SpriteDelete(NE_spritepointers[i]);
}

int NE_SpriteSystemReset(int max_sprites)
{
    if (ne_sprite_system_inited)
        NE_SpriteSystemEnd();

    if (max_sprites < 1)
        NE_MAX_SPRITES = NE_DEFAULT_SPRITES;
    else
        NE_MAX_SPRITES = max_sprites;

    NE_spritepointers = calloc(NE_MAX_SPRITES, sizeof(NE_spritepointers));
    if (NE_spritepointers == NULL)
    {
        NE_DebugPrint("Not enough memory");
        return -1;
    }

    ne_sprite_system_inited = true;
    return 0;
}

void NE_SpriteSystemEnd(void)
{
    if (!ne_sprite_system_inited)
        return;

    NE_SpriteDeleteAll();

    free(NE_spritepointers);

    ne_sprite_system_inited = false;
}

void NE_SpriteDraw(const NE_Sprite *sprite)
{
    if (!ne_sprite_system_inited)
        return;

    NE_AssertPointer(sprite, "NULL pointer");

    if (!sprite->visible)
        return;

    if (sprite->rot_angle)
    {
        MATRIX_PUSH = 0;

        NE_2DViewRotateScaleByPositionI(sprite->x + (sprite->w >> 1),
                                        sprite->y + (sprite->h >> 1),
                                        sprite->rot_angle,
                                        sprite->scale);
    }
    else
    {
        NE_2DViewScaleByPositionI(sprite->x + (sprite->w >> 1),
                                  sprite->y + (sprite->h >> 1),
                                  sprite->scale);
    }

    GFX_POLY_FORMAT = POLY_ALPHA(sprite->alpha) | POLY_ID(sprite->id) |
                      NE_CULL_NONE;

    NE_2DDrawTexturedQuadColorCanvas(sprite->x, sprite->y,
                                     sprite->x + sprite->w,
                                     sprite->y + sprite->h,
                                     sprite->priority,
                                     sprite->tl, sprite->tt,
                                     sprite->tr, sprite->tb,
                                     sprite->mat, sprite->color);

    if (sprite->rot_angle)
        MATRIX_POP = 1;
}

void NE_SpriteDrawAll(void)
{
    if (!ne_sprite_system_inited)
        return;

    for (int i = 0; i < NE_MAX_SPRITES; i++)
    {
        if (NE_spritepointers[i] == NULL)
            continue;

        NE_Sprite *sprite = NE_spritepointers[i];

        if (!sprite->visible)
            continue;

        if (sprite->rot_angle)
        {
            MATRIX_PUSH = 0;

            NE_2DViewRotateScaleByPositionI(sprite->x + (sprite->w >> 1),
                                            sprite->y + (sprite->h >> 1),
                                            sprite->rot_angle,
                                            sprite->scale);
        }
        else
        {
            NE_2DViewScaleByPositionI(sprite->x + (sprite->w >> 1),
                                      sprite->y + (sprite->h >> 1),
                                      sprite->scale);
        }

        GFX_POLY_FORMAT = POLY_ALPHA(sprite->alpha) |
                          POLY_ID(sprite->id) | NE_CULL_NONE;

        NE_2DDrawTexturedQuadColorCanvas(sprite->x, sprite->y,
                                         sprite->x + sprite->w,
                                         sprite->y + sprite->h,
                                         sprite->priority,
                                         sprite->tl, sprite->tt,
                                         sprite->tr, sprite->tb,
                                         sprite->mat, sprite->color);

        if (sprite->rot_angle)
            MATRIX_POP = 1;
    }
}

//----------------------------------------------------------
//
//              Functions to draw freely in 2D.
//
//----------------------------------------------------------

// Internal use. See NETexture.c

int __NE_TextureGetRawX(const NE_Material *tex);
int __NE_TextureGetRawY(const NE_Material *tex);

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

void NE_2DViewInit(void)
{
    GFX_VIEWPORT = 0 | (0 << 8) | (255 << 16) | (191 << 24);

    // The projection matrix actually thinks that the size of the DS is
    // (256 << factor) x (192 << factor). After this, we scale the MODELVIEW
    // matrix to match this scale factor.
    //
    // This way, it is possible to draw on the screen by using numbers up to 256
    // x 192, but internally the DS has more digits when it does transformations
    // like a rotation. Not having this factor results in noticeable flickering,
    // specially in some emulators.
    //
    // Unfortunately, applying this factor reduces the accuracy of the Y
    // coordinate a lot (nothing is noticeable in the X coordinate). Any factor
    // over 4 starts showing a noticeable accuracy loss: some sprites start
    // being slightly distorted, with missing some horizontal lines as the
    // height is reduced. When the number is higher, like 12, the Y coordinate
    // is significantly compressed. When the number is even higher, like 18, the
    // polygons disappear because too much accuracy has been lost.
    //
    // The current solution is to compromise, and use a factor of 2, which
    // doesn't cause any distortion, and solves most of the flickering. Ideally
    // we would use 0 to simplify the calculations, but we want to reduce the
    // flickering.
    //
    // On hardware, the difference in flickering between 0 and 2 isn't too
    // noticeable, but it is noticeable. In DeSmuMe it is very noticeable.
    // In my tests, Y axis distortion starts to happen with a factor of 4, so a
    // factor of 2 should be safe and reduce enough flickering.

    MATRIX_CONTROL = GL_PROJECTION;
    MATRIX_IDENTITY = 0;

    int factor = 2;

    glOrthof32(0, 256 << factor, 192 << factor, 0, inttof32(1), inttof32(-1));

    MATRIX_CONTROL = GL_MODELVIEW;
    MATRIX_IDENTITY = 0;

    MATRIX_SCALE = inttof32(1 << factor);
    MATRIX_SCALE = inttof32(1 << factor);
    MATRIX_SCALE = inttof32(1);

    NE_PolyFormat(31, 0, 0, NE_CULL_NONE, 0);
}

void NE_2DViewRotateScaleByPositionI(int x, int y, int rotz, int scale)
{
    NE_ViewMoveI(x, y, 0);

    MATRIX_SCALE = scale;
    MATRIX_SCALE = scale;
    MATRIX_SCALE = inttof32(1);

    glRotateZi(rotz << 6);

    NE_ViewMoveI(-x, -y, 0);
}

void NE_2DViewRotateByPosition(int x, int y, int rotz)
{
    NE_ViewMoveI(x, y, 0);

    glRotateZi(rotz << 6);

    NE_ViewMoveI(-x, -y, 0);
}

void NE_2DViewScaleByPositionI(int x, int y, int scale)
{
    NE_ViewMoveI(x, y, 0);

    MATRIX_SCALE = scale;
    MATRIX_SCALE = scale;
    MATRIX_SCALE = inttof32(1);

    NE_ViewMoveI(-x, -y, 0);
}

void NE_2DDrawQuad(s16 x1, s16 y1, s16 x2, s16 y2, s16 z, u32 color)
{
    GFX_BEGIN = GL_QUADS;

    GFX_TEX_FORMAT = 0;

    GFX_COLOR = color;

    GFX_VERTEX16 = (y1 << 16) | (x1 & 0xFFFF); // Up-left
    GFX_VERTEX16 = z;

    GFX_VERTEX_XY = (y2 << 16) | (x1 & 0xFFFF); // Down-left

    GFX_VERTEX_XY = (y2 << 16) | (x2 & 0xFFFF); // Down-right

    GFX_VERTEX_XY = (y1 << 16) | (x2 & 0xFFFF); // Up-right
}

void NE_2DDrawQuadGradient(s16 x1, s16 y1, s16 x2, s16 y2, s16 z, u32 color1,
                           u32 color2, u32 color3, u32 color4)
{
    GFX_BEGIN = GL_QUADS;

    GFX_TEX_FORMAT = 0;

    GFX_COLOR = color1;
    GFX_VERTEX16 = (y1 << 16) | (x1 & 0xFFFF); // Up-left
    GFX_VERTEX16 = z;

    GFX_COLOR = color4;
    GFX_VERTEX_XY = (y2 << 16) | (x1 & 0xFFFF); // Down-left

    GFX_COLOR = color3;
    GFX_VERTEX_XY = (y2 << 16) | (x2 & 0xFFFF); // Down-right

    GFX_COLOR = color2;
    GFX_VERTEX_XY = (y1 << 16) | (x2 & 0xFFFF); // Up-right
}

void NE_2DDrawTexturedQuad(s16 x1, s16 y1, s16 x2, s16 y2, s16 z,
                           const NE_Material *mat)
{
    NE_AssertPointer(mat, "NULL pointer");
    NE_Assert(mat->texindex != NE_NO_TEXTURE, "No texture");

    int x = NE_TextureGetSizeX(mat), y = NE_TextureGetSizeY(mat);

    NE_MaterialUse(mat);

    GFX_BEGIN = GL_QUADS;

    GFX_TEX_COORD = TEXTURE_PACK(0, 0);
    GFX_VERTEX16 = (y1 << 16) | (x1 & 0xFFFF); // Up-left
    GFX_VERTEX16 = z;

    GFX_TEX_COORD = TEXTURE_PACK(0, inttot16(y));
    GFX_VERTEX_XY = (y2 << 16) | (x1 & 0xFFFF); // Down-left

    GFX_TEX_COORD = TEXTURE_PACK(inttot16(x), inttot16(y));
    GFX_VERTEX_XY = (y2 << 16) | (x2 & 0xFFFF); // Down-right

    GFX_TEX_COORD = TEXTURE_PACK(inttot16(x), 0);
    GFX_VERTEX_XY = (y1 << 16) | (x2 & 0xFFFF); // Up-right
}

void NE_2DDrawTexturedQuadColor(s16 x1, s16 y1, s16 x2, s16 y2, s16 z,
                                const NE_Material *mat, u32 color)
{
    NE_AssertPointer(mat, "NULL pointer");
    NE_Assert(mat->texindex != NE_NO_TEXTURE, "No texture");

    int x = NE_TextureGetSizeX(mat), y = NE_TextureGetSizeY(mat);

    NE_MaterialUse(mat);

    GFX_COLOR = color;

    GFX_BEGIN = GL_QUADS;

    GFX_TEX_COORD = TEXTURE_PACK(0, 0);
    GFX_VERTEX16 = (y1 << 16) | (x1 & 0xFFFF); // Up-left
    GFX_VERTEX16 = z;

    GFX_TEX_COORD = TEXTURE_PACK(0, inttot16(y));
    GFX_VERTEX_XY = (y2 << 16) | (x1 & 0xFFFF); // Down-left

    GFX_TEX_COORD = TEXTURE_PACK(inttot16(x), inttot16(y));
    GFX_VERTEX_XY = (y2 << 16) | (x2 & 0xFFFF); // Down-right

    GFX_TEX_COORD = TEXTURE_PACK(inttot16(x), 0);
    GFX_VERTEX_XY = (y1 << 16) | (x2 & 0xFFFF); // Up-right
}

void NE_2DDrawTexturedQuadGradient(s16 x1, s16 y1, s16 x2, s16 y2, s16 z,
                                   const NE_Material *mat, u32 color1,
                                   u32 color2, u32 color3, u32 color4)
{
    NE_AssertPointer(mat, "NULL pointer");
    NE_Assert(mat->texindex != NE_NO_TEXTURE, "No texture");

    int x = NE_TextureGetSizeX(mat), y = NE_TextureGetSizeY(mat);

    NE_MaterialUse(mat);

    GFX_BEGIN = GL_QUADS;

    GFX_COLOR = color1;
    GFX_TEX_COORD = TEXTURE_PACK(0, 0);
    GFX_VERTEX16 = (y1 << 16) | (x1 & 0xFFFF); // Up-left
    GFX_VERTEX16 = z;

    GFX_COLOR = color4;
    GFX_TEX_COORD = TEXTURE_PACK(0, inttot16(y));
    GFX_VERTEX_XY = (y2 << 16) | (x1 & 0xFFFF); // Down-left

    GFX_COLOR = color3;
    GFX_TEX_COORD = TEXTURE_PACK(inttot16(x), inttot16(y));
    GFX_VERTEX_XY = (y2 << 16) | (x2 & 0xFFFF); // Down-right

    GFX_COLOR = color2;
    GFX_TEX_COORD = TEXTURE_PACK(inttot16(x), 0);
    GFX_VERTEX_XY = (y1 << 16) | (x2 & 0xFFFF); // Up-right
}

void NE_2DDrawTexturedQuadColorCanvas(s16 x1, s16 y1, s16 x2, s16 y2, s16 z,
                                      int tl, int tt, int tr, int tb,
                                      const NE_Material *mat, u32 color)
{
    NE_AssertPointer(mat, "NULL pointer");
    NE_Assert(mat->texindex != NE_NO_TEXTURE, "No texture");

    NE_MaterialUse(mat);

    GFX_COLOR = color;

    GFX_BEGIN = GL_QUADS;

    GFX_TEX_COORD = TEXTURE_PACK(inttot16(tl), inttot16(tt));
    GFX_VERTEX16 = (y1 << 16) | (x1 & 0xFFFF); // Up-left
    GFX_VERTEX16 = z;

    GFX_TEX_COORD = TEXTURE_PACK(inttot16(tl), inttot16(tb));
    GFX_VERTEX_XY = (y2 << 16) | (x1 & 0xFFFF); // Down-left

    GFX_TEX_COORD = TEXTURE_PACK(inttot16(tr), inttot16(tb));
    GFX_VERTEX_XY = (y2 << 16) | (x2 & 0xFFFF); // Down-right

    GFX_TEX_COORD = TEXTURE_PACK(inttot16(tr), inttot16(tt));
    GFX_VERTEX_XY = (y1 << 16) | (x2 & 0xFFFF); // Up-right
}
