/***************************************************************************
 *
 * Copyright 2015-2019 BES.
 * All rights reserved. All unpublished rights reserved.
 *
 * No part of this work may be used or reproduced in any form or by any
 * means, or stored in a database or retrieval system, without prior written
 * permission of BES.
 *
 * Use of this work is governed by a license granted by BES.
 * This work contains confidential and proprietary information of
 * BES. which is protected by copyright, trade secret,
 * trademark and other intellectual property rights.
 *
 ****************************************************************************/

#include "string.h"
#include "cmsis_os.h"
#include "hal_trace.h"
#include "hal_timer.h"
#include "apps.h"
#include "stdbool.h"
#include "app_tota.h" 
#include "app_tota_cmd_code.h"
#include "app_tota_cmd_handler.h"
#include "app_tota_data_handler.h"
#include "app_utils.h"
#include "crc32.h"
#include "app_spp_tota.h"
#include "app_spp_tota_general_service.h"
#include "tota_buffer_manager.h"
#include "tota_stream_data_transfer.h"
#include "app_tota_conn.h"

/* 
**  stream control strcut
**  stream packet format:
**  # header + body #
**  > header: 2   bytes
**  > body  : 664 bytes
**  
**  note: stream will never be encrypted!
*/
typedef struct{
    uint32_t flush_bytes;
    bool     is_streaming;
    uint8_t  module;
    
    osMutexId mutex;
    osSemaphoreId sem;
}stream_control_t;

stream_control_t stream_control;

osMutexDef (tota_tx_buf_mutex);
osMutexId   tota_tx_buf_mutex_id;

#define SPP_BUFFER_NUM          5

static uint8_t spp_tx_buffer[MAX_SPP_PACKET_SIZE*SPP_BUFFER_NUM];
static uint8_t tx_buf_index = 0;


/* static function */
static bool _tota_send_stream_packet(uint8_t * pdata, uint32_t dataLen);
static void _tota_stream_data_init();
static void _update_tx_buf();
static uint8_t * _get_tx_buf_ptr();

// Semaphore
osSemaphoreDef(app_tota_send_data_sem);

// define stream data transfer thread
#define TOTA_STREAM_DATA_STACK_SIZE             2048
static void tota_stream_data_transfer_thread(void const *argument);
osThreadId tota_stream_thread_tid;
osThreadDef(tota_stream_data_transfer_thread, osPriorityHigh, 1, TOTA_STREAM_DATA_STACK_SIZE, "TOTA_STREAM_DATA_THREAD");

static void tota_stream_data_transfer_thread(void const *argument)
{
    // TODO: max speed
    uint8_t buf[MAX_SPP_PACKET_SIZE];
    while ( true )
    {
        app_sysfreq_req(APP_SYSFREQ_USER_OTA, APP_SYSFREQ_32K);
        osSignalWait(0x0001, osWaitForever);
        app_sysfreq_req(APP_SYSFREQ_USER_OTA, APP_SYSFREQ_208M);
        while ( tota_stream_buffer_read(buf + STREAM_HEADER_SIZE, MAX_SPP_PACKET_SIZE - STREAM_HEADER_SIZE) )
        {
            _tota_send_stream_packet(buf, MAX_SPP_PACKET_SIZE);
        }
    }
}

void app_tota_stream_data_transfer_init()
{
    stream_control.sem     = osSemaphoreCreate(osSemaphore(app_tota_send_data_sem), SPP_BUFFER_NUM);
    tota_stream_thread_tid = osThreadCreate(osThread(tota_stream_data_transfer_thread), NULL);
    _tota_stream_data_init();
}

// stream start
void app_tota_stream_data_start(uint16_t set_module)
{
    stream_control.module       = set_module;
    stream_control.is_streaming = true;
}

// stream end
void app_tota_stream_data_end()
{
    stream_control.is_streaming = false;
}

// stream send data while stream is start
bool app_tota_send_stream_data(uint8_t * pdata, uint32_t dataLen)
{
    if ( !stream_control.is_streaming )
    {
        TOTA_LOG_DBG(0, "error: data stream not start.");
        return false;
    }
    if ( tota_stream_buffer_write(pdata, dataLen) )
    {
        TOTA_LOG_DBG(0, "send to stream buffer ok.");
        return true;
    }
    else
    {
        TOTA_LOG_DBG(0, "send to stream buffer error.");
        return false;
    }
}

// stream flush
void app_tota_stream_data_flush()
{
    tota_stream_buffer_flush();
}

// stream clean
void app_tota_stream_data_clean()
{
    tota_stream_buffer_clean();
}


bool app_tota_send_data_via_spp(uint8_t * pdata, uint32_t dataLen)
{
    osSemaphoreWait(stream_control.sem, osWaitForever);
    osMutexRelease(tota_tx_buf_mutex_id);
    uint8_t *   pbuf    = pdata;
    uint32_t    bufLen  = dataLen;
#if TOTA_ENCODE
    if ( ((uint16_t *)pdata)[0] == OP_TOTA_STRING )
    {
        TOTA_LOG_DBG(0, "yeah! This is a string. Do not encrypt");
    }
    else if ( is_tota_connected() )
    {
        pbuf = tota_encrypt_packet(pdata, dataLen, &bufLen);
    }
#endif
    memcpy(_get_tx_buf_ptr(), pbuf, bufLen);

    if ( app_spp_tota_send_data(_get_tx_buf_ptr(), bufLen) )
    {
        _update_tx_buf();
        osMutexRelease(tota_tx_buf_mutex_id);
        return true;
    }
    else
    {
        osMutexRelease(tota_tx_buf_mutex_id);
        osSemaphoreRelease(stream_control.sem);
        return false;
    }

}

void app_tota_tx_done_callback()
{
    osSemaphoreRelease(stream_control.sem);
}

bool is_stream_data_running()
{
    return stream_control.is_streaming;
}

// send stream packet with header. packet size:666
static bool _tota_send_stream_packet(uint8_t * pdata, uint32_t dataLen)
{
    osSemaphoreWait(stream_control.sem, osWaitForever);
    osMutexRelease(tota_tx_buf_mutex_id);
    ((uint16_t *)pdata)[0] = (OP_TOTA_STREAM_DATA - stream_control.module);
    memcpy(_get_tx_buf_ptr(), pdata, dataLen);
    if ( app_spp_tota_send_data(_get_tx_buf_ptr(), dataLen) )
    {
        _update_tx_buf();
        osMutexRelease(tota_tx_buf_mutex_id);
        return true;
    }
    else
    {
        osMutexRelease(tota_tx_buf_mutex_id);
        osSemaphoreRelease(stream_control.sem);
        return false;
    }
}

static void _tota_stream_data_init()
{
    tota_stream_buffer_init(tota_stream_thread_tid);
    stream_control.module       = 0;
    stream_control.is_streaming = false;
}

static void _update_tx_buf()
{
    tx_buf_index = (tx_buf_index + 1) % 5;
}


static uint8_t * _get_tx_buf_ptr()
{
    return (spp_tx_buffer + tx_buf_index*MAX_SPP_PACKET_SIZE);
}


