/* Servicios del firmware que usaremos: núcleo, pantalla, GUI, vista y botones. */
#include <furi.h>
#include <gui/canvas.h>
#include <gui/gui.h>
#include <gui/view_port.h>
#include <input/input.h>

/* Funciones estándar de C: formar texto y reservar/liberar memoria. */
#include <stdio.h>
#include <stdlib.h>

/* La cola puede guardar hasta ocho pulsaciones pendientes. */
#define APP_FIT_QUEUE_SIZE 8

/* Una enumeración asigna nombres legibles a las tres pantallas posibles. */
typedef enum {
    AppFitScreenSplash,
    AppFitScreenCards,
    AppFitScreenTip,
} AppFitScreen;

/* Este struct es el molde de una tarjeta educativa. */
typedef struct {
    const char* title;
    const char* line1;
    const char* line2;
    const char* tip1;
    const char* tip2;
} AppFitTopic;

/* Estas son las cuatro tarjetas creadas con el molde anterior. */
static const AppFitTopic app_fit_topics[] = {
    {
        .title = "NFC",
        .line1 = "13.56 MHz",
        .line2 = "Tarjetas y telefonos",
        .tip1 = "Leer no significa",
        .tip2 = "clonar pagos.",
    },
    {
        .title = "RFID LF",
        .line1 = "125 kHz",
        .line2 = "Tags de proximidad",
        .tip1 = "NFC y RFID LF usan",
        .tip2 = "frecuencias distintas.",
    },
    {
        .title = "SUB-GHZ",
        .line1 = "Mandos y sensores",
        .line2 = "Depende de la region",
        .tip1 = "Codigo fijo no es",
        .tip2 = "rolling code.",
    },
    {
        .title = "GPIO",
        .line1 = "UART / SPI / I2C",
        .line2 = "Modulos externos",
        .tip1 = "Sus pines logicos",
        .tip2 = "trabajan a 3.3 V.",
    },
};

/* Este struct guarda el estado vivo de la aplicación mientras está abierta. */
typedef struct {
    FuriMessageQueue* input_queue;
    ViewPort* view_port;
    Gui* gui;
    AppFitScreen screen;
    uint8_t topic_index;
} AppFitXvii;

/* Dibuja la portada. No cambia datos ni lee botones. */
static void app_fit_draw_splash(Canvas* canvas) {
    canvas_set_font(canvas, FontPrimary);
    canvas_draw_str_aligned(canvas, 64, 14, AlignCenter, AlignBottom, "APP FIT-XVII");

    canvas_set_font(canvas, FontSecondary);
    canvas_draw_str_aligned(canvas, 64, 29, AlignCenter, AlignBottom, "Flipper Zero Lab");
    canvas_draw_str_aligned(canvas, 64, 43, AlignCenter, AlignBottom, "FIT XVII");
    canvas_draw_str_aligned(canvas, 64, 61, AlignCenter, AlignBottom, "OK: iniciar  Back: salir");
}

/* Dibuja la tarjeta seleccionada por topic_index. */
static void app_fit_draw_card(Canvas* canvas, const AppFitXvii* app) {
    const AppFitTopic* topic = &app_fit_topics[app->topic_index];
    char progress[20];
    snprintf(
        progress,
        sizeof(progress),
        "FIT-XVII %u/%u",
        (unsigned int)app->topic_index + 1,
        (unsigned int)COUNT_OF(app_fit_topics));

    canvas_set_font(canvas, FontSecondary);
    canvas_draw_str(canvas, 2, 9, progress);
    canvas_draw_line(canvas, 0, 12, 127, 12);

    canvas_set_font(canvas, FontPrimary);
    canvas_draw_str_aligned(canvas, 64, 27, AlignCenter, AlignBottom, topic->title);

    canvas_set_font(canvas, FontSecondary);
    canvas_draw_str_aligned(canvas, 64, 40, AlignCenter, AlignBottom, topic->line1);
    canvas_draw_str_aligned(canvas, 64, 51, AlignCenter, AlignBottom, topic->line2);
    canvas_draw_str_aligned(canvas, 64, 63, AlignCenter, AlignBottom, "Flechas: cambia  OK: dato");
}

/* Dibuja la explicación corta de la tarjeta actual. */
static void app_fit_draw_tip(Canvas* canvas, const AppFitXvii* app) {
    const AppFitTopic* topic = &app_fit_topics[app->topic_index];

    canvas_set_font(canvas, FontSecondary);
    canvas_draw_str(canvas, 2, 9, "DATO RAPIDO");
    canvas_draw_line(canvas, 0, 12, 127, 12);

    canvas_set_font(canvas, FontPrimary);
    canvas_draw_str_aligned(canvas, 64, 27, AlignCenter, AlignBottom, topic->title);

    canvas_set_font(canvas, FontSecondary);
    canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignBottom, topic->tip1);
    canvas_draw_str_aligned(canvas, 64, 52, AlignCenter, AlignBottom, topic->tip2);
    canvas_draw_str_aligned(canvas, 64, 63, AlignCenter, AlignBottom, "OK / Back: volver");
}

/* El firmware llama este callback cada vez que la vista debe redibujarse. */
static void app_fit_draw_callback(Canvas* canvas, void* context) {
    furi_assert(context);
    AppFitXvii* app = context;

    canvas_clear(canvas);
    canvas_draw_frame(canvas, 0, 0, 128, 64);

    if(app->screen == AppFitScreenSplash) {
        app_fit_draw_splash(canvas);
    } else if(app->screen == AppFitScreenCards) {
        app_fit_draw_card(canvas, app);
    } else {
        app_fit_draw_tip(canvas, app);
    }
}

/* El callback de entrada es breve: copia el evento a la cola y termina. */
static void app_fit_input_callback(InputEvent* input_event, void* context) {
    furi_assert(context);
    AppFitXvii* app = context;
    furi_message_queue_put(app->input_queue, input_event, 0);
}

/* Retrocede una tarjeta y vuelve al final si estamos en la primera. */
static void app_fit_previous_topic(AppFitXvii* app) {
    if(app->topic_index == 0) {
        app->topic_index = COUNT_OF(app_fit_topics) - 1;
    } else {
        app->topic_index--;
    }
}

/* Avanza una tarjeta; el operador % crea un recorrido circular. */
static void app_fit_next_topic(AppFitXvii* app) {
    app->topic_index = (app->topic_index + 1) % COUNT_OF(app_fit_topics);
}

/* entry_point del manifest: aquí nace, vive y termina la aplicación. */
int32_t app_fit_xvii_app(void* p) {
    UNUSED(p);

    /* 1. Reservar memoria e inicializar el estado. */
    AppFitXvii* app = malloc(sizeof(AppFitXvii));
    app->input_queue = furi_message_queue_alloc(APP_FIT_QUEUE_SIZE, sizeof(InputEvent));
    app->view_port = view_port_alloc();
    app->screen = AppFitScreenSplash;
    app->topic_index = 0;

    /* 2. Conectar nuestros callbacks con la vista del firmware. */
    view_port_draw_callback_set(app->view_port, app_fit_draw_callback, app);
    view_port_input_callback_set(app->view_port, app_fit_input_callback, app);

    app->gui = furi_record_open(RECORD_GUI);
    gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);

    /* 3. Procesar eventos hasta que Back cambie running a false. */
    bool running = true;
    InputEvent event;

    while(running) {
        if(furi_message_queue_get(app->input_queue, &event, FuriWaitForever) != FuriStatusOk) {
            continue;
        }

        const bool is_arrow = (event.key == InputKeyLeft) || (event.key == InputKeyRight) ||
                              (event.key == InputKeyUp) || (event.key == InputKeyDown);

        /* Aceptamos pulsación corta; las flechas también pueden repetirse. */
        if((event.type != InputTypeShort) && !((event.type == InputTypeRepeat) && is_arrow)) {
            continue;
        }

        if(app->screen == AppFitScreenSplash) {
            if(event.key == InputKeyOk) {
                app->screen = AppFitScreenCards;
            } else if(event.key == InputKeyBack) {
                running = false;
            }
        } else if(app->screen == AppFitScreenCards) {
            if((event.key == InputKeyLeft) || (event.key == InputKeyUp)) {
                app_fit_previous_topic(app);
            } else if((event.key == InputKeyRight) || (event.key == InputKeyDown)) {
                app_fit_next_topic(app);
            } else if(event.key == InputKeyOk) {
                app->screen = AppFitScreenTip;
            } else if(event.key == InputKeyBack) {
                app->screen = AppFitScreenSplash;
            }
        } else {
            if((event.key == InputKeyOk) || (event.key == InputKeyBack)) {
                app->screen = AppFitScreenCards;
            }
        }

        view_port_update(app->view_port);
    }

    /* 4. Liberar en orden inverso todo lo que reservamos. */
    view_port_enabled_set(app->view_port, false);
    gui_remove_view_port(app->gui, app->view_port);
    furi_record_close(RECORD_GUI);
    view_port_free(app->view_port);
    furi_message_queue_free(app->input_queue);
    free(app);

    return 0;
}
