/* *****************************************************************************
 * File:      app.c
 * Date:      20 Mar 2026
 * Author:    Andrew Levido
 *
 * Copyright 2026 Imbrius Pty Ltd - All Rights Reserved
 *
 ******************************************************************************/

/* Includes ------------------------------------------------------------------*/
#include "app.h"
#include "cli.h"
#include "main.h"
#include "screen.h"
#include "stm32l4xx_hal.h"
#include "stm32l4xx_hal_gpio.h"
#include "stm32l4xx_hal_pwr.h"
#include "stm32l4xx_hal_pwr_ex.h"
#include "usbd_cdc_if.h"
#include "usbd_core.h"
#include "cli_commands.h"
#include "graphics.h"
#include "analog.h"
#include "analyser.h"
#include "backlight.h"
#include "beep.h"

/* Defines -------------------------------------------------------------------*/
#define APP_MIN_VBAT                2000L     // 2.0V in mV
#define APP_CLI_TICKS               100       // 100 ms
#define APP_KEYSCAN_TICKS           10        // 10 ms

#define APP_DEBOUNCE_MASK           0b1111    // Check last four samples...
#define APP_DEBOUNCE_LO2HI          0b0111    // 1 low then 3 consecutive highs
#define APP_DEBOUNCE_HI2LO          0b1000    // 1 high then 3 consecutive lows

#define APP_TIMER_RESULT            15000L
#define APP_USB_ENUMERATE_DELAY     2000L

#define APP_BACKLIGHT_HI            20L

/* Typedefs & Enumerations ---------------------------------------------------*/
typedef struct {
  GPIO_TypeDef * port;
  uint16_t pin;
  volatile bool state;
  uint8_t debounce;
} pin_t;

/* Global Variables ----------------------------------------------------------*/

static struct {
  volatile bool usb_active;
  volatile uint32_t cli_tick_count;
  volatile bool time_out;
  volatile uint32_t timer_tick_count;
  volatile uint32_t keyscan_tick_count;
  pin_t start_button;
  pin_t stop_button;
} app;

const char version_string[] = "Version 1.a";

/* Private Functions ---------------------------------------------------------*/

// function called by CLI to write characters to the host via USB CDC device
static void cli_putc(char c) 
{
  if(app.usb_active == true) {
    while(CDC_Transmit_FS((uint8_t*)&c, 1) == USBD_BUSY);
  }
}

// function called in CDC_Receive_FS() in usbd_cdc_if.c
void app_usb_getc(char c)
{
  cli_getc(c);
}

// function called in HAL_PCD_ResumeCallback() in usbd_conf.c
void app_usb_resume(void)
{
  app.usb_active = true;
}

// function called in HAL_PCD_SuspendCallback() in usbd_conf.c
void app_usb_suspend(void)
{
 app.usb_active = false;
}

void app_sleep(void) 
{
  cli_write_line("\tSleeping");
  HAL_Delay (100);
  __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
  __HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);
  HAL_PWREx_EnableGPIOPullDown(PWR_GPIO_C, START_Pin);
  HAL_PWREx_EnablePullUpPullDownConfig();
  HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN2_HIGH);
  HAL_SuspendTick();
  HAL_PWREx_EnterSHUTDOWNMode();
}

void io_pin_debounce(pin_t * pindata)
{
  GPIO_PinState pinvalue;
  pinvalue = HAL_GPIO_ReadPin(pindata->port, pindata->pin);
  pindata->debounce = pindata->debounce << 1;
  pindata->debounce |= pinvalue == GPIO_PIN_SET ? 1 : 0;
  pindata->debounce &= APP_DEBOUNCE_MASK;

  if(pindata->debounce == APP_DEBOUNCE_LO2HI) {
    pindata->state = true;
  }
  else if(pindata->debounce == APP_DEBOUNCE_HI2LO) {
    pindata->state= false;
  }
}

void app_start_timer(uint32_t ticks_ms)
{
  app.time_out = false;
  app.timer_tick_count = ticks_ms;
}

void app_stop_timer(void)
{
  app.time_out = false;
  app.timer_tick_count = 0;
}

/* Public Functions ----------------------------------------------------------*/
void app_start(void) 
{
  screen_desc_t splash;

  // Initialse 
  app.cli_tick_count = APP_CLI_TICKS;
  app.keyscan_tick_count = APP_KEYSCAN_TICKS;
  app.start_button.port = START_GPIO_Port;
  app.start_button.pin = START_Pin;
  app.stop_button.port = STOP_GPIO_Port;
  app.stop_button.pin = STOP_Pin;

  // Power up regulators
  HAL_GPIO_WritePin(PWR_UP_GPIO_Port, PWR_UP_Pin, GPIO_PIN_SET);
  // Beep 
  beep(A5, BEEP_SHORT);
  // Initialse screen and show splash
  gfx_init(GFX_WHITE, GFX_BLACK, APP_BACKLIGHT_HI);

  if(HAL_GPIO_ReadPin(VBUS_GPIO_Port, VBUS_Pin) == GPIO_PIN_SET) {
    // Wait 2 seconds for USB to enumerate
  splash.screen = &scrn_splash;
  splash.string1_override = (char*)version_string;
  scrn_draw(&splash);
    HAL_Delay(APP_USB_ENUMERATE_DELAY);
  }

  // Initialise CLI & write version number
  cli_init(cli_putc); 
  cli_register_commands();
  cli_write_line("\n\nF9062 - Semiconductor Analyser");
  cli_printf("Firmware %s", version_string);
 
  // Initalise ADC driver and read analog voltage
  an_init();
 
  cli_printf("Vdda = %i mV", an_get_vdda_mv());

  // Start analysis loop
  while(1) {
    ax_analyse();
    if(app.usb_active != true) {
    app_start_timer(APP_TIMER_RESULT);
    }

    while(1)  { 
      if(app.start_button.state == true) { 
        beep(A5, BEEP_SHORT);
        break; }
      if((app.stop_button.state == true) || (app.time_out == true)) {
      beep(A5, BEEP_SHORT);
      app_sleep();
      }
    }
    app_stop_timer();
  } 
}

/* Interrupt Service Routines ------------------------------------------------*/
void app_tick_1ms(void)
{
  if(--app.cli_tick_count == 0) {
    app.cli_tick_count = APP_CLI_TICKS;
    cli_process();
  }
  if(--app.keyscan_tick_count == 0) {
    app.keyscan_tick_count = APP_KEYSCAN_TICKS;
    io_pin_debounce(&app.start_button);
    io_pin_debounce(&app.stop_button);
  }
  if(app.timer_tick_count > 0) {
    if(--app.timer_tick_count == 0) {
      app.time_out = true;
    }
  }
}
/* End app */