/*
 * Copyright 2017-2018 NXP
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

// Implementations of all platform-specific routines (declared in ack_user_platform.h) for LPC55S16-EVK.

#include "ack.h"
#include "ack_circularbuffer.h"

#include "ack_lpc55s16_platform.h"
#include "ack_lpc55s16_ota.h"
#include "board.h"
#include "fsl_common.h"
#include "fsl_usart.h"
#include "fsl_gpio.h"
#include "fsl_debug_console.h"
#include "fsl_crc.h"
#include "fsl_iap.h"
#include "fsl_iap_ffr.h"

#include "ack_crc32.h"
#include <stdarg.h>
#include <stdio.h>
#include <inttypes.h>


// Info we need to manage GPIO pins for the ACK Host MCU sample applications.
// Values in the array MUST be in the same order as the ACKHardwareKind_t enum.
static struct
{
    uint32_t Port;
    uint32_t Pin;
}
sg_lpc55s16evkDigitalPins[] =
{
    { ACK_HOST_INTERRUPT_GPIO_Port, ACK_HOST_INTERRUPT_Pin },
    { ACK_MODULE_RESET_GPIO_Port, ACK_MODULE_RESET_Pin }
#ifdef ACK_SAMPLE_APPLICATIONS_LED_PIN
    ,{ ACK_SAMPLE_APPLICATIONS_LED_GPIO_Port, ACK_SAMPLE_APPLICATIONS_LED_Pin }
#endif
#ifdef ACK_SAMPLE_APPLICATIONS_GPIO_PIN_1
    ,{ ACK_SAMPLE_APPLICATIONS_GPIO_1_GPIO_Port, ACK_SAMPLE_APPLICATIONS_GPIO_1_Pin }
#endif
#ifdef ACK_SAMPLE_APPLICATIONS_GPIO_PIN_2
    ,{ ACK_SAMPLE_APPLICATIONS_GPIO_2_GPIO_Port, ACK_SAMPLE_APPLICATIONS_GPIO_2_Pin }
#endif    
#ifdef ACK_SAMPLE_APPLICATIONS_GPIO_PIN_3
    ,{ ACK_SAMPLE_APPLICATIONS_GPIO_3_GPIO_Port, ACK_SAMPLE_APPLICATIONS_GPIO_3_Pin }
#endif    
};

volatile ACKCircularBuffer_t vg_commsRxBuffer;

volatile uint32_t g_systickCounter;
void SysTick_Handler(void)
{
    g_systickCounter++;
}

static void InitCrc32(CRC_Type *base, uint32_t seed)
{
    crc_config_t config;

    config.polynomial    = kCRC_Polynomial_CRC_32;
    config.reverseIn     = true;
    config.complementIn  = false;
    config.reverseOut    = true;
    config.complementOut = true;
    config.seed          = seed;

    CRC_Init(base, &config);
}

flash_config_t flashInstance;

void ACKPlatform_Initialize()
{
    GPIO_PortInit(GPIO, 0U);
    GPIO_PortInit(GPIO, 1U);

    gpio_pin_config_t reset_config = {
        kGPIO_DigitalOutput,
        0,
    };
    /* Init Module Reset GPIO. */
    GPIO_PinInit(GPIO, ACK_MODULE_RESET_GPIO_Port, ACK_MODULE_RESET_Pin, &reset_config);

#ifdef ACK_SAMPLE_APPLICATIONS_LED_PIN
    // Make sure the LED starts in an off state.
    /* Define the init structure for the output LED pin*/
    gpio_pin_config_t led_config = {
        kGPIO_DigitalOutput,
        0,
    };
    /* Init output LED GPIO. */
    GPIO_PinInit(GPIO, ACK_SAMPLE_APPLICATIONS_LED_GPIO_Port, ACK_SAMPLE_APPLICATIONS_LED_Pin, &led_config);
#endif
#ifdef ACK_SAMPLE_APPLICATIONS_GPIO_PIN_1
    gpio_pin_config_t ack_pin_1_config = {
        kGPIO_DigitalOutput,
        0,
    };
    /* Init output ACK PIN 1 GPIO. */
    GPIO_PinInit(GPIO, ACK_SAMPLE_APPLICATIONS_GPIO_1_GPIO_Port, ACK_SAMPLE_APPLICATIONS_GPIO_1_Pin, &ack_pin_1_config);
#endif
#ifdef ACK_SAMPLE_APPLICATIONS_GPIO_PIN_2
    gpio_pin_config_t ack_pin_2_config = {
        kGPIO_DigitalOutput,
        0,
    };
    /* Init output ACK PIN 2 GPIO. */
    GPIO_PinInit(GPIO, ACK_SAMPLE_APPLICATIONS_GPIO_2_GPIO_Port, ACK_SAMPLE_APPLICATIONS_GPIO_2_Pin, &ack_pin_2_config);
#endif
#ifdef ACK_SAMPLE_APPLICATIONS_GPIO_PIN_3
    gpio_pin_config_t ack_pin_3_config = {
        kGPIO_DigitalOutput,
        0,
    };
    /* Init output ACK PIN 3 GPIO. */
    GPIO_PinInit(GPIO, ACK_SAMPLE_APPLICATIONS_GPIO_3_GPIO_Port, ACK_SAMPLE_APPLICATIONS_GPIO_3_Pin, &ack_pin_3_config);
#endif

    /* Set systick reload value to generate 1ms interrupt */
    if (SysTick_Config(SystemCoreClock / 1000U))
    {
        while (1)
        {
        }
    }
    
    if (FLASH_Init(&flashInstance) != kStatus_Success)
    {
        while (1)
        {
        }
    }
    
    usart_config_t config;
    /*
     * config.baudRate_Bps = 115200U;
     * config.parityMode = kUSART_ParityDisabled;
     * config.stopBitCount = kUSART_OneStopBit;
     * config.loopback = false;
     * config.enableTxFifo = false;
     * config.enableRxFifo = false;
     */
    USART_GetDefaultConfig(&config);
    config.baudRate_Bps = 115200;
    config.enableTx     = true;
    config.enableRx     = true;

    USART_Init(USART2, &config, CLOCK_GetFlexCommClkFreq(2U));

    /* Enable RX interrupt. */
    USART_EnableInterrupts(USART2, kUSART_RxLevelInterruptEnable | kUSART_RxErrorInterruptEnable);
    EnableIRQ(FLEXCOMM2_IRQn);

    InitCrc32(CRC_ENGINE, 0xFFFFFFFFU);
}

uint32_t ACKPlatform_TickCount(void)
{
    return g_systickCounter;
}

void ACKPlatform_Delay(uint32_t milliseconds)
{
    uint32_t tickstart = g_systickCounter;
    uint32_t wait = milliseconds;
  
    /* Add a period to guarantee minimum wait */
    if (wait < 0xFFFFFFFFU)
    {
       wait++;
    }
  
    while((g_systickCounter - tickstart) < wait)
    {
    }
}

void ACKPlatform_WriteDigitalPin(ACKHardwarePin_t pin, bool state)
{
    GPIO_PinWrite(GPIO, sg_lpc55s16evkDigitalPins[pin].Port, sg_lpc55s16evkDigitalPins[pin].Pin, state ? (1U) : (0U));
}

bool ACKPlatform_ReadDigitalPin(ACKHardwarePin_t pin)
{
    return (0U) != GPIO_PinRead(GPIO, sg_lpc55s16evkDigitalPins[pin].Port, sg_lpc55s16evkDigitalPins[pin].Pin);
}

void ACKPlatform_SetDigitalPinPWMLevel(ACKHardwarePin_t pin, uint8_t val)
{
    // This is a temporary solution to make code compile.
    // If val >= 128, the pin will be set, otherwise it will be reset.
    // Need to replace with an actual PWM implementation.
    GPIO_PinWrite(GPIO, sg_lpc55s16evkDigitalPins[pin].Port, sg_lpc55s16evkDigitalPins[pin].Pin, val >= 128 ? (1U) : (0U));
}

bool ACKPlatform_Send(const void* buffer, size_t length, uint32_t timeoutMilliseconds)
{
    size_t i = 0;
    uint32_t startTime = ACKPlatform_TickCount();

    for (i = 0; i < length; ++i)
    {
        if ((ACKPlatform_TickCount() - startTime) >= timeoutMilliseconds)
        {
            return false;
        }

        USART_WriteBlocking(USART2, &(((const uint8_t*)buffer)[i]), 1);
    }

    return true;
}

bool ACKPlatform_Receive(void* pBuffer, size_t length, uint32_t timeoutMilliseconds)
{
    size_t bytesRead = 0;
    uint32_t startTime = ACKPlatform_TickCount();

    // Note that the timeout check is carefully constructed to work even if millis() wraps.
    while ((bytesRead < length) && ((ACKPlatform_TickCount() - startTime) < timeoutMilliseconds))
    {
        if (!ACK_ReadFromCircularBuffer(&vg_commsRxBuffer, &((uint8_t*)pBuffer)[bytesRead]))
        {
            continue;
        }
        bytesRead++;
    }

    return bytesRead == length;
}

void ACKPlatform_DrainRead()
{
    uint8_t dummyByte;
    while (ACK_ReadFromCircularBuffer(&vg_commsRxBuffer, &dummyByte))
    {
        ;
    }
}

uint32_t ACKPlatform_CalculateCrc32(const void* pInput, size_t length)
{
    InitCrc32(CRC_ENGINE, 0xFFFFFFFFU);
    CRC_WriteData(CRC_ENGINE, (const uint8_t*)pInput, length);

    return CRC_Get32bitResult(CRC_ENGINE);
}

void ACKPlatform_DebugPrint(const char* pMessage, ...)
{
    char buffer[512];
    va_list args;

    va_start(args, pMessage);
    vsnprintf(buffer, sizeof(buffer), pMessage, args);

    va_end(args);
    
    PRINTF("%s\r\n", buffer);
}

void FLEXCOMM2_IRQHandler(void)
{
    uint8_t data;

    /* If new data arrived. */
    if ((kUSART_RxFifoNotEmptyFlag | kUSART_RxError) & USART_GetStatusFlags(USART2))
    {
        data = USART_ReadByte(USART2);
        ACK_InsertIntoCircularBuffer(&vg_commsRxBuffer, data);
    }
/* Add for ARM errata 838869, affects Cortex-M4, Cortex-M4F Store immediate overlapping
  exception return operation might vector to incorrect interrupt */
#if defined __CORTEX_M && (__CORTEX_M == 4U)
    __DSB();
#endif
}

#ifdef ACK_HOST_FIRMWARE_UPDATE

static uint32_t sg_otaSize;
static uint32_t sg_otaCrc32;

bool ACKPlatform_StartHostFirmwareUpdate(uint32_t size, uint32_t targetAddress, uint32_t crc32)
{
    unsigned status;
    uint32_t errorAddress;
    uint32_t fsl_status;

    sg_otaSize = size;
    sg_otaCrc32 = crc32;

    if (ACK_LPC55S16_OTA_PRIMARY_PARTITION_START != targetAddress)
    {
        ACK_DEBUG_PRINT_E(
            "Host firmware update targets address %"PRIx32" but primary partition is at %"PRIx32".",
            targetAddress,
            ACK_LPC55S16_OTA_PRIMARY_PARTITION_START);

        return false;
    }

    ACK_ASSERT(ACK_LPC55S16_OTA_STAGING_PARTITION_SIZE == ACK_LPC55S16_OTA_PRIMARY_PARTITION_SIZE);
    if (sg_otaSize > ACK_LPC55S16_OTA_STAGING_PARTITION_SIZE)
    {
        ACK_DEBUG_PRINT_E(
            "Host firmware update of %"PRIu32" bytes is too large for partitions of %"PRIu32" bytes.",
            sg_otaSize,
            ACK_LPC55S16_OTA_STAGING_PARTITION_SIZE);

        return false;
    }

    fsl_status = FLASH_Erase(&flashInstance, ACK_LPC55S16_OTA_STAGING_PARTITION_START, ACK_LPC55S16_OTA_STAGING_PARTITION_SIZE, kFLASH_ApiEraseKey);
    if(fsl_status != kStatus_Success)
    {
        ACK_DEBUG_PRINT_E(
            "Erasing Staging Partition (%"PRIu32" bytes at 0x%"PRIx32") failed at 0x%"PRIx32"; err %u.",
            ACK_LPC55S16_OTA_STAGING_PARTITION_SIZE,
            ACK_LPC55S16_OTA_STAGING_PARTITION_START,
            errorAddress,
            status);
        return false;
    }
    
    fsl_status = FLASH_VerifyErase(&flashInstance, ACK_LPC55S16_OTA_STAGING_PARTITION_START, ACK_LPC55S16_OTA_STAGING_PARTITION_SIZE);
    if(fsl_status != kStatus_Success)
    {
        ACK_DEBUG_PRINT_E(
            "Erasing Staging Partition (%"PRIu32" bytes at 0x%"PRIx32") failed at 0x%"PRIx32"; err %u.",
            ACK_LPC55S16_OTA_STAGING_PARTITION_SIZE,
            ACK_LPC55S16_OTA_STAGING_PARTITION_START,
            errorAddress,
            status);
        return false;
    }

    return true;
}

static uint8_t sg_OtaBuff[ACK_LPC55S16_OTA_FLASH_PAGE_SIZE] = {0};
bool ACKPlatform_SaveHostFirmwareUpdateChunk(uint32_t startOffset, const uint8_t* pData, uint32_t size)
{
    uint32_t targetAddress;
    uint32_t unitCount;
    uint32_t status;
    uint32_t errorAddress;

    //Page size should be a multiple of the chunk size. Please check ACK_HOST_FIRMWARE_UPDATE_CHUNK_SIZE.
    ACK_ASSERT(ACK_LPC55S16_OTA_FLASH_PAGE_SIZE % size == 0 || (startOffset + size == sg_otaSize));

    targetAddress = ACK_LPC55S16_OTA_STAGING_PARTITION_START + startOffset;

    if(startOffset % ACK_LPC55S16_OTA_FLASH_PAGE_SIZE == 0)
    {
        memset(sg_OtaBuff, 0, sizeof(sg_OtaBuff));
        memcpy(sg_OtaBuff, pData, size);
        status = FLASH_Program(&flashInstance, targetAddress, sg_OtaBuff, ACK_LPC55S16_OTA_FLASH_PAGE_SIZE);
    }
    else
    {
        targetAddress = targetAddress - (startOffset % ACK_LPC55S16_OTA_FLASH_PAGE_SIZE);

        memset(sg_OtaBuff, 0, sizeof(sg_OtaBuff));
        memcpy(sg_OtaBuff, (uint8_t*) targetAddress, (startOffset % ACK_LPC55S16_OTA_FLASH_PAGE_SIZE));
        memcpy((uint8_t *)(sg_OtaBuff + (startOffset % ACK_LPC55S16_OTA_FLASH_PAGE_SIZE)), pData, size);

        FLASH_Erase(&flashInstance, targetAddress, ACK_LPC55S16_OTA_FLASH_PAGE_SIZE, kFLASH_ApiEraseKey);

        status = FLASH_Program(&flashInstance, targetAddress, (uint8_t *)sg_OtaBuff, ACK_LPC55S16_OTA_FLASH_PAGE_SIZE);
    }

    if (status != kStatus_Success)
    {
        ACK_DEBUG_PRINT_E(
            "Writing Staging Partition (%"PRIu32" bytes at 0x%"PRIx32") failed near 0x%"PRIx32"; err %u.",
            unitCount * 2,
            targetAddress,
            errorAddress,
            status);
        return false;
    }

    return true;
}

bool ACKPlatform_HostFirmwareUpdateSuccessfullyRetrieved(void)
{
    uint32_t crc32;
    ACKLpc55s16OtaStatusPartition_t statusPartition;
    unsigned status;
    uint32_t fsl_status;
    uint32_t errorAddress;

    // Check CRC.
    crc32 = ACKPlatform_CalculateCrc32((const void*)ACK_LPC55S16_OTA_STAGING_PARTITION_START, sg_otaSize);
    if (crc32 != sg_otaCrc32)
    {
        ACK_DEBUG_PRINT_E(
            "Calculated OTA image CRC 0x%"PRIx32" does not match expected CRC 0x%"PRIx32".",
            crc32,
            sg_otaCrc32);

        return false;
    }

    // CRC is OK. Update the status partition to indicate that an update image is present.
    memset(&statusPartition, 0, sizeof(statusPartition));
    statusPartition.Signature1 = ACK_LPC55S16_OTA_STATUS_PARTITION_SIGNATURE1;
    statusPartition.Signature2 = ACK_LPC55S16_OTA_STATUS_PARTITION_SIGNATURE2;
    statusPartition.State = ACK_LPC55S16_OTA_STATUS_PARTITION_STATE_UNAPPLIED_IMAGE;
    statusPartition.Crc32 = sg_otaCrc32;
    statusPartition.ImageStartAddress = ACK_LPC55S16_OTA_PRIMARY_PARTITION_START;
    statusPartition.ImageSize = sg_otaSize;
    
    fsl_status = FLASH_Erase(&flashInstance, ACK_LPC55S16_OTA_STATUS_PARTITION_START, ACK_LPC55S16_OTA_STATUS_PARTITION_SIZE, kFLASH_ApiEraseKey);

    if (fsl_status == kStatus_Success)
    {

        ACK_STATIC_ASSERT(ACK_LPC55S16_OTA_STATUS_PARTITION_SIZE >= (sizeof(ACKLpc55s16OtaStatusPartition_t) % 1));
        fsl_status = FLASH_Program(&flashInstance, ACK_LPC55S16_OTA_STATUS_PARTITION_START, (uint8_t *)&statusPartition, ACK_LPC55S16_OTA_STATUS_PARTITION_SIZE);
        if (fsl_status != kStatus_Success)
        {
            ACK_DEBUG_PRINT_E(
                "Writing Status Partition (%"PRIu32" bytes at 0x%"PRIx32") failed near 0x%"PRIx32"; err %u.",
                sizeof(ACKLpc55s16OtaStatusPartition_t),
                ACK_LPC55S16_OTA_STATUS_PARTITION_START,
                errorAddress,
                status);
        }
    }
    else
    {
        ACK_DEBUG_PRINT_E(
            "Erasing Status Partition (%"PRIu32" bytes at 0x%"PRIx32") failed at 0x%"PRIx32"; err %u.",
            ACK_LPC55S16_OTA_STATUS_PARTITION_SIZE,
            ACK_LPC55S16_OTA_STATUS_PARTITION_START,
            errorAddress,
            status);
    }

    return fsl_status == kStatus_Success;
}

void ACKPlatform_HostFirmwareUpdateFailed(void)
{
    // No-op on this platform.
}

void ACKPlatform_ApplyHostFirmwareUpdate(void)
{
    // Does not return.
    NVIC_SystemReset();
}

#endif // def ACK_HOST_FIRMWARE_UPDATE
