/**
  ******************************************************************************
  * @file    LmhpFirmwareManagement.c
  * @author  MCD Application Team
  * @brief   Implements the LoRa-Alliance Firmware Management package
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2020(-2021) STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */

/* Includes ------------------------------------------------------------------*/
#include "platform.h"
#include "LoRaMac.h"
#include "LmHandler.h"
#include "LmhpFirmwareManagement.h"
#include "mw_log_conf.h"  /* needed for MW_LOG */

/* Private typedef -----------------------------------------------------------*/
/*!
 * Package current context
 */
typedef struct LmhpFirmwareManagementState_s
{
    bool Initialized;
#if (defined( LORAMAC_VERSION ) && ( LORAMAC_VERSION == 0x01000300 ))
    bool IsRunning;
#elif (defined( LORAMAC_VERSION ) && ( LORAMAC_VERSION == 0x01000400 ))
    bool IsTxPending;
#endif /* LORAMAC_VERSION */
    uint8_t DataBufferMaxSize;
    uint8_t *DataBuffer;
} LmhpFirmwareManagementState_t;

typedef enum LmhpFirmwareManagementMoteCmd_e
{
    FW_MANAGEMENT_PKG_VERSION_ANS              = 0x00,
    FW_MANAGEMENT_DEV_VERSION_ANS              = 0x01,
    FW_MANAGEMENT_DEV_REBOOT_TIME_ANS          = 0x02,
    FW_MANAGEMENT_DEV_REBOOT_COUNTDOWN_ANS     = 0x03,
    FW_MANAGEMENT_DEV_UPGRADE_IMAGE_ANS        = 0x04,
    FW_MANAGEMENT_DEV_DELETE_IMAGE_ANS         = 0x05,
} LmhpFirmwareManagementMoteCmd_t;

typedef enum LmhpFirmwareManagementSrvCmd_e
{
    FW_MANAGEMENT_PKG_VERSION_REQ              = 0x00,
    FW_MANAGEMENT_DEV_VERSION_REQ              = 0x01,
    FW_MANAGEMENT_DEV_REBOOT_TIME_REQ          = 0x02,
    FW_MANAGEMENT_DEV_REBOOT_COUNTDOWN_REQ     = 0x03,
    FW_MANAGEMENT_DEV_UPGRADE_IMAGE_REQ        = 0x04,
    FW_MANAGEMENT_DEV_DELETE_IMAGE_REQ         = 0x05,
} LmhpFirmwareManagementSrvCmd_t;

typedef enum LmhpFirmwareManagementUpImageStatus_e
{
    FW_MANAGEMENT_NO_PRESENT_IMAGE             = 0x00,
    FW_MANAGEMENT_CORRUPTED_IMAGE              = 0x01,
    FW_MANAGEMENT_INCOMPATIBLE_IMAGE           = 0x02,
    FW_MANAGEMENT_VALID_IMAGE                  = 0x03,
} LmhpFirmwareManagementUpImageStatus_t;
/* Private define ------------------------------------------------------------*/
/*!
 * LoRaWAN Application Layer Remote multicast setup Specification
 */
#define FW_MANAGEMENT_PORT                          203
#define FW_MANAGEMENT_ID                            4
#define FW_MANAGEMENT_VERSION                       1
#define FW_VERSION                                  0x00000000 /* Not yet managed */
#define HW_VERSION                                  0x00000000 /* Not yet managed */

/* Private macro -------------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
/*!
 * Initializes the package with provided parameters
 *
 * \param [in] params            Pointer to the package parameters
 * \param [in] dataBuffer        Pointer to main application buffer
 * \param [in] dataBufferMaxSize Main application buffer maximum size
 */
static void LmhpFirmwareManagementInit(void *params, uint8_t *dataBuffer, uint8_t dataBufferMaxSize);

/*!
 * Returns the current package initialization status.
 *
 * \retval status Package initialization status
 *                [true: Initialized, false: Not initialized]
 */
static bool LmhpFirmwareManagementIsInitialized(void);

#if (defined( LORAMAC_VERSION ) && ( LORAMAC_VERSION == 0x01000300 ))
/*!
 * Returns the package operation status.
 *
 * \retval status Package operation status
 *                [true: Running, false: Not running]
 */
static bool LmhpFirmwareManagementIsRunning(void);
#elif (defined( LORAMAC_VERSION ) && ( LORAMAC_VERSION == 0x01000400 ))
/*!
 * Returns if a package transmission is pending or not.
 *
 * \retval status Package transmission status
 *                [true: pending, false: Not pending]
 */
static bool LmhpFirmwareManagementIsTxPending(void);
#endif /* LORAMAC_VERSION */

/*!
 * Processes the internal package events.
 */
static void LmhpFirmwareManagementProcess(void);

/*!
 * Processes the MCPS Indication
 *
 * \param [in] mcpsIndication     MCPS indication primitive data
 */
static void LmhpFirmwareManagementOnMcpsIndication(McpsIndication_t *mcpsIndication);

static void OnRebootTimer(void *context);

/* Private variables ---------------------------------------------------------*/
static LmhpFirmwareManagementState_t LmhpFirmwareManagementState =
{
    .Initialized = false,
#if (defined( LORAMAC_VERSION ) && ( LORAMAC_VERSION == 0x01000300 ))
    .IsRunning =   false,
#elif (defined( LORAMAC_VERSION ) && ( LORAMAC_VERSION == 0x01000400 ))
    .IsTxPending = false,
#endif /* LORAMAC_VERSION */
};

static LmhPackage_t LmhpFirmwareManagementPackage =
{
    .Port = FW_MANAGEMENT_PORT,
    .Init = LmhpFirmwareManagementInit,
    .IsInitialized = LmhpFirmwareManagementIsInitialized,
#if (defined( LORAMAC_VERSION ) && ( LORAMAC_VERSION == 0x01000300 ))
    .IsRunning = LmhpFirmwareManagementIsRunning,
#elif (defined( LORAMAC_VERSION ) && ( LORAMAC_VERSION == 0x01000400 ))
    .IsTxPending = LmhpFirmwareManagementIsTxPending,
#endif /* LORAMAC_VERSION */
    .Process = LmhpFirmwareManagementProcess,
    .OnPackageProcessEvent = NULL,                             // To be initialized by LmHandler
    .OnMcpsConfirmProcess = NULL,                              // Not used in this package
    .OnMcpsIndicationProcess =    LmhpFirmwareManagementOnMcpsIndication,
    .OnMlmeConfirmProcess = NULL,                              // Not used in this package
    .OnMlmeIndicationProcess = NULL,                           // Not used in this package
    .OnJoinRequest = NULL,                                     // To be initialized by LmHandler
    .OnDeviceTimeRequest = NULL,                               // To be initialized by LmHandler
    .OnSysTimeUpdate = NULL,                                   // To be initialized by LmHandler
#if (defined( LORAMAC_VERSION ) && ( LORAMAC_VERSION == 0x01000400 ))
    .OnSystemReset = NULL,                                     // To be initialized by LmHandler
#endif /* LORAMAC_VERSION */
};

/*!
 * Reboot timer
 */
static TimerEvent_t RebootTimer;

/* Exported functions ---------------------------------------------------------*/
LmhPackage_t *LmhpFirmwareManagementPackageFactory(void)
{
    return &LmhpFirmwareManagementPackage;
}

/* Private  functions ---------------------------------------------------------*/
static void LmhpFirmwareManagementInit(void *params, uint8_t *dataBuffer, uint8_t dataBufferMaxSize)
{
    if( dataBuffer != NULL )
    {
        LmhpFirmwareManagementState.DataBuffer = dataBuffer;
        LmhpFirmwareManagementState.DataBufferMaxSize = dataBufferMaxSize;
        LmhpFirmwareManagementState.Initialized = true;
#if (defined( LORAMAC_VERSION ) && ( LORAMAC_VERSION == 0x01000300 ))
        LmhpFirmwareManagementState.IsRunning = true;
#endif /* LORAMAC_VERSION */
        TimerInit(&RebootTimer, OnRebootTimer);
    }
    else
    {
#if (defined( LORAMAC_VERSION ) && ( LORAMAC_VERSION == 0x01000300 ))
        LmhpFirmwareManagementState.IsRunning = true;
#endif /* LORAMAC_VERSION */
        LmhpFirmwareManagementState.Initialized = false;
    }
#if (defined( LORAMAC_VERSION ) && ( LORAMAC_VERSION == 0x01000400 ))
    LmhpFirmwareManagementState.IsTxPending = false;
#endif /* LORAMAC_VERSION */
}

static bool LmhpFirmwareManagementIsInitialized(void)
{
  return LmhpFirmwareManagementState.Initialized;
}
#if (defined( LORAMAC_VERSION ) && ( LORAMAC_VERSION == 0x01000300 ))
static bool LmhpFirmwareManagementIsRunning(void)
{
    if (LmhpFirmwareManagementState.Initialized == false)
    {
        return false;
    }

  return LmhpFirmwareManagementState.IsRunning;
}
#elif (defined( LORAMAC_VERSION ) && ( LORAMAC_VERSION == 0x01000400 ))
static bool LmhpFirmwareManagementIsTxPending( void )
{
    return LmhpFirmwareManagementState.IsTxPending;
}
#endif /* LORAMAC_VERSION */
static void LmhpFirmwareManagementProcess(void)
{
  /* Not yet implemented */
}

static void LmhpFirmwareManagementOnMcpsIndication(McpsIndication_t *mcpsIndication)
{
    uint8_t cmdIndex = 0;
    uint8_t dataBufferIndex = 0;

    if( mcpsIndication->Port != FW_MANAGEMENT_PORT )
    {
        return;
    }

    while( cmdIndex < mcpsIndication->BufferSize )
    {
        switch( mcpsIndication->Buffer[cmdIndex++] )
        {
            case FW_MANAGEMENT_PKG_VERSION_REQ:
            {
                LmhpFirmwareManagementState.DataBuffer[dataBufferIndex++] = FW_MANAGEMENT_PKG_VERSION_ANS;
                LmhpFirmwareManagementState.DataBuffer[dataBufferIndex++] = FW_MANAGEMENT_ID;
                LmhpFirmwareManagementState.DataBuffer[dataBufferIndex++] = FW_MANAGEMENT_VERSION;
                break;
            }
            case FW_MANAGEMENT_DEV_VERSION_REQ:
            {
                LmhpFirmwareManagementState.DataBuffer[dataBufferIndex++] = FW_MANAGEMENT_DEV_VERSION_ANS;
                /* FW Version */
                LmhpFirmwareManagementState.DataBuffer[dataBufferIndex++] = (FW_VERSION >> 0) & 0xFF;
                LmhpFirmwareManagementState.DataBuffer[dataBufferIndex++] = (FW_VERSION >> 8) & 0xFF;
                LmhpFirmwareManagementState.DataBuffer[dataBufferIndex++] = (FW_VERSION >> 16) & 0xFF;
                LmhpFirmwareManagementState.DataBuffer[dataBufferIndex++] = (FW_VERSION >> 24) & 0xFF;
                /* HW Version */
                LmhpFirmwareManagementState.DataBuffer[dataBufferIndex++] = (HW_VERSION >> 0) & 0xFF;
                LmhpFirmwareManagementState.DataBuffer[dataBufferIndex++] = (HW_VERSION >> 8) & 0xFF;
                LmhpFirmwareManagementState.DataBuffer[dataBufferIndex++] = (HW_VERSION >> 16) & 0xFF;
                LmhpFirmwareManagementState.DataBuffer[dataBufferIndex++] = (HW_VERSION >> 24) & 0xFF;
                break;
            }
            case FW_MANAGEMENT_DEV_REBOOT_TIME_REQ:
            {
                uint32_t rebootTimeReq = 0;
                uint32_t rebootTimeAns = 0;
                rebootTimeReq  = (mcpsIndication->Buffer[cmdIndex++] << 0) & 0x000000FF;
                rebootTimeReq += (mcpsIndication->Buffer[cmdIndex++] << 8) & 0x0000FF00;
                rebootTimeReq += (mcpsIndication->Buffer[cmdIndex++] << 16) & 0x00FF0000;
                rebootTimeReq += (mcpsIndication->Buffer[cmdIndex++] << 24) & 0xFF000000;

                if (rebootTimeReq == 0)
                {
                    NVIC_SystemReset();
                }
                else if (rebootTimeReq == 0xFFFFFFFF)
                {
                    rebootTimeAns = rebootTimeReq;
                    TimerStop(&RebootTimer);
                }
                else
                {
                    SysTime_t curTime = { .Seconds = 0, .SubSeconds = 0 };
                    curTime = SysTimeGet();

                    rebootTimeAns = rebootTimeReq - curTime.Seconds;
                    if (rebootTimeAns > 0)
                    {
                        /* Start session start timer */
                        TimerSetValue(&RebootTimer, rebootTimeAns * 1000);
                        TimerStart(&RebootTimer);
                    }
                }

                LmhpFirmwareManagementState.DataBuffer[dataBufferIndex++] = FW_MANAGEMENT_DEV_REBOOT_TIME_ANS;
                /* FW Version */
                LmhpFirmwareManagementState.DataBuffer[dataBufferIndex++] = (rebootTimeAns >> 0) & 0xFF;
                LmhpFirmwareManagementState.DataBuffer[dataBufferIndex++] = (rebootTimeAns >> 8) & 0xFF;
                LmhpFirmwareManagementState.DataBuffer[dataBufferIndex++] = (rebootTimeAns >> 16) & 0xFF;
                LmhpFirmwareManagementState.DataBuffer[dataBufferIndex++] = (rebootTimeAns >> 24) & 0xFF;

                break;
            }
            case FW_MANAGEMENT_DEV_REBOOT_COUNTDOWN_REQ:
            {
                uint32_t rebootCountdown = 0;
                rebootCountdown  = (mcpsIndication->Buffer[cmdIndex++] << 0) & 0x000000FF;
                rebootCountdown += (mcpsIndication->Buffer[cmdIndex++] << 8) & 0x0000FF00;
                rebootCountdown += (mcpsIndication->Buffer[cmdIndex++] << 16) & 0x00FF0000;

                if (rebootCountdown == 0)
                {
                    NVIC_SystemReset();
                }
                else if (rebootCountdown == 0xFFFFFF)
                {
                    TimerStop(&RebootTimer);
                }
                else
                {
                    if (rebootCountdown > 0)
                    {
                        /* Start session start timer */
                        TimerSetValue(&RebootTimer, rebootCountdown * 1000);
                        TimerStart(&RebootTimer);
                    }
                }
                LmhpFirmwareManagementState.DataBuffer[dataBufferIndex++] = FW_MANAGEMENT_DEV_REBOOT_COUNTDOWN_ANS;
                /* FW Version */
                LmhpFirmwareManagementState.DataBuffer[dataBufferIndex++] = (rebootCountdown >> 0) & 0xFF;
                LmhpFirmwareManagementState.DataBuffer[dataBufferIndex++] = (rebootCountdown >> 8) & 0xFF;
                LmhpFirmwareManagementState.DataBuffer[dataBufferIndex++] = (rebootCountdown >> 16) & 0xFF;
                break;
            }
            case FW_MANAGEMENT_DEV_UPGRADE_IMAGE_REQ:
            {
                uint32_t imageVersion = 0;
                uint8_t imageStatus = FW_MANAGEMENT_NO_PRESENT_IMAGE;
                LmhpFirmwareManagementState.DataBuffer[dataBufferIndex++] = FW_MANAGEMENT_DEV_UPGRADE_IMAGE_ANS;
                /* No FW present */
                LmhpFirmwareManagementState.DataBuffer[dataBufferIndex++] = imageStatus & 0x03;

                if (imageStatus == FW_MANAGEMENT_VALID_IMAGE)
                {
                    /* Next FW version (opt) */
                    LmhpFirmwareManagementState.DataBuffer[dataBufferIndex++] = (imageVersion >> 0) & 0xFF;
                    LmhpFirmwareManagementState.DataBuffer[dataBufferIndex++] = (imageVersion >> 8) & 0xFF;
                    LmhpFirmwareManagementState.DataBuffer[dataBufferIndex++] = (imageVersion >> 16) & 0xFF;
                    LmhpFirmwareManagementState.DataBuffer[dataBufferIndex++] = (imageVersion >> 24) & 0xFF;
                }
                break;
            }
            case FW_MANAGEMENT_DEV_DELETE_IMAGE_REQ:
            {
                uint32_t firmwareVersion = 0;
                firmwareVersion  = (mcpsIndication->Buffer[cmdIndex++] << 0) & 0x000000FF;
                firmwareVersion += (mcpsIndication->Buffer[cmdIndex++] << 8) & 0x0000FF00;
                firmwareVersion += (mcpsIndication->Buffer[cmdIndex++] << 16) & 0x00FF0000;
                firmwareVersion += (mcpsIndication->Buffer[cmdIndex++] << 24) & 0xFF000000;

                LmhpFirmwareManagementState.DataBuffer[dataBufferIndex++] = FW_MANAGEMENT_DEV_DELETE_IMAGE_ANS;
                /* No valid image present */
                LmhpFirmwareManagementState.DataBuffer[dataBufferIndex++] = 0x01;
                break;
            }
            default:
            {
                break;
            }
        }
    }

    if( dataBufferIndex != 0 )
    {
        // Answer commands
        LmHandlerAppData_t appData =
        {
            .Buffer = LmhpFirmwareManagementState.DataBuffer,
            .BufferSize = dataBufferIndex,
            .Port = FW_MANAGEMENT_PORT
        };

        bool current_dutycycle;
        LmHandlerGetDutyCycleEnable( &current_dutycycle );

        /* force Duty Cycle OFF to this Send */
        LmHandlerSetDutyCycleEnable( false );
        LmHandlerSend( &appData, LORAMAC_HANDLER_UNCONFIRMED_MSG, true );

        /* restore initial Duty Cycle */
        LmHandlerSetDutyCycleEnable( current_dutycycle );
    }
}

static void OnRebootTimer(void *context)
{
    NVIC_SystemReset();
}