/**
 * Copyright (c) 2013 - 2017, Nordic Semiconductor ASA
 * 
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form, except as embedded into a Nordic
 *    Semiconductor ASA integrated circuit in a product or a software update for
 *    such product, must reproduce the above copyright notice, this list of
 *    conditions and the following disclaimer in the documentation and/or other
 *    materials provided with the distribution.
 * 
 * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 * 
 * 4. This software, with or without modification, must only be used with a
 *    Nordic Semiconductor ASA integrated circuit.
 * 
 * 5. Any software provided in binary form under this license must not be reverse
 *    engineered, decompiled, modified and/or disassembled.
 * 
 * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 */

#include "bootloader.h"
#include <string.h>
#include "bootloader_types.h"
#include "bootloader_util.h"
#include "dfu.h"
#include "dfu_transport.h"
#include "nrf.h"
#include "app_error.h"
#include "nrf_sdm.h"
#include "nrf_soc.h"
#include "nordic_common.h"
#include "pstorage.h"
#include "app_scheduler.h"
#include "nrf_delay.h"

#include "debug_pin.h"

#define IRQ_ENABLED             0x01                    /**< Field identifying if an interrupt is enabled. */
#define MAX_NUMBER_INTERRUPTS   32                      /**< Maximum number of interrupts available. */

/*
* bootloader_settings space is subdivided into 8 128byte sized blocks to properly use pstorage data abstraction scheme
* with the intent to use the last block located, specifically at 0x3FF80, for ant_boot_settings matters.
*/
#define BOOTLOADER_SETTINGS_FLASH_BLOCK_SIZE    128
#define BOOTLOADER_SETTINGS_FLASH_BLOCK_COUNT   8       /* Total of 1024 */

typedef enum
{
    BOOTLOADER_UPDATING,
    BOOTLOADER_SETTINGS_SAVING,
    BOOTLOADER_COMPLETE,
    BOOTLOADER_TIMEOUT,
    BOOTLOADER_RESET,
} bootloader_status_t;

static pstorage_handle_t        m_bootsettings_handle;  /**< Pstorage handle to use for registration and identifying the bootloader module on subsequent calls to the pstorage module for load and store of bootloader setting in flash. */
static bootloader_status_t      m_update_status;        /**< Current update status for the bootloader module to ensure correct behaviour when updating settings and when update completes. */
static uint8_t                  m_delay_applied = false;/**< Delay has been applied before the initial access to flash > */

/* NOTE: Temporary use of this page until the bootloader_settings slotting is implemented */
#define BOOT_SETTINGS_PEND_ADDRESS      (BOOTLOADER_MBR_RETAINING_PAGE_ADDRESS)
#define BOOT_SETTINGS_PEND_VALUE        0xFFFFFFFE
uint8_t  m_boot_settings_pend[CODE_PAGE_SIZE] __attribute__((at(BOOT_SETTINGS_PEND_ADDRESS))) __attribute__((used));

uint32_t blocking_flash_page_erase(uint32_t page_number)
{
    uint32_t err_code;
    do{
        err_code = sd_flash_page_erase(page_number);
    } while (err_code == NRF_ERROR_BUSY);
    return err_code;
}

uint32_t blocking_flash_word_write(uint32_t * const p_dst, uint32_t data)
{
    uint32_t err_code;
    do{
        err_code = sd_flash_write(p_dst, &data, 1);
    } while (err_code == NRF_ERROR_BUSY);
    return err_code;
}

static void pstorage_callback_handler(pstorage_handle_t * handle, uint8_t op_code, uint32_t result, uint8_t * p_data, uint32_t data_len)
{
    // If we are in BOOTLOADER_SETTINGS_SAVING state and we receive an PSTORAGE_STORE_OP_CODE
    // response then settings has been saved and update has completed.
    if ((m_update_status == BOOTLOADER_SETTINGS_SAVING) && (op_code == PSTORAGE_STORE_OP_CODE) && (handle->block_id == BOOTLOADER_SETTINGS_ADDRESS)) //
    {
        m_update_status = BOOTLOADER_COMPLETE;

        /*Clears bootloader_settings critical flag*/
        if (*((uint32_t *)BOOT_SETTINGS_PEND_ADDRESS) == BOOT_SETTINGS_PEND_VALUE)
        {
            uint32_t err_code = blocking_flash_page_erase(BOOT_SETTINGS_PEND_ADDRESS / CODE_PAGE_SIZE);
            APP_ERROR_CHECK(err_code);
        }
    }
    APP_ERROR_CHECK(result);
}


/**@brief   Function for waiting for events.
 *
 * @details This function will place the chip in low power mode while waiting for events from
 *          the S110 SoftDevice or other peripherals. When interrupted by an event, it will call the
 *          @ref app_sched_execute function to process the received event. This function will return
 *          when the final state of the firmware update is reached OR when a tear down is in
 *          progress.
 */
static void wait_for_events(void)
{
    for (;;)
    {
        // Wait in low power state for any events.
        uint32_t err_code = sd_app_evt_wait();
        APP_ERROR_CHECK(err_code);

        // Event received. Process it from the scheduler.
        app_sched_execute();

        if ((m_update_status == BOOTLOADER_COMPLETE) ||
            (m_update_status == BOOTLOADER_TIMEOUT)  ||
            (m_update_status == BOOTLOADER_RESET))
        {
            // When update has completed or a timeout/reset occured we will return.
            return;
        }
    }
}


bool bootloader_app_is_valid(uint32_t app_addr)
{
    const bootloader_settings_t * p_bootloader_settings;

    // Critical flag was not cleared.
    if (*((uint32_t *)BOOT_SETTINGS_PEND_ADDRESS) == BOOT_SETTINGS_PEND_VALUE)
    {
        return false;
    }

    // There exists an application in CODE region 1.
    if (*((uint32_t *) app_addr) == EMPTY_FLASH_MASK)
    {
        return false;
    }

    bootloader_util_settings_get(&p_bootloader_settings);

    // The application in CODE region 1 was not flagged as invalid.
    if (p_bootloader_settings->valid_app == BOOTLOADER_SETTINGS_INVALID_APPLICATION)
    {
        return false;
    }

    return true;
}


static void bootloader_settings_save(bootloader_settings_t * p_settings)
{
    //TODO. This is temporary
    static uint8_t              ant_boot_settings[BOOTLOADER_SETTINGS_FLASH_BLOCK_SIZE];
    static pstorage_handle_t    ant_boot_settings_handle;

    /* Backing up ant_boot_settings. */
    ant_boot_settings_handle.module_id  = m_bootsettings_handle.module_id;
    ant_boot_settings_handle.block_id   = m_bootsettings_handle.block_id + (BOOTLOADER_SETTINGS_FLASH_BLOCK_SIZE * (BOOTLOADER_SETTINGS_FLASH_BLOCK_COUNT - 1));
    memcpy(ant_boot_settings, (uint8_t*)ant_boot_settings_handle.block_id, BOOTLOADER_SETTINGS_FLASH_BLOCK_SIZE);

    /* NOTE: Must erase the whole module to prevent pstorage block erasing which uses swap space*/
    uint32_t err_code = pstorage_clear(&m_bootsettings_handle,
                                        BOOTLOADER_SETTINGS_FLASH_BLOCK_SIZE * BOOTLOADER_SETTINGS_FLASH_BLOCK_COUNT);
    APP_ERROR_CHECK(err_code);

    /* Write back ant_boot_settings */
    err_code = pstorage_store(&ant_boot_settings_handle,
                              ant_boot_settings,
                              BOOTLOADER_SETTINGS_FLASH_BLOCK_SIZE,
                              0);
    APP_ERROR_CHECK(err_code);

    err_code = pstorage_store(&m_bootsettings_handle,
                              (uint8_t *)p_settings,
                              sizeof(bootloader_settings_t),
                              0);
    APP_ERROR_CHECK(err_code);
}


void bootloader_dfu_update_process(dfu_update_status_t update_status)
{
    static bootloader_settings_t    settings;
    const bootloader_settings_t *   p_bootloader_settings;
    uint32_t                        err_code;

    bootloader_util_settings_get(&p_bootloader_settings);                                   /* Extract current values of the bootloader_settings*/
    memcpy(&settings, p_bootloader_settings, sizeof(bootloader_settings_t));                /* Copy over to local bootloader_settings*/

    if (update_status.status_code == DFU_UPDATE_NEW_IMAGES)
    {
        if (update_status.sd_image_size != NEW_IMAGE_SIZE_EMPTY)
        {
            settings.sd_image.st.size = update_status.sd_image_size;
            settings.sd_image.st.bank = update_status.bank_used;
        }
        else
        {
            settings.sd_image.st.size = NEW_IMAGE_SIZE_EMPTY;
        }

        if (update_status.bl_image_size != NEW_IMAGE_SIZE_EMPTY)
        {
            settings.bl_image.st.size = update_status.bl_image_size;
            settings.bl_image.st.bank = update_status.bank_used;
        }
        else
        {
            settings.bl_image.st.size = NEW_IMAGE_SIZE_EMPTY;
        }

        if (update_status.ap_image_size != NEW_IMAGE_SIZE_EMPTY)
        {
            settings.ap_image.st.size = update_status.ap_image_size;
            settings.ap_image.st.bank = update_status.bank_used;
        }
        else
        {
            settings.ap_image.st.size = NEW_IMAGE_SIZE_EMPTY;
        }

        settings.src_image_address = update_status.src_image_address;
        m_update_status             = BOOTLOADER_SETTINGS_SAVING;

        /*Clears bootloader_settings critical flag - in case it was used by MBR*/
        if (*((uint32_t *)BOOT_SETTINGS_PEND_ADDRESS) != 0xFFFFFFFF)
        {
            err_code = blocking_flash_page_erase(BOOT_SETTINGS_PEND_ADDRESS / CODE_PAGE_SIZE);
            APP_ERROR_CHECK(err_code);
        }

        // TEMPORARY: This serves as a critical flag for the bootloader_settings updating.
        err_code = blocking_flash_word_write((uint32_t *)BOOT_SETTINGS_PEND_ADDRESS,
                                             BOOT_SETTINGS_PEND_VALUE);
        APP_ERROR_CHECK(err_code);

        bootloader_settings_save(&settings);
    }
    else if (update_status.status_code == DFU_UPDATE_AP_SWAPPED)
    {
        settings.ap_image.st.bank   = NEW_IMAGE_BANK_DONE;
        settings.ap_image.st.size   = p_bootloader_settings->ap_image.st.size;
        settings.valid_app          = BOOTLOADER_SETTINGS_VALID_APPLICATION;
        m_update_status             = BOOTLOADER_SETTINGS_SAVING;

        /*Clears bootloader_settings critical flag - in case it was used by MBR*/
        if (*((uint32_t *)BOOT_SETTINGS_PEND_ADDRESS) != 0xFFFFFFFF)
        {
            err_code = blocking_flash_page_erase(BOOT_SETTINGS_PEND_ADDRESS / CODE_PAGE_SIZE);
            APP_ERROR_CHECK(err_code);
        }

        // TEMPORARY: This serves as a critical flag for the bootloader_settings updating.
        err_code = blocking_flash_word_write((uint32_t *)BOOT_SETTINGS_PEND_ADDRESS,
                                             BOOT_SETTINGS_PEND_VALUE);
        APP_ERROR_CHECK(err_code);

        bootloader_settings_save(&settings);
    }
    else if (update_status.status_code == DFU_UPDATE_AP_INVALIDATED)
    {
        if (p_bootloader_settings->valid_app != BOOTLOADER_SETTINGS_INVALID_APPLICATION)
        {
            settings.valid_app          = BOOTLOADER_SETTINGS_INVALID_APPLICATION;
            m_update_status             = BOOTLOADER_UPDATING;
            bootloader_settings_save(&settings);
        }
    }
    else if (update_status.status_code == DFU_TIMEOUT)
    {
        // Timeout has occurred. Close the connection with the DFU Controller.
//        dfu_transport_close();

        m_update_status      = BOOTLOADER_TIMEOUT;
    }
    else if (update_status.status_code == DFU_RESET)
    {
        // Timeout has occurred. Close the connection with the DFU Controller.
//        dfu_transport_close();

        m_update_status      = BOOTLOADER_RESET;
    }
    else
    {
        // No implementation needed.
    }
}


uint32_t bootloader_init(void)
{
    uint32_t err_code;
    pstorage_module_param_t storage_params;

    storage_params.cb          = pstorage_callback_handler;
    storage_params.block_size  = BOOTLOADER_SETTINGS_FLASH_BLOCK_SIZE;
    storage_params.block_count = BOOTLOADER_SETTINGS_FLASH_BLOCK_COUNT;

    err_code = pstorage_init();
    if (err_code != NRF_SUCCESS)
    {
        return err_code;
    }

    err_code = pstorage_register(&storage_params, &m_bootsettings_handle);

    m_delay_applied = false;
    return err_code;
}


uint32_t bootloader_dfu_start(void)
{
    uint32_t                err_code = NRF_SUCCESS;

    err_code = dfu_init();
    if (err_code != NRF_SUCCESS)
    {
        return err_code;
    }

    err_code = dfu_transport_update_start();

    wait_for_events();

    err_code = dfu_transport_close();

    return err_code;
}


/**@brief Function for disabling all interrupts before jumping from bootloader to application.
 */
static void interrupts_disable(void)
{
    uint32_t interrupt_setting_mask;
    uint8_t  irq;

    // We start the loop from first interrupt, i.e. interrupt 0.
    irq                    = 0;
    // Fetch the current interrupt settings.
    interrupt_setting_mask = NVIC->ISER[0];

    for (; irq < MAX_NUMBER_INTERRUPTS; irq++)
    {
        if (interrupt_setting_mask & (IRQ_ENABLED << irq))
        {
            // The interrupt was enabled, and hence disable it.
            NVIC_DisableIRQ((IRQn_Type) irq);
        }
    }
}

// Ensure that flash operations are not executed within the first 100 ms seconds to allow
//  a debugger to be attached
static void debugger_delay (void)
{
    if (m_delay_applied == false)
    {
        m_delay_applied = true;
        nrf_delay_ms(100);
    }
}

void bootloader_app_start(uint32_t app_addr)
{
    // If the applications CRC has been checked and passed, the magic number will be written and we
    // can start the application safely.
    uint32_t err_code = sd_softdevice_disable();
    APP_ERROR_CHECK(err_code);

    interrupts_disable();

#if defined (S210_V3_STACK)
    err_code = sd_softdevice_forward_to_application();
#else
    err_code = sd_softdevice_vector_table_base_set(CODE_REGION_1_START);
#endif
    APP_ERROR_CHECK(err_code);

    bootloader_util_app_start(CODE_REGION_1_START);
}


#if !defined (S210_V3_STACK)
uint32_t temp_value;
uint32_t bootloader_dfu_sd_update_continue()
{
    uint32_t err_code = NRF_SUCCESS;
    const bootloader_settings_t * p_bootloader_settings;

    bootloader_util_settings_get(&p_bootloader_settings);

    /* Ignore update attempts on invalid src_image_address */
    if ( (p_bootloader_settings->src_image_address == SRC_IMAGE_ADDRESS_EMPTY)   ||
        (p_bootloader_settings->src_image_address == SRC_IMAGE_ADDRESS_INVALID))
    {
        return NRF_SUCCESS;
    }

    if ( (p_bootloader_settings->sd_image.st.bank == NEW_IMAGE_BANK_0)           ||
        (p_bootloader_settings->sd_image.st.bank == NEW_IMAGE_BANK_1))
    {
        debugger_delay();

        err_code = dfu_sd_image_swap();
        if (dfu_sd_image_validate() == NRF_SUCCESS)
        {
            /* This is a manual write to flash, non-softdevice managed */
            uint32_t address    = (uint32_t)p_bootloader_settings + BOOTLOADER_SETTINGS_SD_IMAGE_SIZE_ADR_OFFSET;
            temp_value          = p_bootloader_settings->sd_image.all & 0x3FFFFFFF; // clears image bank bits.
            err_code            = blocking_flash_word_write((uint32_t *)address, temp_value);
            //TODO need to catch verification error
        }
    }
    return err_code;
}

uint32_t bootloader_dfu_bl_update_continue(void)
{
    uint32_t err_code = NRF_SUCCESS;
    const bootloader_settings_t * p_bootloader_settings;

    bootloader_util_settings_get(&p_bootloader_settings);

    /* Ignore update attempts on invalid src_image_address */
    if ( (p_bootloader_settings->src_image_address == SRC_IMAGE_ADDRESS_EMPTY)   ||
        (p_bootloader_settings->src_image_address == SRC_IMAGE_ADDRESS_INVALID))
    {
        return NRF_SUCCESS;
    }

    if ( (p_bootloader_settings->bl_image.st.bank == NEW_IMAGE_BANK_0)           ||
        (p_bootloader_settings->bl_image.st.bank == NEW_IMAGE_BANK_1))
    {
        debugger_delay();

        if (dfu_bl_image_validate() != NRF_SUCCESS)
        {
            err_code = dfu_bl_image_swap(); // reset is built in to the mbr bootloader swap
        }
        else
        {
            /* This is a manual write to flash, non-softdevice managed */
            uint32_t address    = (uint32_t)p_bootloader_settings + BOOTLOADER_SETTINGS_BL_IMAGE_SIZE_ADR_OFFSET;
            uint32_t value      = p_bootloader_settings->bl_image.all & 0x3FFFFFFF; // clears image bank bits.
            err_code            = blocking_flash_word_write((uint32_t *)address, value);
            //TODO need to catch verification error
        }
    }
    return err_code;
}
#endif // !S210_V3_STACK

uint32_t bootloader_dfu_ap_update_continue(void)
{
    uint32_t err_code = NRF_SUCCESS;
    const bootloader_settings_t * p_bootloader_settings;

    bootloader_util_settings_get(&p_bootloader_settings);

    /* Ignore update attempts on invalid src_image_address */
    if ( (p_bootloader_settings->src_image_address == SRC_IMAGE_ADDRESS_EMPTY)   ||
        (p_bootloader_settings->src_image_address == SRC_IMAGE_ADDRESS_INVALID))
    {
        return NRF_SUCCESS;
    }

    if ( (p_bootloader_settings->ap_image.st.bank == NEW_IMAGE_BANK_0)           ||
        (p_bootloader_settings->ap_image.st.bank == NEW_IMAGE_BANK_1))
    {
        /* If updating application only, we can start the copy right now*/
        if ((p_bootloader_settings->sd_image.st.size == NEW_IMAGE_SIZE_EMPTY)   &&
            (p_bootloader_settings->bl_image.st.size == NEW_IMAGE_SIZE_EMPTY))
        {
            err_code = dfu_ap_image_swap();

            dfu_update_status_t update_status = {DFU_UPDATE_AP_SWAPPED, };

            bootloader_dfu_update_process(update_status);

            wait_for_events();
        }
    }
   return err_code;
}
