/* Copyright (C) 2022-2023 Salvatore Sanfilippo -- All Rights Reserved
 * See the LICENSE file for information about the license. */

#include "app.h"
#include <cc1101.h>

static void direct_sampling_timer_start(ProtoViewApp* app);
static void direct_sampling_timer_stop(ProtoViewApp* app);

#define CAPTURED_BITMAP_BITS (128 * 64)
#define CAPTURED_BITMAP_BYTES (CAPTURED_BITMAP_BITS / 8)
#define DEFAULT_USEC_PER_PIXEL 50
#define USEC_PER_PIXEL_SMALL_CHANGE 5
#define USEC_PER_PIXEL_LARGE_CHANGE 25
#define USEC_PER_PIXEL_MIN 5
#define USEC_PER_PIXEL_MAX 300
typedef struct {
    uint8_t* captured; // Bitmap with the last captured screen.
    uint32_t captured_idx; // Current index to write into the bitmap
    uint32_t usec_per_pixel; // Number of useconds a pixel should represent
    bool show_usage_info;
} DirectSamplingViewPrivData;

/* Read directly from the G0 CC1101 pin, and draw a black or white
 * dot depending on the level. */
void render_view_direct_sampling(Canvas* const canvas, ProtoViewApp* app) {
    DirectSamplingViewPrivData* privdata = app->view_privdata;

    if(!app->direct_sampling_enabled && privdata->show_usage_info) {
        canvas_set_font(canvas, FontSecondary);
        canvas_draw_str(canvas, 2, 9, "Direct sampling displays the");
        canvas_draw_str(canvas, 2, 18, "the captured signal in real");
        canvas_draw_str(canvas, 2, 27, "time, like in a CRT TV set.");
        canvas_draw_str(canvas, 2, 36, "Use UP/DOWN to change the");
        canvas_draw_str(canvas, 2, 45, "resolution (usec/pixel).");
        canvas_set_font(canvas, FontPrimary);
        canvas_draw_str(canvas, 5, 60, "To start/stop, press OK");
        return;
    }
    privdata->show_usage_info = false;

    /* Draw on screen. */
    int idx = 0;
    for(int y = 0; y < 64; y++) {
        for(int x = 0; x < 128; x++) {
            bool level = bitmap_get(privdata->captured, CAPTURED_BITMAP_BYTES, idx++);
            if(level) canvas_draw_dot(canvas, x, y);
        }
    }

    char buf[32];
    snprintf(buf, sizeof(buf), "%lu usec/px", privdata->usec_per_pixel);
    canvas_set_font(canvas, FontSecondary);
    canvas_draw_str_with_border(canvas, 1, 60, buf, ColorWhite, ColorBlack);
}

/* Handle input */
void process_input_direct_sampling(ProtoViewApp* app, InputEvent input) {
    DirectSamplingViewPrivData* privdata = app->view_privdata;

    if(input.type == InputTypePress && input.key == InputKeyOk) {
        app->direct_sampling_enabled = !app->direct_sampling_enabled;
    }

    if((input.key == InputKeyUp || input.key == InputKeyDown) &&
       (input.type == InputTypePress || input.type == InputTypeRepeat)) {
        uint32_t change = input.type == InputTypePress ? USEC_PER_PIXEL_SMALL_CHANGE :
                                                         USEC_PER_PIXEL_LARGE_CHANGE;
        if(input.key == InputKeyUp) change = -change;
        privdata->usec_per_pixel += change;
        if(privdata->usec_per_pixel < USEC_PER_PIXEL_MIN)
            privdata->usec_per_pixel = USEC_PER_PIXEL_MIN;
        else if(privdata->usec_per_pixel > USEC_PER_PIXEL_MAX)
            privdata->usec_per_pixel = USEC_PER_PIXEL_MAX;
        /* Update the timer frequency. */
        direct_sampling_timer_stop(app);
        direct_sampling_timer_start(app);
    }
}

/* Enter view. Stop the subghz thread to prevent access as we read
 * the CC1101 data directly. */
void view_enter_direct_sampling(ProtoViewApp* app) {
    /* Set view defaults. */
    DirectSamplingViewPrivData* privdata = app->view_privdata;
    privdata->usec_per_pixel = DEFAULT_USEC_PER_PIXEL;
    privdata->captured = malloc(CAPTURED_BITMAP_BYTES);
    privdata->show_usage_info = true;

    if(app->txrx->txrx_state == TxRxStateRx && !app->txrx->debug_timer_sampling) {
        subghz_devices_stop_async_rx(app->radio_device);

        /* To read data asynchronously directly from the view, we need
         * to put the CC1101 back into reception mode (the previous call
         * to stop the async RX will put it into idle) and configure the
         * G0 pin for reading. */
        subghz_devices_set_rx(app->radio_device);
        furi_hal_gpio_init(
            subghz_devices_get_data_gpio(app->radio_device),
            GpioModeInput,
            GpioPullNo,
            GpioSpeedLow);
    } else {
        raw_sampling_worker_stop(app);
    }

    // Start the timer to capture raw data
    direct_sampling_timer_start(app);
}

/* Exit view. Restore the subghz thread. */
void view_exit_direct_sampling(ProtoViewApp* app) {
    DirectSamplingViewPrivData* privdata = app->view_privdata;
    if(privdata->captured) free(privdata->captured);
    app->direct_sampling_enabled = false;

    direct_sampling_timer_stop(app);

    /* Restart normal data feeding. */
    if(app->txrx->txrx_state == TxRxStateRx && !app->txrx->debug_timer_sampling) {
        subghz_devices_start_async_rx(app->radio_device, protoview_rx_callback, NULL);
    } else {
        furi_hal_gpio_init(
            subghz_devices_get_data_gpio(app->radio_device),
            GpioModeInput,
            GpioPullNo,
            GpioSpeedLow);
        raw_sampling_worker_start(app);
    }
}

/* =========================== Timer implementation ========================= */

static void ds_timer_isr(void* ctx) {
    ProtoViewApp* app = ctx;
    DirectSamplingViewPrivData* privdata = app->view_privdata;

    if(app->direct_sampling_enabled) {
        bool level = furi_hal_gpio_read(subghz_devices_get_data_gpio(app->radio_device));
        bitmap_set(privdata->captured, CAPTURED_BITMAP_BYTES, privdata->captured_idx, level);
        privdata->captured_idx = (privdata->captured_idx + 1) % CAPTURED_BITMAP_BITS;
    }
    LL_TIM_ClearFlag_UPDATE(TIM2);
}

static void direct_sampling_timer_start(ProtoViewApp* app) {
    DirectSamplingViewPrivData* privdata = app->view_privdata;

    furi_hal_bus_enable(FuriHalBusTIM2);

    LL_TIM_InitTypeDef tim_init = {
        .Prescaler = 63, /* CPU frequency is ~64Mhz. */
        .CounterMode = LL_TIM_COUNTERMODE_UP,
        .Autoreload = privdata->usec_per_pixel};

    LL_TIM_Init(TIM2, &tim_init);
    LL_TIM_SetClockSource(TIM2, LL_TIM_CLOCKSOURCE_INTERNAL);
    LL_TIM_DisableCounter(TIM2);
    LL_TIM_SetCounter(TIM2, 0);
    furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, ds_timer_isr, app);
    LL_TIM_EnableIT_UPDATE(TIM2);
    LL_TIM_EnableCounter(TIM2);
}

static void direct_sampling_timer_stop(ProtoViewApp* app) {
    UNUSED(app);
    FURI_CRITICAL_ENTER();
    LL_TIM_DisableCounter(TIM2);
    LL_TIM_DisableIT_UPDATE(TIM2);
    furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, NULL, NULL);
    furi_hal_bus_disable(FuriHalBusTIM2);
    FURI_CRITICAL_EXIT();
}