/* Copyright (c) 2013 Nordic Semiconductor. All Rights Reserved.
 *
 * The information contained herein is property of Nordic Semiconductor ASA.
 * Terms and conditions of usage are described in detail in NORDIC
 * SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT.
 *
 * Licensees are granted free, non-transferable use of the information. NO
 * WARRANTY of ANY KIND is provided. This heading must NOT be removed from
 * the file.
 *
 */

/*
 * Before compiling this example for NRF52, complete the following steps:
 * - Download the S212 SoftDevice from <a href="https://www.thisisant.com/developer/components/nrf52832" target="_blank">thisisant.com</a>.
 * - Extract the downloaded zip file and copy the S212 SoftDevice headers to <tt>\<InstallFolder\>/components/softdevice/s212/headers</tt>.
 * If you are using Keil packs, copy the files into a @c headers folder in your example folder.
 * - Make sure that @ref ANT_LICENSE_KEY in @c nrf_sdm.h is uncommented.
 */

#include "dfu.h"
#include "dfu_transport.h"
#include "bootloader.h"
#include "bootloader_util.h"
#include <stdint.h>
#include <string.h>
#include <stddef.h>
#include "nordic_common.h"
#include "nrf.h"
#include "app_error.h"
#include "nrf_gpio.h"
#include "nrf_soc.h"
#include "nrf_delay.h"
#include "ant_interface.h"
#include "ant_parameters.h"
#include "ant_error.h"
#include "nrf.h"
#include "app_scheduler.h"
#include "app_timer_appsh.h"
#include "nrf_error.h"
#include "boards.h"
#include "softdevice_handler.h"
#include "pstorage_platform.h"
#include "ant_boot_settings_api.h"
#include "antfs_ota.h"
#if !defined (S210_V3_STACK)
#include "nrf_mbr.h"
#endif // !S210_V3_STACK
#include "ant_stack_config.h"
#include "debug_pin.h"
#include "app_timer_appsh.h"
#include "softdevice_handler_appsh.h"

#define ENABLE_BUTTON // include button detection
//#define ENABLE_IO_LED // include LED status on N5DK1 i/o board

#if defined (ENABLE_BUTTON)
   #if defined (BOARD_N5DK1)
      #define BOOTLOADER_BUTTON_PIN        BUTTON_D                                                /**< Button used to enter SW update mode. */
   #else
      #define BOOTLOADER_BUTTON_PIN        BUTTON_1                                                /**< Button used to enter SW update mode. */
   #endif
#endif

#if defined (ENABLE_IO_LED)
   #define BOOTLOADER_ERROR_LED         LED_C                                                   /**< N5DK Leds, set=led off, clr=led on  */
   #define BOOTLOADER_ACTIVE_LED        LED_D
#endif // ENABLE_IO_LED

#define APP_TIMER_PRESCALER             0                                                       /**< Maximum number of simultaneously created timers. */
#define APP_TIMER_OP_QUEUE_SIZE         4                                                       /**< Size of timer operation queues. */

#define SCHED_MAX_EVENT_DATA_SIZE       MAX(ANT_STACK_EVT_MSG_BUF_SIZE, 0)                      /**< Maximum size of scheduler events. */

#define SCHED_QUEUE_SIZE                20                                                      /**< Maximum number of events in the scheduler queue. */


/**@brief Function for error handling, which is called when an error has occurred.
 *
 * @warning This handler is an example only and does not fit a final product. You need to analyze
 *          how your product is supposed to react in case of error.
 *
 * @param[in] error_code  Error code supplied to the handler.
 * @param[in] line_num    Line number where the handler is called.
 * @param[in] p_file_name Pointer to the file name.
 */
#if defined (DEBUG_DFU_BOOTLOADER)
uint32_t error_code_;
uint32_t line_num_;
const uint8_t * p_file_name_;
#endif // DEBUG_DFU_BOOTLOADER

void app_error_fault_handler(uint32_t id, uint32_t pc, uint32_t info)
{
#if defined (DEBUG_DFU_BOOTLOADER)
    app_error_save_and_stop(id, pc, info);
#endif // DEBUG_DFU_BOOTLOADER

#if defined (ENABLE_IO_LED)
//    nrf_gpio_pin_set(BOOTLOADER_ERROR_LED);
#endif // ENABLE_IO_LED

    // This call can be used for debug purposes during application development.
    // On assert, the system can only recover on reset.
    NVIC_SystemReset();
}

void HardFault_Handler(uint32_t ulProgramCounter, uint32_t ulLinkRegister)
{
   (void)ulProgramCounter;
   (void)ulLinkRegister;

   NVIC_SystemReset();
}

/**@brief Callback function for asserts in the SoftDevice.
 *
 * @details This function will be called in case of an assert in the SoftDevice.
 *
 * @warning This handler is an example only and does not fit a final product. You need to analyze
 *          how your product is supposed to react in case of Assert.
 * @warning On assert from the SoftDevice, the system can only recover on reset.
 *
 * @param[in] line_num    Line number of the failing ASSERT call.
 * @param[in] file_name   File name of the failing ASSERT call.
 */
void assert_nrf_callback(uint16_t line_num, const uint8_t * p_file_name)
{
    app_error_handler(0xDEADBEEF, line_num, p_file_name);
}


#if defined (ENABLE_IO_LED)
/**@brief Function for initialization of LEDs.
 *
 * @details Initializes all LEDs used by the application.
 */
static void leds_init(void)
{
    nrf_gpio_cfg_output(LED_A);
    nrf_gpio_cfg_output(LED_B);
    nrf_gpio_cfg_output(LED_C);
    nrf_gpio_cfg_output(LED_D);

     // turn on all leds
     nrf_gpio_pin_clear(LED_A);
     nrf_gpio_pin_clear(LED_B);
     nrf_gpio_pin_clear(LED_C);
     nrf_gpio_pin_clear(LED_D);
}
#endif // ENABLE_IO_LED


#if defined (ENABLE_IO_LED)
/**@brief Function for clearing the LEDs.
 *
 * @details Clears all LEDs used by the application.
 */
static void leds_off(void)
{
    nrf_gpio_pin_set(LED_A); // unused
    nrf_gpio_pin_set(LED_B); // unused
    nrf_gpio_pin_set(LED_C);
    nrf_gpio_pin_set(LED_D);
}
#endif // ENABLE_IO_LED


/**@brief Function for the Timer initialization.
 *
 * @details Initializes the timer module.
 */
static void timers_init(void)
{
    // Initialize timer module, making it use the scheduler.
    APP_TIMER_APPSH_INIT(APP_TIMER_PRESCALER, APP_TIMER_OP_QUEUE_SIZE, true);
}


#if defined (ENABLE_BUTTON)
/**@brief Function for initializing the button module.
 */
static void buttons_init(void)
{
    nrf_gpio_cfg_sense_input(BOOTLOADER_BUTTON_PIN,
                             BUTTON_PULL,
                             NRF_GPIO_PIN_SENSE_LOW);
}
#endif // ENABLE_BUTTON


/**@brief Function for dispatching a stack event to all modules with a stack event handler.
 *
 * @details This function is called from the scheduler in the main loop after a stack
 *          event has been received.
 *
 * @param[in]   event   stack event.
 */
static void sys_evt_dispatch(uint32_t event)
{
    pstorage_sys_event_handler(event);
}


/**@brief Function for initializing the ANT stack.
 *
 * @details Initializes the SoftDevice and the BLE event interrupt.
 */
static void ant_stack_init(void)
{
    uint32_t         err_code;
    nrf_clock_lf_cfg_t clock_lf_cfg = NRF_CLOCK_LFCLKSRC;

    err_code = softdevice_handler_init(&clock_lf_cfg, NULL, 0, softdevice_evt_schedule);
    APP_ERROR_CHECK(err_code);

    err_code = softdevice_sys_evt_handler_set(sys_evt_dispatch);
    APP_ERROR_CHECK(err_code);
    
    // Configure ant stack regards used channels.
    err_code = ant_stack_static_config();
    APP_ERROR_CHECK(err_code);
}


/**@brief Function for event scheduler initialization.
 */
static void scheduler_init(void)
{
    APP_SCHED_INIT(SCHED_MAX_EVENT_DATA_SIZE, SCHED_QUEUE_SIZE);
}

static uint32_t enter_boot_get (void )
{
    uint32_t val = PARAM_FLAGS_ENTER_BOOT_BypassInit;

    if (((*ANT_BOOT_PARAM_FLAGS & PARAM_FLAGS_PARAM_VALID_Msk) >> PARAM_FLAGS_PARAM_VALID_Pos) == PARAM_FLAGS_PARAM_VALID_True )
    {
        val = (*ANT_BOOT_PARAM_FLAGS & PARAM_FLAGS_ENTER_BOOT_Msk) >> PARAM_FLAGS_ENTER_BOOT_Pos;
    }

    return val;
}

static void enter_boot_set (uint32_t value)
{
    uint32_t ant_boot_param_flags = *ANT_BOOT_PARAM_FLAGS;

    uint32_t enter_boot = (ant_boot_param_flags >> PARAM_FLAGS_ENTER_BOOT_Pos) & PARAM_FLAGS_ENTER_BOOT_Msk;
    if (enter_boot == value)
    {
        return; // no need to rewrite the same value.
    }

    ant_boot_param_flags &= ~PARAM_FLAGS_ENTER_BOOT_Msk;
    ant_boot_param_flags |= value << PARAM_FLAGS_ENTER_BOOT_Pos;
  
    uint32_t err_code = blocking_flash_word_write(ANT_BOOT_PARAM_FLAGS, ant_boot_param_flags);
    APP_ERROR_CHECK(err_code);
}

static void enter_boot_update (void)
{
    const bootloader_settings_t * p_bootloader_settings;

    bootloader_util_settings_get(&p_bootloader_settings);

   if(p_bootloader_settings->ap_image.st.bank == NEW_IMAGE_BANK_0 || p_bootloader_settings->ap_image.st.bank == NEW_IMAGE_BANK_1)
   {
       enter_boot_set(PARAM_FLAGS_ENTER_BOOT_BypassDone);
   }
   else
   {
       if (p_bootloader_settings->valid_app != BOOTLOADER_SETTINGS_INVALID_APPLICATION)
       {
           enter_boot_set(PARAM_FLAGS_ENTER_BOOT_BypassDone);
           return;
       }
       enter_boot_set(PARAM_FLAGS_ENTER_BOOT_EnterBoot);
   }

   // If the current application has been invalidated, then application's self protection is of no use now.
   // Lets clear it.
   if (p_bootloader_settings->valid_app == BOOTLOADER_SETTINGS_INVALID_APPLICATION)
   {
       if(*ANT_BOOT_APP_SIZE != APP_SIZE_Empty)
       {
            uint32_t err_code = blocking_flash_word_write(ANT_BOOT_APP_SIZE, APP_SIZE_Clear);
            APP_ERROR_CHECK(err_code);
       }
   }
}

/**@brief Function for application main entry.
 */
int main(void)
{
    uint32_t err_code;
    bool     bootloader_is_pushed = false;

#if defined (ENABLE_IO_LED)
    leds_init();
#endif // ENABLE_IO_LED
#if defined (ENABLE_BUTTON)
    buttons_init();
#endif

#if defined (DEBUG_DFU_BOOTLOADER)
    NRF_GPIO->DIRSET = 0x40000908;  //stack debugging
    DBG_PIN_DIR_INIT;
#endif //DEBUG_DFU_BOOTLOADER
#if defined (DBG_DFU_BOOTLOADER_PATH)
    DEBUG_PIN_FALL(DBG_DFU_BOOTLOADER_PATH);
#endif //DBG_DFU_BOOTLOADER_PATH

    // This check ensures that the defined fields in the bootloader corresponds with actual
    // setting in the chip.
    APP_ERROR_CHECK_BOOL(*((uint32_t *)NRF_UICR_BOOT_START_ADDRESS) == BOOTLOADER_REGION_START);
    APP_ERROR_CHECK_BOOL(NRF_FICR->CODEPAGESIZE == CODE_PAGE_SIZE);

#if !defined (S210_V3_STACK)
    sd_mbr_command_t com = {SD_MBR_COMMAND_INIT_SD, };

    err_code = sd_mbr_command(&com);
    APP_ERROR_CHECK(err_code);

    err_code = sd_softdevice_vector_table_base_set(BOOTLOADER_REGION_START);
    APP_ERROR_CHECK(err_code);

    err_code = bootloader_dfu_sd_update_continue();
    APP_ERROR_CHECK(err_code);

    err_code = bootloader_dfu_bl_update_continue();
    APP_ERROR_CHECK(err_code);
#endif // !S210_V3_STACK

    // Initialize.
    timers_init();
    (void)bootloader_init();
    ant_stack_init();
    scheduler_init();

#if defined (ENABLE_BUTTON)
    // Push button switch
    bootloader_is_pushed = ((nrf_gpio_pin_read(BOOTLOADER_BUTTON_PIN) == 0) ? true: false);
    if (bootloader_is_pushed)
    {
        enter_boot_set(PARAM_FLAGS_ENTER_BOOT_EnterBoot);
    }
#endif // ENABLE_BUTTON

    if ((enter_boot_get() == PARAM_FLAGS_ENTER_BOOT_EnterBoot)  ||
        (bootloader_is_pushed)                                  ||
        (!bootloader_app_is_valid(DFU_BANK_0_REGION_START)))
    {
        #if defined (DBG_DFU_BOOTLOADER_PATH)
        DEBUG_PIN_FALL(DBG_DFU_BOOTLOADER_PATH);DEBUG_PIN_FALL(DBG_DFU_BOOTLOADER_PATH);
        #endif //DBG_DFU_BOOTLOADER_PATH

#if defined (ENABLE_IO_LED)
        leds_off();
        nrf_gpio_pin_clear(BOOTLOADER_ACTIVE_LED);
#endif // ENABLE_IO_LED

        // Initiate an update of the firmware.
        err_code = bootloader_dfu_start();
        APP_ERROR_CHECK(err_code);

        enter_boot_update();
    }

#if defined (ENABLE_IO_LED)
        leds_off();
#endif // ENABLE_IO_LED

    err_code = bootloader_dfu_ap_update_continue();
    APP_ERROR_CHECK(err_code);

    if (bootloader_app_is_valid(DFU_BANK_0_REGION_START))
    {
        #if defined (DBG_DFU_BOOTLOADER_PATH)
        DEBUG_PIN_FALL(DBG_DFU_BOOTLOADER_PATH);DEBUG_PIN_FALL(DBG_DFU_BOOTLOADER_PATH);DEBUG_PIN_FALL(DBG_DFU_BOOTLOADER_PATH);
        #endif //DBG_DFU_BOOTLOADER_PATH
        // Select a bank region to use as application region.
        // @note: Only applications running from DFU_BANK_0_REGION_START is supported.
        bootloader_app_start(DFU_BANK_0_REGION_START);
    }

#if defined (DBG_DFU_BOOTLOADER_PATH)
DEBUG_PIN_FALL(DBG_DFU_BOOTLOADER_PATH);DEBUG_PIN_FALL(DBG_DFU_BOOTLOADER_PATH);DEBUG_PIN_FALL(DBG_DFU_BOOTLOADER_PATH);DEBUG_PIN_FALL(DBG_DFU_BOOTLOADER_PATH);
#endif //DBG_DFU_BOOTLOADER_PATH
    NVIC_SystemReset();
}
