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

// This example uses window 0 to create a circular mask that hides the
// background outside of it. It's inspired by the DMA demo example of Tonc, and
// it uses code adapted from this website to draw the circule:
//
// https://www.geeksforgeeks.org/dsa/mid-point-circle-drawing-algorithm/

#include <nds.h>

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

// Calculating the circle takes a bit of time. Instead of doing it in the
// VBL interrupt handler, we can calculate it in the main application loop,
// store the results in a backbuffer, and copy it to the live buffer right in
// the VBL interrupt.
static uint16_t win0_table_live[192];
static uint16_t win0_table_backbuffer[192];

static void circle_plot(int x1, int x2, int y)
{
    if ((y < 0) || (y >= 192))
        return;

    if (x1 < 0)
        x1 = 0;
    if (x2 < 0)
        x2 = 0;
    if (x1 > 255)
        x1 = 255;
    if (x2 > 255)
        x2 = 255;

    win0_table_backbuffer[y] = (x1 << 8) | x2;
}

static void calculate_circle(int center_x, int center_y, int radius)
{
    memset(win0_table_backbuffer, 0, sizeof(win0_table_backbuffer));

    // Fill tables
    int x = radius, y = 0;

    circle_plot(center_x - radius, center_x + radius, center_y);
    circle_plot(center_x, center_x, center_y + radius);
    circle_plot(center_x, center_x, center_y - radius);

    // Initialising the value of P
    int P = 1 - radius;
    while (x > y)
    {
        y++;

        if (P <= 0)
        {
            // Mid-point is inside or on the perimeter
            P = P + 2 * y + 1;
        }
        else
        {
            // Mid-point is outside the perimeter
            x--;
            P = P + 2 * y - 2 * x + 1;
        }

        // All the perimeter points have already been printed
        if (x < y)
            break;

        circle_plot(center_x - x, center_x + x, center_y + y);
        circle_plot(center_x - x, center_x + x, center_y - y);
        circle_plot(center_x - y, center_x + y, center_y + x);
        circle_plot(center_x - y, center_x + y, center_y - x);
    }
}

static void vblank_handler(void)
{
    // Stop the previous DMA copies
    dmaStopSafe(0);

    // Refresh the live buffer from the backbuffer
    memcpy(win0_table_live, win0_table_backbuffer, sizeof(win0_table_live));

    // Set the horizontal scroll for the first line. The first horizontal blank
    // happens after line 0 has been drawn, so we need to set the first bounds
    // of the window of line 0 now.
    REG_WIN0H = win0_table_live[0];
    REG_WIN0V = 0 << 8 | 192, // Allow all the vertical range

    // Make sure that DMA can see the updated values of the arrays and the
    // updated values don't stay in the data cache.
    DC_FlushRange(win0_table_live, sizeof(win0_table_live));

    // Restart the DMA copies
    dmaSetParams(0,
                 &win0_table_live[1], // Skip first entry (we have just used it)
                 (void *)&REG_WIN0H, // Write to window 0 register
                 DMA_SRC_INC | // Autoincrement source after each copy
                 DMA_DST_FIX | // Keep destination fixed
                 DMA_START_HBL | // Start copy at the start of horizontal blank
                 DMA_REPEAT | // Don't stop DMA after the first copy.
                 DMA_COPY_HALFWORDS | 1 | // Copy one halfword each time
                 DMA_ENABLE);
}

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

    vramSetPrimaryBanks(VRAM_A_MAIN_BG, VRAM_B_LCD, VRAM_C_LCD, VRAM_D_LCD);

    int bg = bgInitHidden(0, BgType_Text8bpp, BgSize_T_256x256, 0,1);

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

    bgShow(bg);

    // Make the backgdrop color black. This is the color that gets drawn on the
    // screen if no background or sprite is in that part of the screen. This
    // value is stored in BG_PALETTE[0], so it must be set after copying the
    // background palette.
    setBackdropColor(RGB15(0, 0, 0));

    // Setup window 0. In this example, window 0 will change every scanline to
    // form a circle. The background needs to only appear inside the window, not
    // outside.
    bgWindowEnable(bg, WINDOW_0);
    bgWindowDisable(bg, WINDOW_OUT);
    windowEnable(WINDOW_0);

    // Start interrupt handler
    irqSet(IRQ_VBLANK, vblank_handler);

    // Print instructions
    consoleDemoInit();

    printf("PAD:   Move circle\n");
    printf("A/B:   Change radius of circle\n");
    printf("START: Exit to loader\n");

    int center_x = 256 / 2;
    int center_y = 192 / 2;
    int radius = 60;

    while (1)
    {
        swiWaitForVBlank();

        calculate_circle(center_x, center_y, radius);

        scanKeys();

        u16 keys_held = keysHeld();

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

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

        if ((keys_held & KEY_B) && (radius > 0))
            radius--;
        else if ((keys_held & KEY_A) && (radius < 500))
            radius++;

        if (keys_held & KEY_START)
            break;
    }

    return 0;
}
