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

// This example sets up a horizontal blank interrupt handler and it uses it to
// change the horizontal scroll of a background by changing the scroll values
// after every line is drawn. This can be used to generate wave effects,
// noise...
//
// The 3D layer is displayed using video layer 0. That means the 3D output can
// also be scrolled with REG_BG0HOFS and REG_BG0VOFS. However, some emulators
// won't do a correct screen capture if the 3D output is scrolled while the
// capture takes place.

#include <stdio.h>

#include <nds.h>

// Headers autogenerated for each PNG file inside GFXDIRS in the Makefile
#include "forest_town.h"
#include "neon.h"

static int angle_offset = 0;

static void vblank_handler(void)
{
    // Setup scroll for line 0 before it starts getting drawn.
    int32_t value = sinLerp(degreesToAngle(angle_offset + 0)) >> 7;
    REG_BG0HOFS = value;
    REG_BG2HOFS = -value;
}

static void hblank_handler(void)
{
    // Don't run during the VBL period
    if (REG_VCOUNT >= 192)
        return;

    // REG_VCOUNT always keeps the current line being drawn. However, this
    // interrupt handler only starts getting called after the first line is
    // drawn, so we need to fix the scroll of line 0 in the VBL handler.
    int32_t value = sinLerp(degreesToAngle(angle_offset + REG_VCOUNT + 1)) >> 7;
    REG_BG0HOFS = value;
    REG_BG2HOFS = -value;

    // Note that writing to the scroll registers directly will overwrite the
    // values writen by the user with bgSetScroll() and bgUpdate().
}

int main(int argc, char **argv)
{
    // Enable 3D and sprites
    videoSetMode(MODE_0_3D);

    // Setup some VRAM as memory for main engine background and 3D textures.
    vramSetPrimaryBanks(VRAM_A_MAIN_BG, VRAM_B_LCD, VRAM_C_TEXTURE, VRAM_D_TEXTURE);

    // Setup console in the sub screen
    vramSetBankH(VRAM_H_SUB_BG);
    consoleDemoInit();

    // Setup 2D background
    // ===================

    int bg = bgInitHidden(2, BgType_Text8bpp, BgSize_T_256x256, 0, 4);
    bgSetPriority(bg, 2);

    memcpy(bgGetGfxPtr(bg), forest_townTiles, forest_townTilesLen);
    memcpy(bgGetMapPtr(bg), forest_townMap, forest_townMapLen);
    memcpy(BG_PALETTE, forest_townPal, forest_townPalLen);

    bgShow(bg);

    // Setup 3D
    // ========

    glInit();

    glEnable(GL_TEXTURE_2D);
    glEnable(GL_ANTIALIAS);

    // IMPORTANT: The 3D background must be transparent (alpha = 0) so that the
    // 2D layers behind it can be seen.
    glClearColor(0, 0, 0, 0);
    glClearPolyID(63);

    glClearDepth(0x7FFF);

    glViewport(0, 0, 255, 191);

    int textureID;

    // Load texture
    glGenTextures(1, &textureID);
    glBindTexture(0, textureID);
    if (glTexImageNtr2D(GL_RGBA, 128, 128, TEXGEN_TEXCOORD, neonBitmap, NULL) == 0)
    {
        printf("Failed to load texture\n");
        while (1)
            swiWaitForVBlank();
    }

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(70, 256.0 / 192.0, 0.1, 40);

    gluLookAt(0.0, 0.0, 2.0,  // Position
              0.0, 0.0, 0.0,  // Look at
              0.0, 1.0, 0.0); // Up

    // Start special efffect
    // =====================

    irqSet(IRQ_VBLANK, vblank_handler);
    irqSet(IRQ_HBLANK, hblank_handler);
    irqEnable(IRQ_HBLANK);

    // Setup done
    // ==========

    int angle_x = 0;
    int angle_z = 0;

    while (1)
    {
        // Synchronize game loop to the screen refresh
        swiWaitForVBlank();

        // Print some text in the demo console
        // -----------------------------------

        consoleClear();

        // Print some controls
        printf("PAD:   Rotate quad\n");
        printf("START: Exit to loader\n");
        printf("\n");

        // Handle user input
        // -----------------

        scanKeys();

        uint16_t keys = keysHeld();

        if (keys & KEY_LEFT)
            angle_z += 3;
        if (keys & KEY_RIGHT)
            angle_z -= 3;

        if (keys & KEY_UP)
            angle_x += 3;
        if (keys & KEY_DOWN)
            angle_x -= 3;

        if (keys & KEY_START)
            break;

        // Animate wave effect
        // -------------------

        angle_offset++;
        if (angle_offset >= degreesToAngle(360))
            angle_offset -= degreesToAngle(360);

        // Render 3D scene
        // ---------------

        glMatrixMode(GL_MODELVIEW);

        glPushMatrix();

        glRotateZ(angle_z);
        glRotateX(angle_x);

        glPolyFmt(POLY_ALPHA(31) | POLY_CULL_NONE);

        glBindTexture(0, textureID);

        glColor3f(1, 1, 1);

        glBegin(GL_QUADS);

            glTexCoord2t16(0, inttot16(128));
            glVertex3v16(floattov16(-1), floattov16(-1), 0);

            glTexCoord2t16(inttot16(128), inttot16(128));
            glVertex3v16(floattov16(1), floattov16(-1), 0);

            glTexCoord2t16(inttot16(128), 0);
            glVertex3v16(floattov16(1), floattov16(1), 0);

            glTexCoord2t16(0, 0);
            glVertex3v16(floattov16(-1), floattov16(1), 0);

        glEnd();

        glPopMatrix(1);

        glFlush(0);
    }

    glDeleteTextures(1, &textureID);

    return 0;
}

