/**
 * 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 "nrf_serial_dfu.h"

#include <string.h>
#include "boards.h"
#include "app_util_platform.h"
#include "nrf_dfu_transport.h"
#include "nrf_dfu_req_handler.h"
#include "slip.h"
#include "nrf_log.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 serial_dfu_t m_dfu;


DFU_TRANSPORT_REGISTER(nrf_dfu_transport_t const dfu_trans) =
{
    .init_func  = serial_dfu_transport_init,
    .close_func = serial_dfu_transport_close
};


ANON_UNIONS_ENABLE

typedef struct
{
    uint8_t            op_code;
    nrf_dfu_res_code_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;
    };

} serial_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(serial_dfu_t          * p_dfu,
                          serial_dfu_response_t * p_response)
{
    static uint8_t encoded_response[(2 * MAX_RESPONSE_SIZE) + 1] = {0};
    uint8_t response_buffer[MAX_RESPONSE_SIZE] = {0};
    uint32_t encoded_response_length = 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]);
                index += uint32_encode(p_response->select_response.crc, &response_buffer[index]);
                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);
    }

    // encode into slip
    (void)slip_encode(encoded_response, response_buffer, index, &encoded_response_length);

    // send
    (void)nrf_drv_uart_tx(&p_dfu->uart_instance, encoded_response, encoded_response_length);
}

static void on_packet_received(serial_dfu_t * p_dfu)
{
    nrf_dfu_req_t dfu_req;
    nrf_dfu_res_t dfu_res = {{{0}}};
    serial_dfu_response_t serial_response = {0};

    memset(&dfu_req, 0, sizeof(nrf_dfu_req_t));

    const serial_dfu_op_code_t op_code             = (serial_dfu_op_code_t)p_dfu->recv_buffer[0];
    const uint16_t             packet_payload_len  = p_dfu->slip.current_index - 1;
    uint8_t * p_payload                            = &p_dfu->recv_buffer[1];

    serial_response.op_code = 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.obj_type =  p_payload[0];

            // Get length value
            dfu_req.object_size = uint32_decode(&p_payload[1]);

            // Set req type
            dfu_req.req_type = NRF_DFU_OBJECT_OP_CREATE;

            serial_response.resp_val = nrf_dfu_req_handler_on_req(NULL, &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.req_type =  NRF_DFU_OBJECT_OP_CRC;

            serial_response.resp_val            = nrf_dfu_req_handler_on_req(NULL, &dfu_req, &dfu_res);
            serial_response.crc_response.offset = dfu_res.offset;
            serial_response.crc_response.crc    = dfu_res.crc;
            break;

        case SERIAL_DFU_OP_CODE_EXECUTE_OBJECT:
            NRF_LOG_DEBUG("Received execute object");

            // Set req type
            dfu_req.req_type =  NRF_DFU_OBJECT_OP_EXECUTE;

            serial_response.resp_val = nrf_dfu_req_handler_on_req(NULL, &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.obj_type = p_payload[0];

            dfu_req.req_type = NRF_DFU_OBJECT_OP_SELECT;

            serial_response.resp_val = nrf_dfu_req_handler_on_req(NULL, &dfu_req, &dfu_res);
            serial_response.select_response.max_size = dfu_res.max_size;
            serial_response.select_response.offset   = dfu_res.offset;
            serial_response.select_response.crc      = dfu_res.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.req_type =  NRF_DFU_OBJECT_OP_WRITE;

            // Set data and length
            dfu_req.p_req   = &p_payload[0];
            dfu_req.req_len = packet_payload_len;

            serial_response.resp_val = nrf_dfu_req_handler_on_req(NULL, &dfu_req, &dfu_res);
            if (serial_response.resp_val != NRF_DFU_RES_CODE_SUCCESS)
            {
                NRF_LOG_ERROR("Failure to run packet write");
            }

            // Check if a packet receipt notification is needed to be sent.
            if (p_dfu->pkt_notif_target != 0 && --p_dfu->pkt_notif_target_count == 0)
            {
                serial_response.op_code             = SERIAL_DFU_OP_CODE_CALCULATE_CRC;
                serial_response.crc_response.offset = dfu_res.offset;
                serial_response.crc_response.crc    = dfu_res.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);
    }
}


static __INLINE void on_rx_complete(serial_dfu_t * p_dfu, uint8_t * p_data, uint8_t len)
{
    ret_code_t ret_code;

    ret_code = slip_decode_add_byte(&p_dfu->slip, p_data[0]);

    if (ret_code == NRF_SUCCESS)
    {
        on_packet_received(p_dfu);

        // reset the slip decoding
        p_dfu->slip.current_index = 0;
        p_dfu->slip.state = SLIP_STATE_DECODING;
    }

    (void)nrf_drv_uart_rx(&m_dfu.uart_instance, &m_dfu.uart_buffer, 1);
}


static void uart_event_handler(nrf_drv_uart_event_t * p_event, void * p_context)
{
    switch (p_event->type)
    {
        case NRF_DRV_UART_EVT_TX_DONE:
            nrf_dfu_req_handler_reset_if_dfu_complete();
            break;

        case NRF_DRV_UART_EVT_RX_DONE:
            on_rx_complete((serial_dfu_t*)p_context, p_event->data.rxtx.p_data, p_event->data.rxtx.bytes);
            break;

        case NRF_DRV_UART_EVT_ERROR:
            APP_ERROR_HANDLER(p_event->data.error.error_mask);
            break;
    }
}


uint32_t serial_dfu_transport_init(void)
{
    uint32_t err_code;

    leds_init();

    m_dfu.slip.p_buffer      = m_dfu.recv_buffer;
    m_dfu.slip.current_index = 0;
    m_dfu.slip.buffer_len    = sizeof(m_dfu.recv_buffer);
    m_dfu.slip.state         = SLIP_STATE_DECODING;

    nrf_drv_uart_config_t uart_config = NRF_DRV_UART_DEFAULT_CONFIG;

    uart_config.pseltxd   = TX_PIN_NUMBER;
    uart_config.pselrxd   = RX_PIN_NUMBER;
    uart_config.pselcts   = CTS_PIN_NUMBER;
    uart_config.pselrts   = RTS_PIN_NUMBER;
    uart_config.hwfc      = NRF_UART_HWFC_ENABLED;
    uart_config.p_context = &m_dfu;


    nrf_drv_uart_t instance =  NRF_DRV_UART_INSTANCE(0);
    memcpy(&m_dfu.uart_instance, &instance, sizeof(instance));

    err_code =  nrf_drv_uart_init(&m_dfu.uart_instance, &uart_config, uart_event_handler);
    if (err_code != NRF_SUCCESS)
    {
        NRF_LOG_ERROR("Failed initializing uart");
        return err_code;
    }

    nrf_drv_uart_rx_enable(&m_dfu.uart_instance);

    err_code = nrf_drv_uart_rx(&m_dfu.uart_instance, &m_dfu.uart_buffer, 1);
    if (err_code != NRF_SUCCESS)
    {
        NRF_LOG_ERROR("Failed initializing rx");
    }

    NRF_LOG_DEBUG("UART initialized");

    return err_code;
}


uint32_t serial_dfu_transport_close(void)
{
    nrf_drv_uart_uninit(&m_dfu.uart_instance);
    return NRF_SUCCESS;
}
