// SPDX-License-Identifier: CC0-1.0
//
// SPDX-FileContributor: Michele Di Giorgio, 2024
// SPDX-FileContributor: Antonio Niño Díaz, 2024
//
// Gesture recognition example that use the $N Multistroke Recognizer from:
//
//     Anthony, L. and Wobbrock, J.O. (2010). A lightweight multistroke
//     recognizer for user interface prototypes. Proceedings of Graphics
//     Interface (GI '10). Ottawa, Ontario (May 31-June 2, 2010). Toronto,
//     Ontario: Canadian Information Processing Society, pp. 245-252.
//     https://dl.acm.org/citation.cfm?id=1839258
//
//     Anthony, L. and Wobbrock, J.O. (2012). $N-Protractor: A fast and
//     accurate multistroke recognizer. Proceedings of Graphics Interface
//     (GI '12). Toronto, Ontario (May 28-30, 2012). Toronto, Ontario:
//     Canadian Information Processing Society, pp. 117-120.
//     https://dl.acm.org/citation.cfm?id=2305296

#include <gl2d.h>
#include <nds.h>

#include "ndollar.h"

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

#define SCREEN_WIDTH     256
#define SCREEN_HEIGHT    192
#define TOUCH_BOX_RADIUS 3

Point stroke[SCREEN_WIDTH*SCREEN_HEIGHT];
u16   screen[SCREEN_HEIGHT][SCREEN_WIDTH];

static int reference_bg;

void initBackground()
{
    reference_bg = bgInitHidden(2, BgType_Bmp16, BgSize_B16_256x256, 0, 0);
}

void showBackground()
{
    bgShow(reference_bg);
}

void hideBackground()
{
    bgHide(reference_bg);
}

void scrollBackground(int x, int y)
{
    uint16_t *fb = bgGetMapPtr(reference_bg);

    for (int j = 0; j < 104; j++)
    {
        for (int i = 0; i < 256; i++)
        {
            fb[256 * j + i] = ((uint16_t *)gesturesBitmap)[650 * (j + y) + i + x];
        }
    }
}

void printGestures()
{
    glBegin2D();

    // Draw a square on the bottom screen to highlight the stroke.
    glPolyFmt(POLY_ALPHA(31) | POLY_CULL_NONE | POLY_ID(0));

    for (u16 i = 0; i < SCREEN_HEIGHT; ++i) {
        for (u16 j = 0; j < SCREEN_WIDTH; ++j) {
            if (screen[i][j]) {
                glBoxFilled(
                    i - TOUCH_BOX_RADIUS,
                    j - TOUCH_BOX_RADIUS,
                    i + TOUCH_BOX_RADIUS,
                    j + TOUCH_BOX_RADIUS,
                    RGB8(146, 77, 200) | 0x8000
                );
            }
        }
    }
    glEnd2D();
}

void clearGestures()
{
    for (u16 i = 0; i < SCREEN_HEIGHT; ++i) {
        for (u16 j = 0; j < SCREEN_WIDTH; ++j) {
            screen[i][j] = 0;
        }
    }
}

int main(int argc, char **argv)
{
    // Initialize display:
    // - sub screen (top): console output
    // - main screen (bottom): GL2D graphics
    lcdSwap();

    videoSetMode(MODE_5_3D);

    vramSetPrimaryBanks(VRAM_A_MAIN_BG_0x06000000, VRAM_B_LCD,
                        VRAM_C_SUB_BG_0x06200000, VRAM_D_LCD);

    consoleDemoInit();

    glScreen2D();

    // Make the 3D layer transparent (alpha = 0) to see the reference image
    // under the 3D layer.
    glClearColor(0, 0, 0, 0);

    printf("Gesture recognition example\n");
    printf("===========================\n");
    printf("\n");
    printf("A: Recognize the current gesture\n");
    printf("B: Show/Hide reference images\n");
    printf("Left/Right: Scroll reference\n");
    printf("\n");
    printf("START: Return to loader\n");
    printf("\n");
    printf("Use touch screen to draw. The\n");
    printf("number of strokes has to be the\n");
    printf("same as in the image.\n");
    printf("\n");
    printf("\n");

    initBackground();

    touchPosition   touch_pos    = { 0, 0, 0, 0, 0, 0 };
    unsigned int    strokeLength = 0;
    NDRecognizer_s *ndrec        = createNDRecognizer();

    u16 clear = 1;
    clearGestures();

    showBackground();

    bool show_reference = true;
    int x = 0;
    scrollBackground(x, 0);

    while (1)
    {
        swiWaitForVBlank();

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

        // Handle key presses.
        if (keys_down & KEY_START) {
            break;
        }

        // If pen down, update touch input.
        if (keys_held & KEY_TOUCH)
        {
            if (clear) {
                clearGestures();
                clear = 0;
            }

            touchRead(&touch_pos);
            stroke[strokeLength].x = (double) touch_pos.px;
            stroke[strokeLength].y = (double) touch_pos.py;
            strokeLength++;

            screen[touch_pos.px][touch_pos.py] = 1;
        } else {
            if (strokeLength > 0) {
                addGesture(ndrec, stroke, strokeLength);
                strokeLength = 0;
            }
        }

        if (keys_down & KEY_B)
        {
            show_reference = !show_reference;
            if (show_reference)
                showBackground();
            else
                hideBackground();
        }

        if (keys_held & KEY_LEFT)
        {
            x -= 4;
            if (x < 0)
                x = 0;
            scrollBackground(x, 0);
        }
        else if (keys_held & KEY_RIGHT)
        {
            x += 4;
            if (x > (650 - 256))
                x = 650 - 256;
            scrollBackground(x, 0);
        }

        if (keys_down & KEY_A) {
            Result res = recognize(ndrec);
            printf("Result: %s (score: %.2f)\n", res.name, res.score);
            flushGestures(ndrec);
            clear = 1;
        }

        printGestures();
        glFlush(0);
    }

    destroyNDRecognizer(ndrec);

    return 0;
}

