/**
 * @file    app.c
 * @brief   Application module
 * @author  Kenny Tran
 * @date    May 05 2023
 * @version 0.0.1
 *
 * @section LICENSES
 * Copyright (C) Morgan Advanced Programmable Systems, Inc.
 */

/*--------------------------------------------------------------------------------------------------
 * INCLUDE FILES
 *------------------------------------------------------------------------------------------------*/
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>

#include <zephyr/irq.h>

#include "../lib/errno.h"
#include "../lib/print.h"
#include "../lib/fs.h"

#include "../bsp/als.h"
#include "../bsp/battery.h"
#include "../bsp/button.h"
#include "../bsp/imu.h"
#include "../bsp/led_ring.h"
#include "../bsp/led.h"
#include "../bsp/mcu.h"
#include "../bsp/nvm_ext.h"
#include "../bsp/power.h"
#include "../bsp/rtc.h"
#include "../bsp/wdt.h"

#include "app_cli.h"
#include "ble.h"
#include "bubble.h"
#include "fiber.h"
#include "fsm.h"
#include "event.h"
#include "light_sensor.h"
#include "motion.h"
#include "pin.h"
#include "scope_ring.h"
#include "shot_storage.h"
#include "shot_capture.h"

#include "app.h"


/*--------------------------------------------------------------------------------------------------
 * MACROS & CONSTANTS
 *------------------------------------------------------------------------------------------------*/


/*--------------------------------------------------------------------------------------------------
 * TYPE DEFINITIONS, STRUCTURES & ENUMERATIONS
 *------------------------------------------------------------------------------------------------*/


/*--------------------------------------------------------------------------------------------------
 * PRIVATE CONSTANTS & VARIABLES
 *------------------------------------------------------------------------------------------------*/
static FS_file_t  _app_file;
static APP_info_s _app_info = (APP_info_s) {
    .main_pcb_version = { 1, 0, 0 },
    .ring_pcb_version = { 1, 0, 0 },
    .version          = { APP_MAJOR, APP_MINOR, APP_BUILD },
    .mfg_id           = 888000000,
    .mfg_date         = 20251215,
    .pn               = APP_PN,
    .sn               = APP_SN,
    .power_cnt        = 0,
    .cli_enabled      = (uint8_t) true,
    .imu_temp_offset  = 25
};


/*-------------------------------------------------------------------------------------------------
 * PRIVATE FUNCTION PROTOTYPES
 *-----------------------------------------------------------------------------------------------*/
static void _app_init (void);
static void _app_cmd_register (void);
static void _app_cli_cmd (int32_t argc, char **argv);
static void _app_info_read (void);
static void _app_info_write (void);


/*--------------------------------------------------------------------------------------------------
 * PUBLIC FUNCTION DEFINITIONS
 *------------------------------------------------------------------------------------------------*/
void APP_init (void)
{
    volatile uint32_t irq_key = irq_lock ();

    /* BSP */
    (void) MCU_init ();
    
    /* Initialize filesystem for file IO. */
    (void) FS_init ();
    
    /* Initialize and start watchdog. */
    (void) WDT_init ();

    /* Fetch application info stored. */
    _app_info_read ();
    /* Update power count. */
    _app_info.power_cnt++;
    /* Write updates to flash. */
    _app_info_write ();

    /* Initialize app modules. */
    _app_init ();

    /* Register app command. */
    if (_app_info.cli_enabled)
    {
        _app_cmd_register ();
    }

    (void) irq_unlock (irq_key);
}

void APP_start (void)
{
    /* Install application tasks */
    if (_app_info.cli_enabled)
    {
        APP_CLI_task_install ();
    }

    EVENT_task_install ();
    MOTION_task_install ();
    SCOPE_RING_task_install ();
    FSM_task_install ();

    /* README: Not used in initial release. */
    SHOT_CAPTURE_task_install();
    SHOT_STORAGE_task_install();

    /* Install BSP tasks */
    IMU_task_install ();
    IMU_interrupt_handler_task_install ();
}

void APP_reset (void)
{
    MCU_reset (true);
}

void APP_cli_enable (bool en)
{
    _app_info.cli_enabled = (uint8_t) en;

    /* Write updates to flash. */
    _app_info_write ();

    if (MCU_reset (true) != ERRNO_SUCCESS)
    {
        PRINT (INFO, "Failure to reset.\r\n");
    }
}

void APP_factory_reset (void)
{
    /* Get current application info to preserve after erasing flash. */
    APP_info_s info = APP_info_get ();
    /* Get motion calibration info. */
    MOTION_config_s *p_motion_cfg = MOTION_config_get ();

    PRINT (INFO, "Performing factory reset...\r\n");
    
    /* Erase entire flash. */
    if (NVM_EXT_erase_all () != ERRNO_SUCCESS)
    {
        PRINT (INFO, "Failure to erase flash.\r\n");
    }

    /* Restore application info that should be preserved. */
    _app_info = info;

    /* Update app version. */
    _app_info.version.major = APP_MAJOR;
    _app_info.version.minor = APP_MINOR;
    _app_info.version.build = APP_BUILD;
    /* Clear power count. */
    _app_info.power_cnt = 0;

    /* With flash erased, calling _app_info_read should find no file and write a new file
       with current info structure. */
    _app_info_read ();

    /* Set back calibration info so we don't lose it. */
    MOTION_config_set (*p_motion_cfg);

    if (MCU_reset (true) != ERRNO_SUCCESS)
    {
        PRINT (INFO, "Failure to reset.\r\n");
    }
}

void APP_temp_calibrate (float current_temp)
{
    float temp = 0.0;

    if (IMU_temp_c_read (&temp) != ERRNO_SUCCESS)
    {
        PRINT (ERROR, "Failure to read IMU temp sensor.\r\n");
        return;
    }

    /* Update offset and save to flash. */
    _app_info.imu_temp_offset = (current_temp - temp);

    _app_info_write ();
}

float APP_temp_c_get (void)
{
    float temp = 0.0;

    if (IMU_temp_c_read (&temp))
    {
        PRINT (ERROR, "Failure to read IMU temp sensor.\r\n");
    }

    return (temp + _app_info.imu_temp_offset);
}

APP_info_s APP_info_get (void)
{
    return _app_info;
}


/*-------------------------------------------------------------------------------------------------
 * PRIVATE FUNCTION DEFINITIONS
 *-----------------------------------------------------------------------------------------------*/
static void _app_init (void)
{
    /* Application */
    if (_app_info.cli_enabled)
    {
        APP_CLI_init ();
    }
    else
    {
        /* Disable PRINT. */
        PRINT_level_set (DISABLED);
    }

    BLE_init ();
    BUBBLE_init ();
    FIBER_init ();
    FSM_init ();
    EVENT_init ();
    LIGHT_SENSOR_init ();
    MOTION_init ();
    PIN_init ();
    SCOPE_RING_init ();

    /* README: Not used in initial release. */
    SHOT_STORAGE_init();
    SHOT_CAPTURE_init();
}

static void _app_cmd_register (void)
{
    /* APP */
    CLI_cmd_register (CLI_CMD_APP, (char *) "app",          _app_cli_cmd);
    CLI_cmd_register (CLI_CMD_APP, (char *) "ble",          BLE_cli_cmd);
    CLI_cmd_register (CLI_CMD_APP, (char *) "bubble",       BUBBLE_cli_cmd);
    CLI_cmd_register (CLI_CMD_APP, (char *) "fiber",        FIBER_cli_cmd);
    CLI_cmd_register (CLI_CMD_APP, (char *) "fsm",          FSM_cli_cmd);
    CLI_cmd_register (CLI_CMD_APP, (char *) "event",        EVENT_cli_cmd);
    CLI_cmd_register (CLI_CMD_APP, (char *) "light_sensor", LIGHT_SENSOR_cli_cmd);
    CLI_cmd_register (CLI_CMD_APP, (char *) "motion",       MOTION_cli_cmd);
    CLI_cmd_register (CLI_CMD_APP, (char *) "pin",          PIN_cli_cmd);
    CLI_cmd_register (CLI_CMD_APP, (char *) "scope_ring",   SCOPE_RING_cli_cmd);
    /* README: Not used in initial release. */
    CLI_cmd_register (CLI_CMD_APP, (char *) "ss",           SHOT_STORAGE_cli_cmd);
    CLI_cmd_register (CLI_CMD_APP, (char *) "sc",           SHOT_CAPTURE_cli_cmd);

    /* BSP */
    CLI_cmd_register (CLI_CMD_DEBUG, (char *) "als",        ALS_cli_cmd);
    CLI_cmd_register (CLI_CMD_DEBUG, (char *) "battery",    BATTERY_cli_cmd);
    CLI_cmd_register (CLI_CMD_DEBUG, (char *) "button",     BUTTON_cli_cmd);
    CLI_cmd_register (CLI_CMD_DEBUG, (char *) "imu",        IMU_cli_cmd);
    CLI_cmd_register (CLI_CMD_DEBUG, (char *) "led_ring",   LED_RING_cli_cmd);
    CLI_cmd_register (CLI_CMD_DEBUG, (char *) "led",        LED_cli_cmd);
    CLI_cmd_register (CLI_CMD_DEBUG, (char *) "mcu",        MCU_cli_cmd);
    CLI_cmd_register (CLI_CMD_DEBUG, (char *) "nvm",        NVM_EXT_cli_cmd);
    CLI_cmd_register (CLI_CMD_DEBUG, (char *) "power",      POWER_cli_cmd);
    CLI_cmd_register (CLI_CMD_DEBUG, (char *) "rtc",        RTC_cli_cmd);
    CLI_cmd_register (CLI_CMD_DEBUG, (char *) "wdt",        WDT_cli_cmd);

    /* LIB */
    CLI_cmd_register (CLI_CMD_DEBUG, (char *) "errno",      ERRNO_cli_cmd);
    CLI_cmd_register (CLI_CMD_DEBUG, (char *) "fs",         FS_cli_cmd);
    CLI_cmd_register (CLI_CMD_DEBUG, (char *) "print",      PRINT_cli_cmd);
}

static void _app_cli_cmd (int32_t argc, char **argv)
{
    if (argc == 0)
    {
        PRINT (INFO, "APP\r\n");
        PRINT (INFO, "-------------------------------------------\r\n");
        PRINT (INFO, "Main PCB Version : %d.%d.%d\r\n", _app_info.main_pcb_version.major,
                                                        _app_info.main_pcb_version.minor,
                                                        _app_info.main_pcb_version.build);
        PRINT (INFO, "Ring PCB Version : %d.%d.%d\r\n", _app_info.ring_pcb_version.major,
                                                        _app_info.ring_pcb_version.minor,
                                                        _app_info.ring_pcb_version.build);
        PRINT (INFO, "Build Version    : %d.%d.%d\r\n", _app_info.version.major,
                                                        _app_info.version.minor,
                                                        _app_info.version.build);
        PRINT (INFO, "Build DTS        : %s %s\r\n", APP_BUILD_DATE, APP_BUILD_TIME);
        PRINT (INFO, "Part Number      : %s\r\n", _app_info.pn);
        PRINT (INFO, "Serial Number    : %s\r\n", _app_info.sn);
        PRINT (INFO, "MFG ID           : %d\r\n", _app_info.mfg_id);
        PRINT (INFO, "MFG Date         : %d\r\n", _app_info.mfg_date);
        PRINT (INFO, "Power On Count   : %d\r\n", _app_info.power_cnt);
    }
    else if (argc == 2)
    {
        if (!strcmp (argv[0], "-cli"))
        {
            if (!strcmp (argv[1], "true"))
            {
                APP_cli_enable (true);
            }
            else if (!strcmp (argv[1], "false"))
            {
                APP_cli_enable (false);
            }
            else
            {
                goto USAGE;
            }
        }
        else if (!strcmp (argv[0], "-mfgid"))
        {
            _app_info.mfg_id = strtoul (argv[1], NULL, 0);

            /* Write changes to flash. */
            _app_info_write ();
        }
        else if (!strcmp (argv[0], "-mfgdate"))
        {
            _app_info.mfg_date = strtoul (argv[1], NULL, 0);

            /* Write changes to flash. */
            _app_info_write ();
        }
        else if (!strcmp (argv[0], "-pn"))
        {
            uint8_t len = strlen(argv[1]);

            /* -1 for null termination. */
            if (len > (sizeof (_app_info.pn)-1))
            {
                PRINT (INFO, "Max length of %d characters!\r\n", (sizeof (_app_info.pn)-1));
                return;
            }

            memset (_app_info.pn, 0, sizeof (_app_info.pn));
            memcpy (_app_info.pn, argv[1], len);

            /* Write changes to flash. */
            _app_info_write ();
        }
        else if (!strcmp (argv[0], "-sn"))
        {
            char    sn[sizeof (_app_info.sn)];
            uint8_t len = strlen(argv[1]);

            /* -4 for null termination and "CS_" prefix for BLE. */
            if (len > (sizeof (_app_info.sn)-4))
            {
                PRINT (INFO, "Max length of %d characters!\r\n", (sizeof (_app_info.pn)-4));
                return;
            }

            memset (_app_info.sn, 0, sizeof (_app_info.sn));
            memcpy (_app_info.sn, argv[1], len);

            /* Write changes to flash. */
            _app_info_write ();

            /* Setup initial advertising name to prefix + sn. */
            strcpy (sn, BLE_DEVICE_NAME_PREFIX);
            strcat (sn, _app_info.sn);

            BLE_adv_name_set (sn);
        }
        else if (!strcmp (argv[0], "-tc"))
        {
            float offset = strtof (argv[1], NULL);

            APP_temp_calibrate (offset);
        }
        else
        {
            goto USAGE;
        }
    }
    else if (argc == 4)
    {
        if (!strcmp (argv[0], "-mpcb"))
        {
            _app_info.main_pcb_version.major = (uint8_t) strtoul (argv[1], NULL, 0);
            _app_info.main_pcb_version.minor = (uint8_t) strtoul (argv[2], NULL, 0);
            _app_info.main_pcb_version.build = (uint8_t) strtoul (argv[3], NULL, 0);

            /* Write changes to flash. */
            _app_info_write ();
        }
        else if (!strcmp (argv[0], "-rpcb"))
        {
            _app_info.ring_pcb_version.major = (uint8_t) strtoul (argv[1], NULL, 0);
            _app_info.ring_pcb_version.minor = (uint8_t) strtoul (argv[2], NULL, 0);
            _app_info.ring_pcb_version.build = (uint8_t) strtoul (argv[3], NULL, 0);

            /* Write changes to flash. */
            _app_info_write ();
        }
        else
        {
            goto USAGE;
        }
    }
    else
    {
USAGE:
        PRINT (INFO, "Usage: app [OPTION...]\r\n");
        PRINT (INFO, "  -cli        Enables or disables command line.\r\n");
        PRINT (INFO, "    <%%b>      true to enable, false to disable.\r\n");
        PRINT (INFO, "  -mpcb       Sets the main pcb version.\r\n");
        PRINT (INFO, "    <%%d>      Major\r\n");
        PRINT (INFO, "    <%%d>      Minor\r\n");
        PRINT (INFO, "    <%%d>      Build\r\n");
        PRINT (INFO, "  -rpcb       Sets the ring pcb version.\r\n");
        PRINT (INFO, "    <%%d>      Major\r\n");
        PRINT (INFO, "    <%%d>      Minor\r\n");
        PRINT (INFO, "    <%%d>      Build\r\n");
        PRINT (INFO, "  -mfgid      Sets the manufacturing ID.\r\n");
        PRINT (INFO, "    <%%d>      Manufacturing ID\r\n");
        PRINT (INFO, "  -mfgdate    Sets the manufacturing date.\r\n");
        PRINT (INFO, "    <%%d>      Format YYYYMMDD\r\n");
        PRINT (INFO, "  -pn         Sets the part number.\r\n");
        PRINT (INFO, "    <%%s>      Part number (max of 15 chars)\r\n");
        PRINT (INFO, "  -sn         Sets the part number.\r\n");
        PRINT (INFO, "    <%%s>      Part number (max of 12 chars)\r\n");
        PRINT (INFO, "  -tc         Calibrates temperature sensor offset\r\n");
        PRINT (INFO, "    <%%f>      current temperature\r\n");
    }
}

static void _app_info_read (void)
{
    ERRNO_e    err;
    APP_info_s info;

    err = FS_open_or_create (APP_FILENAME, &_app_file, sizeof (APP_info_s), (FS_FILE_DEPTH_DEFAULT * 8));
    if (err == ERRNO_SUCCESS)
    {
        /* Read file. */
        err = FS_read (_app_file, (uint8_t *) &info, 0, sizeof (APP_info_s));

        /* Check file entry is valid. */
        if (err != ERRNO_SUCCESS)
        { 
            /* Write defaults to file. */
            _app_info_write ();
            return;
        }

        /* Validate read file. */
        if (info.power_cnt > 1000000)
        {
            /* Write defaults to file. */
            _app_info_write ();
            return;
        }

        /* Update app info to what was read from flash and validated. */
        _app_info = info;

        /* Update app version if, current is newer. */
        _app_info.version.major = APP_MAJOR;
        _app_info.version.minor = APP_MINOR;
        _app_info.version.build = APP_BUILD;
    }
    else if (err == ERRNO_SIZE_MISMATCH)
    {
        /* App info has been modified, need to delete old file and write new info. */
        err = FS_delete (APP_FILENAME);

        if (err != ERRNO_SUCCESS)
        {
            /* Something else is going on... */
            PRINT (ERROR, "Failure to delete old app file.\r\n");
            return;
        }

        /* Create new file. */
        err = FS_open_or_create (MOTION_FILENAME, &_app_file, sizeof (APP_info_s), FS_FILE_DEPTH_DEFAULT);
        if (err != ERRNO_SUCCESS)
        {
            PRINT (ERROR, "Failure to create app file.\r\n");
        }
        else
        {
            PRINT (INFO, "New app file created successfully\r\n");
        }

        /* Write default info to file. */
        _app_info_write ();
    }
    else
    {
        /* Write defaults to file. */
        _app_info_write ();
    }
}

static void _app_info_write (void)
{
    if (FS_write (_app_file, (uint8_t *) &_app_info) != ERRNO_SUCCESS)
    {
        PRINT (INFO, "Failed to write app info to file.\r\n");
    }
}
