/**
 * Copyright (c) 2016 - 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 <string.h>
#include <stdlib.h>
#include "boards.h"
#include "app_util_platform.h"
#include "nrf_dfu_transport.h"
#include "nrf_dfu_req_handler.h"
#include "nrf_dfu_handling_error.h"
#include "sdk_config.h"
#include "nrf_delay.h"

#include "nrf_log.h"
#include "nrf_drv_spis.h"
#include "nrf_spis_dfu.h"
#define AVAILABLE_LED_PIN_NO            BSP_LED_0                                                   /**< Is on when serial DFU transport is enabled (but not connected). */
#define CONNECTED_LED_PIN_NO            BSP_LED_1                                                   /**< Is on when device has connected. */


#define CREATE_OBJECT_REQUEST_LEN       (sizeof(uint8_t)+sizeof(uint32_t))
#define SET_RECEIPT_NOTIF_REQUEST_LEN   (sizeof(uint16_t))
#define CALCULATE_CRC_REQUEST_LEN       0
#define EXECUTE_OBJECT_REQUEST_LEN      0
#define SELECT_OBJECT_REQUEST_LEN       (sizeof(uint8_t))
#define GET_SERIAL_MTU_REQUEST_LEN      0

#define MAX_RESPONSE_SIZE               (1+1+3*4)

static spi_dfu_t m_dfu;
#define SPIS_INSTANCE 1 /**< SPIS instance index. */
static const nrf_drv_spis_t spis = NRF_DRV_SPIS_INSTANCE(SPIS_INSTANCE);/**< SPIS instance. */
static volatile bool spis_xfer_done; /**< Flag used to indicate that SPIS instance completed the transfer. */

static uint8_t spi_rx_buffer[128+1];
static const uint8_t m_length = sizeof(spi_rx_buffer);        /**< Transfer length. */
static uint8_t       m_tx_buf[MAX_RESPONSE_SIZE];          /**< TX buffer. */


DFU_TRANSPORT_REGISTER(nrf_dfu_transport_t const dfu_trans) =
{
    .init_func  = spis_dfu_transport_init,
    .close_func = spis_dfu_transport_close
};


ANON_UNIONS_ENABLE;

typedef struct
{
    uint8_t            op_code;
    nrf_dfu_result_t resp_val;
    union
    {
        struct
        {
            uint32_t offset;
            uint32_t crc;
        }crc_response;

        struct
        {
            uint32_t max_size;
            uint32_t offset;
            uint32_t crc;
        }select_response;

        struct
        {
            uint16_t mtu;
        }serial_mtu_response;

        struct
        {
            uint8_t ping_id;
        }ping_response;
    };

}spi_dfu_response_t;

ANON_UNIONS_DISABLE;


/**@brief       Function for the LEDs initialization.
 *
 * @details     Initializes all LEDs used by this application.
 */
static void leds_init(void)
{
    nrf_gpio_cfg_output(AVAILABLE_LED_PIN_NO);
    nrf_gpio_cfg_output(CONNECTED_LED_PIN_NO);
    nrf_gpio_pin_clear(AVAILABLE_LED_PIN_NO);
    nrf_gpio_pin_set(CONNECTED_LED_PIN_NO);
}

static void response_send(spi_dfu_t          * p_dfu,
                          spi_dfu_response_t * p_response)
{
    uint8_t response_buffer[MAX_RESPONSE_SIZE] = {0};
    uint16_t index = 0;

    NRF_LOG_DEBUG("Sending Response: [0x%01x, 0x%01x]", p_response->op_code, p_response->resp_val);

    response_buffer[index++] = SERIAL_DFU_OP_CODE_RESPONSE;

    // Encode the Request Op code
    response_buffer[index++] = p_response->op_code;

    // Encode the Response Value.
    response_buffer[index++] = (uint8_t)p_response->resp_val;

    if (p_response->resp_val == NRF_DFU_RES_CODE_SUCCESS)
    {
        switch (p_response->op_code)
        {
            case SERIAL_DFU_OP_CODE_CALCULATE_CRC:
                index += uint32_encode(p_response->crc_response.offset, &response_buffer[index]);
                index += uint32_encode(p_response->crc_response.crc, &response_buffer[index]);
                break;

            case SERIAL_DFU_OP_CODE_SELECT_OBJECT:
                index += uint32_encode(p_response->select_response.max_size, &response_buffer[index]);
                index += uint32_encode(p_response->select_response.offset, &response_buffer[index-1]);
                index += uint32_encode(p_response->select_response.crc, &response_buffer[index-1]);
                break;

            case SERIAL_DFU_OP_CODE_GET_SERIAL_MTU:
                index += uint16_encode(p_response->serial_mtu_response.mtu, &response_buffer[index]);
                break;

            case SERIAL_DFU_OP_CODE_PING:
                response_buffer[index] = p_response->ping_response.ping_id;
                index += sizeof(uint8_t);
                break;

            default:
                // no implementation
                break;
        }
    }
    else if (p_response->resp_val == NRF_DFU_RES_CODE_EXT_ERROR)
    {
        response_buffer[index++] = ext_error_get();
        // Clear the last extended error code
        (void) ext_error_set(NRF_DFU_EXT_ERROR_NO_ERROR);
    }

    // send
      memcpy(m_tx_buf,response_buffer,index);
      nrf_drv_spis_buffers_set(&spis, m_tx_buf, index, spi_rx_buffer, m_length);
      //NRF_LOG_DEBUG("response send spi_rx_buffer[0]: 0x%X",spi_rx_buffer[0]);
 
}

static void on_packet_received(spi_dfu_t * p_dfu)
{   
    //NRF_LOG_DEBUG("Received: %p",p_dfu);
    nrf_dfu_request_t dfu_req;
    nrf_dfu_response_t dfu_res = {0};
    spi_dfu_response_t serial_response = {0};
    
    //NRF_LOG_DEBUG("payload length: %i",p_dfu->recv_length);

    memset(&dfu_req, 0, sizeof(nrf_dfu_request_t));

    const serial_dfu_op_code_t op_code             = (serial_dfu_op_code_t)p_dfu->recv_buffer[0];
    //for(int loop = 0; loop < 5; loop++)
      //  NRF_LOG_DEBUG("recv_buffer[%i]: 0x%X", loop, p_dfu->recv_buffer[loop]);
    const uint16_t             packet_payload_len  = p_dfu->recv_length;
    NRF_LOG_DEBUG("payload length: %i",packet_payload_len);
    uint8_t * p_payload                            = &p_dfu->recv_buffer[1];

    serial_response.op_code = op_code;

    NRF_LOG_DEBUG("Opcode received: 0x%x",op_code);

    nrf_gpio_pin_clear(CONNECTED_LED_PIN_NO);
    nrf_gpio_pin_set(AVAILABLE_LED_PIN_NO);
    switch (op_code)
    {
        case SERIAL_DFU_OP_CODE_CREATE_OBJECT:

            if (packet_payload_len != CREATE_OBJECT_REQUEST_LEN)
            {
                serial_response.resp_val = NRF_DFU_RES_CODE_INVALID_PARAMETER;
                break;
            }

            NRF_LOG_DEBUG("Received create object");

            // Reset the packet receipt notification on create object
            p_dfu->pkt_notif_target_count = p_dfu->pkt_notif_target;

            // Get type parameter
            dfu_req.create.object_type =  p_payload[0];

            // Get length value
            dfu_req.create.object_size = uint32_decode(&p_payload[1]);

            // Set req type
            dfu_req.request = NRF_DFU_OP_OBJECT_CREATE;

            serial_response.resp_val = nrf_dfu_req_handler_on_req(&dfu_req, &dfu_res);
            break;

        case SERIAL_DFU_OP_CODE_SET_RECEIPT_NOTIF:
            NRF_LOG_DEBUG("Set receipt notif");
            if (packet_payload_len != SET_RECEIPT_NOTIF_REQUEST_LEN)
            {
                serial_response.resp_val = NRF_DFU_RES_CODE_INVALID_PARAMETER;
                break;
            }

            p_dfu->pkt_notif_target       = uint16_decode(&p_payload[0]);
            p_dfu->pkt_notif_target_count = p_dfu->pkt_notif_target;

            serial_response.resp_val = NRF_DFU_RES_CODE_SUCCESS;
            break;

        case SERIAL_DFU_OP_CODE_CALCULATE_CRC:
            NRF_LOG_DEBUG("Received calculate CRC");

            dfu_req.request =  NRF_DFU_OP_CRC_GET;

            serial_response.resp_val            = nrf_dfu_req_handler_on_req(&dfu_req, &dfu_res);
            serial_response.crc_response.offset = dfu_res.crc.offset;
            serial_response.crc_response.crc    = dfu_res.crc.crc;
            break;

        case SERIAL_DFU_OP_CODE_EXECUTE_OBJECT:
            NRF_LOG_DEBUG("Received execute object");

            // Set req type
            dfu_req.request =  NRF_DFU_OP_OBJECT_EXECUTE;

            serial_response.resp_val =nrf_dfu_req_handler_on_req(&dfu_req, &dfu_res);
            break;

        case SERIAL_DFU_OP_CODE_SELECT_OBJECT:

            NRF_LOG_DEBUG("Received select object");
            if (packet_payload_len != SELECT_OBJECT_REQUEST_LEN)
            {
                serial_response.resp_val = NRF_DFU_RES_CODE_INVALID_PARAMETER;
                break;
            }

            // Set object type to read info about
            dfu_req.select.object_type = p_payload[0];

            dfu_req.request = NRF_DFU_OP_OBJECT_SELECT;

            serial_response.resp_val = nrf_dfu_req_handler_on_req(&dfu_req, &dfu_res);
            serial_response.select_response.max_size = dfu_res.select.max_size;
            serial_response.select_response.offset   = dfu_res.select.offset;
            serial_response.select_response.crc      = dfu_res.select.crc;
            break;

        case SERIAL_DFU_OP_CODE_GET_SERIAL_MTU:
            NRF_LOG_DEBUG("Received get serial mtu");

            serial_response.resp_val = NRF_DFU_RES_CODE_SUCCESS;
            serial_response.serial_mtu_response.mtu = sizeof(p_dfu->recv_buffer);
            break;

        case SERIAL_DFU_OP_CODE_WRITE_OBJECT:
             // Set req type
            dfu_req.request =   NRF_DFU_OP_OBJECT_WRITE;
            NRF_LOG_DEBUG("dfu request: %X", dfu_req.request);

            // Set data and length
            dfu_req.write.p_data   = &p_payload[0];
            NRF_LOG_DEBUG("write.p_data: 0x%x", dfu_req.write.p_data);
            //for(int loop = 0; loop<sizeof(p_payload); loop++)
                  //NRF_LOG_DEBUG("dfu request write p_payload[%i]: 0x%X", loop,p_payload[loop]);
            dfu_req.write.len = packet_payload_len;
            NRF_LOG_DEBUG("dfu request write length: 0x%X", packet_payload_len);

            serial_response.resp_val = nrf_dfu_req_handler_on_req(&dfu_req, &dfu_res);
            if (serial_response.resp_val != NRF_DFU_RES_CODE_SUCCESS)
            {
                NRF_LOG_ERROR("Failure to run packet write");
            }

                serial_response.op_code             = SERIAL_DFU_OP_CODE_CALCULATE_CRC;
                serial_response.crc_response.offset = dfu_res.crc.offset;
                serial_response.crc_response.crc    = dfu_res.crc.crc;

                // Reset the counter for the number of firmware packets.
                p_dfu->pkt_notif_target_count = p_dfu->pkt_notif_target;

                response_send(p_dfu, &serial_response);
            
           
            break;

        case SERIAL_DFU_OP_CODE_PING:
            if (packet_payload_len != sizeof(serial_response.ping_response))
            {
                serial_response.resp_val = NRF_DFU_RES_CODE_INVALID_PARAMETER;
                break;
            }

            serial_response.resp_val = NRF_DFU_RES_CODE_SUCCESS;
            serial_response.ping_response.ping_id = p_payload[0];

            NRF_LOG_DEBUG("Received ping %d", p_payload[0]);
            break;

        default:
            // Unsupported op code.
            NRF_LOG_WARNING("Received unsupported OP code");
            serial_response.resp_val = NRF_DFU_RES_CODE_OP_CODE_NOT_SUPPORTED;
            break;
    }

    if (op_code != SERIAL_DFU_OP_CODE_WRITE_OBJECT)
    {
        response_send(p_dfu, &serial_response);
    }
   
}



/**
 * @brief SPIS user event handler.
 *
 * @param event
 */
void spis_event_handler(nrf_drv_spis_event_t event)
{
    if (event.evt_type == NRF_DRV_SPIS_XFER_DONE)
    {
        //spis_xfer_done = true;
        NRF_LOG_INFO(" Transfer completed. Received: %d bytes",event.rx_amount);
        NRF_LOG_DEBUG("event handler spi_rx_buffer[0]: 0x%X",spi_rx_buffer[0]);

        if ((event.rx_amount)&&(spi_rx_buffer[0]!=0xFF))
        {
            memcpy(m_dfu.recv_buffer,spi_rx_buffer,event.rx_amount);
            m_dfu.recv_length= event.rx_amount-1;
            on_packet_received(&m_dfu);
        }
         
        else{
             NRF_LOG_INFO(" No command received start RX again");
             nrf_drv_spis_buffers_set(&spis, m_tx_buf, 0, spi_rx_buffer, m_length);
            
             //Could be the last response, check if dfu is waiting for a reset
             //nrf_dfu_req_handler_reset_if_dfu_complete();
        }
    }
    
    if (event.evt_type == NRF_DRV_SPIS_BUFFERS_SET_DONE)
    {
        
    }
    
        
}

uint32_t spis_dfu_transport_init(void)
{
    uint32_t err_code;
    
    leds_init();
    
    // Enable the constant latency sub power mode to minimize the time it takes
    // for the SPIS peripheral to become active after the CSN line is asserted
    // (when the CPU is in sleep mode).
    NRF_POWER->TASKS_CONSTLAT = 1;
    nrf_drv_spis_config_t spis_config = NRF_DRV_SPIS_DEFAULT_CONFIG;
    spis_config.csn_pin               = APP_SPIS_CS_PIN;
    spis_config.miso_pin              = APP_SPIS_MISO_PIN;
    spis_config.mosi_pin              = APP_SPIS_MOSI_PIN;
    spis_config.sck_pin               = APP_SPIS_SCK_PIN;

    err_code = nrf_drv_spis_init(&spis, &spis_config, spis_event_handler);

    if (err_code != NRF_SUCCESS)
    {
        NRF_LOG_ERROR("Failed initializing SPIS");
        return err_code;
    }
    //Start RX
    //NRF_LOG_DEBUG("dfu transport spi_rx_buffer[0]: 0x%X",spi_rx_buffer[0]);
    err_code = nrf_drv_spis_buffers_set(&spis, m_tx_buf, 0, spi_rx_buffer, m_length);
    APP_ERROR_CHECK(err_code);
    return NRF_SUCCESS;
}


uint32_t spis_dfu_transport_close(void)
{
    NRF_POWER->TASKS_LOWPWR = 1;
    nrf_drv_spis_uninit(&spis);
    return NRF_SUCCESS;
}
