// SPDX-License-Identifier: CC0-1.0
//
// SPDX-FileContributor: Antonio Niño Díaz, 2024-2025

// This example shows how antialiasing and outline work. They both work in a
// similar way (affecting the polygon edges) and they both interact when they
// are enabled at the same time (which makes outline look bad).
//
// Note that this example doesn't work well in emulators. It works on hardware
// and melonDS.
//
// Also, GBATEK mentions that polygon IDs are used to decide whether a the
// hardware does antialiasing between polygons or not. This is incorrect, the
// hardware doesn't use the polygon ID for antialiasing.

#include <stdlib.h>

#include <nds.h>
#include <nds/arm9/postest.h>

// Headers autogenerated from the files in BINDIRS in the Makefile
#include "sphere_bin.h"

typedef struct {
    int32_t x, y, z;
} BallInfo;

#define NUM_BALLS 10

BallInfo Ball[NUM_BALLS];

void balls_initialize(void)
{
    for (int i = 0; i < NUM_BALLS; i++)
    {
        // Random coordinates from -4 to +4 so that they fit in a v16
        Ball[i].x = (rand() & (inttof32(8) - 1)) - inttof32(4);
        Ball[i].y = (rand() & (inttof32(8) - 1)) - inttof32(4);
        Ball[i].z = (rand() & (inttof32(8) - 1)) - inttof32(4);
    }
}

void balls_draw(bool different_ids)
{
    for (int i = 0; i < NUM_BALLS; i++)
    {
        glPushMatrix();
        glTranslatef32(Ball[i].x, Ball[i].y, Ball[i].z);

        // Each ball needs a different polygon ID for antialiasing to work
        int id;
        if (different_ids)
            id = 0 + i * 4;
        else
            id = 0;

        glPolyFmt(POLY_ALPHA(31) | POLY_CULL_BACK | POLY_FORMAT_LIGHT0 | POLY_ID(id));
        glCallList(sphere_bin);

        glPopMatrix(1);
    }
}

int main(int argc, char *argv[])
{
    // Initialize GL
    glInit();

    consoleDemoInit();

    // Set mode 0, enable BG0 and set it to 3D
    videoSetMode(MODE_0_3D);

    // Set BG to black. Alpha must not be zero for fog to affect the rear plane
    glClearColor(0, 0, 0, 31);
    glClearPolyID(63);
    glClearDepth(0x7FFF);

    // Set the viewport to fullscreen
    glViewport(0, 0, 255, 191);

    // Setup the light
    glLight(0, RGB15(31, 31, 31), 0, 0, floattov10(-1.0));

    // Set outline colors
    glSetOutlineColor(0, RGB15(31, 0, 0));
    glSetOutlineColor(1, RGB15(0, 31, 0));
    glSetOutlineColor(2, RGB15(0, 0, 31));
    glSetOutlineColor(3, RGB15(31, 31, 0));
    glSetOutlineColor(4, RGB15(0, 31, 31));

    glSetOutlineColor(7, RGB15(31, 0, 31));

    // Setup some material properties
    glMaterialf(GL_AMBIENT, RGB15(8, 8, 8));
    glMaterialf(GL_DIFFUSE, RGB15(24, 24, 24));
    glMaterialf(GL_SPECULAR, RGB15(0, 0, 0));
    glMaterialf(GL_EMISSION, RGB15(0, 0, 0));

    // Setup the projection matrix for regular drawing
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60, 256.0 / 192.0, 0.1, 20);

    // Use the modelview matrix while drawing
    glMatrixMode(GL_MODELVIEW);

    balls_initialize();

    u32 rotateX = 0;
    u32 rotateY = 0;

    bool antialias = true;
    bool outline = false;
    int clear_color_id = 63;

    while (1)
    {
        consoleClear();

        printf("PAD: Rotate\n");
        printf("A:   Toggle antialias: %s\n", antialias ? "On" : "Off");
        printf("B:   Toggle outline: %s\n", outline ? "On" : "Off");
        printf("X:   Clear color ID: %d\n", clear_color_id);
        printf("\n");
        printf("START: Exit to loader");

        // Handle key input
        scanKeys();
        u16 keys_held = keysHeld();
        u16 keys_down = keysDown();

        // Rotate balls
        if (keys_held & KEY_UP)
            rotateX += 3 << 5;
        if (keys_held & KEY_DOWN)
            rotateX -= 3 << 5;
        if (keys_held & KEY_LEFT)
            rotateY += 3 << 5;
        if (keys_held & KEY_RIGHT)
            rotateY -= 3 << 5;

        if (keys_down & KEY_A)
            antialias = !antialias;
        if (keys_down & KEY_B)
            outline = !outline;

        if (keys_down & KEY_X)
        {
            if (clear_color_id == 0)
                clear_color_id = 63;
            else
                clear_color_id = 0;
        }

        // Draw 3D scene
        // -------------

        if (outline)
            glEnable(GL_OUTLINE);
        else
            glDisable(GL_OUTLINE);

        if (antialias)
            glEnable(GL_ANTIALIAS);
        else
            glDisable(GL_ANTIALIAS);

        glClearPolyID(clear_color_id);

        // Save the state of the current matrix(the modelview matrix)
        glLoadIdentity();

        // Setup the camera
        gluLookAt(0.0, 0.0, 8.0, // Camera position
                  0.0, 0.0, 0.0,  // Look at
                  0.0, 1.0, 0.0); // Up

        glRotateXi(rotateX);
        glRotateYi(rotateY);

        // Draw the scene

        bool different_ids;
        if (keys_held & KEY_B)
            different_ids = false;
        else
            different_ids = true;

        balls_draw(different_ids);

        glFlush(0);

        swiWaitForVBlank();

        if (keys_held & KEY_START)
            break;
    }

    return 0;
}
