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

// The 2D hardware of the DS supports the same "blending modes" as the GBA. It
// can blend two targets, fade one target to white, or fade one target to black.
//
// A target is a combination of layers (BG0, BG1, BG2, BG3, OBJ, backdrop). For
// example, for alpha, you select a combination of layers that are on top of
// another combination of layers. The ones on top are the source, or first
// target. The ones at the bottom are the destination, or second target.
//
// Sprites can be defined as semi-transparent in OAM or by selecting the OAM
// layer as first target of REG_BLDCNT.
//
// When they are marked as semi-transparent in OAM, the blending effect has
// priority over the setting in REG_BLDCNT, and it is always enabled.
//
// When they are marked as first target in REG_BLDCNT, the mode of REG_BLDCNT
// is the one that affects the effect.
//
// Note that it isn't possible to blend two sprites with this system. The sprite
// layer is computed before the effects happen, so only the sprite with the
// highest priority (with its selected effect) is seen.

#include <stdio.h>

#include <nds.h>

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

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

    vramSetPrimaryBanks(VRAM_A_MAIN_BG, VRAM_B_MAIN_SPRITE,
                        VRAM_C_SUB_BG, VRAM_D_LCD);

    // Setup console in the sub screen
    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 2D sprites
    // ================

    // Note that we need to use the right sprite mapping for bitmap sprites
    oamInit(&oamMain, SpriteMapping_Bmp_1D_128, false);

    oamEnable(&oamMain);

    u16 *gfxMainTiled;
    {
        // Allocate space for the tiles and copy them there
        gfxMainTiled = oamAllocateGfx(&oamMain, SpriteSize_64x64, SpriteColorFormat_256Color);
        memcpy(gfxMainTiled, statueTiles, statueTilesLen);

        // Copy palette
        memcpy(SPRITE_PALETTE, statuePal, statuePalLen);

        oamSet(&oamMain, 0,
               100, 50, // X, Y
               0, // Priority
               0, // Palette index
               SpriteSize_64x64, SpriteColorFormat_256Color, // Size, format
               gfxMainTiled, // Graphics offset
               -1, // Affine index
               false, // Double size
               false, // Hide
               false, false, // H flip, V flip
               false); // Mosaic

        oamSet(&oamMain, 1,
               150, 70, // X, Y
               0, 0, SpriteSize_64x64, SpriteColorFormat_256Color, gfxMainTiled,
               -1, false, false, false, false, false);
    }

    u16 *gfxMainBmp;
    {
        // Allocate space for the bitmap and copy it there
        gfxMainBmp = oamAllocateGfx(&oamMain, SpriteSize_64x64, SpriteColorFormat_Bmp);
        memcpy(gfxMainBmp, photoBitmap, photoBitmapLen);

        oamSet(&oamMain, 2,
               20, 70, // X, Y
               0, // Priority
               15, // Palette index, but it is the alpha value of bitmap sprites
               SpriteSize_64x64, SpriteColorFormat_Bmp, // Size, format
               gfxMainBmp,  // Graphics offset
               -1, // Affine index
               false, // Double size
               false, // Hide
               false, false, // H flip, V flip
               false); // Mosaic
    }

    // Force alpha blending mode for sprite 1. This sprite will ignore the mode
    // setting in REG_BLDCNT.
    oamSetBlendMode(&oamMain, 1, SpriteMode_Blended);

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

    int spr_x = 100, spr_y = 50;

    int eva = 5;
    int evb = 8;
    int evy = 10;
    int spr_alpha = 10;

    int mode = 2;

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

        bgUpdate();

        oamSetXY(&oamMain, 0, spr_x, spr_y);

        oamSetAlpha(&oamMain, 2, spr_alpha);

        oamUpdate(&oamMain);

        // Handle effects
        // --------------

        unsigned int bld_modes[4] = {
            BLEND_NONE, BLEND_ALPHA, BLEND_FADE_WHITE, BLEND_FADE_BLACK
        };
        const char *bld_modes_str[4] = {
            "None", "Alpha", "Fade white", "Fade black"
        };

        REG_BLDCNT = bld_modes[mode & 3]
                   | BLEND_SRC_SPRITE
                   | BLEND_DST_BG2
                   | BLEND_DST_BACKDROP;

        REG_BLDALPHA = BLDALPHA_EVA(eva) | BLDALPHA_EVB(evb);
        REG_BLDY = BLDY_EVY(evy);

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

        consoleClear();

        // Print some controls
        printf("PAD:     Move sprite\n");
        printf("\n");
        printf("A + L/R: EVA:  %d\n", eva);
        printf("B + L/R: EVB:  %d\n", evb);
        printf("Y + L/R: EVY:  %d\n", evy);
        printf("SELECT:  Mode: %s\n", bld_modes_str[mode]);
        printf("\n");
        printf("X + L/R: BMP alpha:  %d\n", spr_alpha);
        printf("\n");
        printf("START:   Exit to loader\n");
        printf("\n");

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

        scanKeys();

        uint16_t keys_held = keysHeld();
        uint16_t keys_down = keysDown();

        if (keys_held & KEY_UP)
            spr_y--;
        else if (keys_held & KEY_DOWN)
            spr_y++;
        if (keys_held & KEY_LEFT)
            spr_x--;
        else if (keys_held & KEY_RIGHT)
            spr_x++;

        if (keys_held & KEY_A)
        {
            if ((keys_down & KEY_L) && (eva > 0))
                eva--;
            else if ((keys_down & KEY_R) && (eva < 16))
                eva++;
        }

        if (keys_held & KEY_B)
        {
            if ((keys_down & KEY_L) && (evb > 0))
                evb--;
            else if ((keys_down & KEY_R) && (evb < 16))
                evb++;
        }

        if (keys_held & KEY_Y)
        {
            if ((keys_down & KEY_L) && (evy > 0))
                evy--;
            else if ((keys_down & KEY_R) && (evy < 16))
                evy++;
        }

        if (keys_held & KEY_X)
        {
            if ((keys_down & KEY_L) && (spr_alpha > 0))
                spr_alpha--;
            else if ((keys_down & KEY_R) && (spr_alpha < 15)) // Not 16!
                spr_alpha++;
        }

        if (keys_down & KEY_SELECT)
            mode = (mode + 1) & 3;

        if (keys_held & KEY_START)
            break;
    }

    oamFreeGfx(&oamMain, gfxMainTiled);
    oamFreeGfx(&oamMain, gfxMainBmp);

    return 0;
}

