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

// The direct FIFO display mode lets you setup a DMA channel to transfer the
// framebuffer to the video output directly from main RAM. This example shows
// how to use double buffering with this video mode.

#include <nds.h>

// This is autogenerated from photo.png and photo.grit
#include "photo.h"

// Align this to 4 bytes because we will use DMA to copy words from this buffer.
u16 framebuffer[2][256 * 192] ALIGN(4);

// Index of the framebuffer currently displayed.
volatile u8 framebuffer_displayed = 0;

// The main loop sets this to true when the next frame is available to be
// displayed.
volatile bool framebuffer_next_ready = false;

void vbl_handler(void)
{
    dmaStopSafe(3);

    // Switch the rendered buffer only when the main loop has finished drawing
    // the next frame.
    if (framebuffer_next_ready)
    {
        framebuffer_next_ready = false;
        framebuffer_displayed ^= 1;
    }

    void *fb = framebuffer[framebuffer_displayed];

    // The source address must be in main RAM.
    REG_DMA_SRC(3) = (uintptr_t)fb;
    // The destination must be REG_DISP_MMEM_FIFO.
    REG_DMA_DEST(3) = (uintptr_t)&REG_DISP_MMEM_FIFO;

    REG_DMA_CR(3) =
        DMA_DISP_FIFO | // Set direct FIFO display mode
        DMA_SRC_INC |   // Increment the source address each time
        DMA_DST_FIX |   // Fix the destination address to REG_DISP_MMEM_FIFO
        DMA_REPEAT |    // Don't stop after the first transfer
        DMA_COPY_WORDS | 4; // Copy 4 words each time (8 pixels)
}

int main(int argc, char *argv[])
{
    videoSetMode(MODE_FIFO);

    irqSet(IRQ_VBLANK, vbl_handler);

    // Show the demo console in the bottom screen
    consoleDemoInit();

    printf("PAD:   Move square\n");
    printf("START: Exit to loader\n");

    int x = 20, y = 30;

    while (1)
    {
        swiWaitForVBlank();

        // Draw a new frame only if the VBL handler has switched to the last
        // drawn buffer.
        if (!framebuffer_next_ready)
        {
            // Draw scene in the next buffer. Copy background image and draw a
            // square on top of it.
            u16 *fb = framebuffer[framebuffer_displayed ^ 1];

            memcpy(fb, photoBitmap, 256 * 192 * 2);

            for (int j = y; j < 50 + y; j++)
            {
                for (int i = x; i < 50 + x; i++)
                {
                    // Invert color, but ignore the top bit
                    fb[256 * j + i] ^= 0x7FFF;
                }
            }

            // Make sure that main RAM is updated
            DC_FlushRange(fb, 256 * 192 * 2);

            framebuffer_next_ready = true;
        }

        scanKeys();
        u16 keys_held = keysHeld();

        if (keys_held & KEY_UP)
            y--;
        else if (keys_held & KEY_DOWN)
            y++;

        if (keys_held & KEY_LEFT)
            x--;
        else if (keys_held & KEY_RIGHT)
            x++;

        if (keys_held & KEY_START)
            break;
    }

    return 0;
}
