/**************************************************************************
 * Copyright (C) 2012-2021  Unisound
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 **************************************************************************
 *
 * Description : uni_uart_record.c
 * Author      : yzs.unisound.com
 * Date        : 2021.03.01
 *
 **************************************************************************/
#include <autoconf.h>
#include <types.h>

#include <lvp_queue.h>
#include <lvp_board.h>
#include <lvp_buffer.h>
#include <driver/uni_uart.h>
#include <driver/uni_cache.h>
#include <driver/uni_padmux.h>

#include "fakelibc.h"
#include "uni_communication.h"
#include "board_misc_config.h"

#define LOG_TAG "[UNI_RECORD]"

#define CONFIG_LVP_UART_RECORD_PORT_BAUDRATE 1500000
#define CONFIG_LVP_UART_RECORD_PORT          MESSAGE_UART

typedef enum {
  CHNL_MESSAGE_CHALLENGE_PACK = 1,
  CHNL_MESSAGE_CHALLENGE_PACK_ACK,
  CHNL_MESSAGE_NETWORK_REQUEST,
  CHNL_MESSAGE_NETWORK_RESPONSE,
  CHNL_MESSAGE_NOISE_REDUCTION_RAW_DATA,
  CHNL_MESSAGE_RASR_RESULT,
  CHNL_MESSAGE_AWAKEN_REQUEST,
  CHNL_MESSAGE_RECOGNIZE_REQUEST,
  CHNL_MESSAGE_PULL_NOISE_REDUCTION_DATA_REQUEST,
  CHNL_MESSAGE_LOGFBANK_DATA,
} ChnlMessageType;

typedef struct {
  unsigned int type; /* CHNL_MESSAGE_PULL_NOISE_REDUCTION_DATA_REQUEST */
  unsigned int mode; /* 1开始接收数据，0停止接收数据 */
} __attribute__((packed)) ChnlPullNoiseReductionDataReq;

typedef struct {
  unsigned char *payload;
  unsigned short payload_len;
  unsigned int type;
} IVM_SEND_PACK;

typedef struct {
  unsigned char *payload;
  int payload_len;
  unsigned char *buf;
} HeaderCbData;

static volatile unsigned char is_send_done = true;
static unsigned char s_push_data = 0;
static unsigned char s_ivm_uart_recv_buffer[64]
  __attribute__((aligned(16))) = {0};
static const int s_record_port = CONFIG_LVP_UART_RECORD_PORT;

#define IVM_UART_SEND_QUENE_LEN 8
static unsigned char s_ivm_uart_send_pack_queue_buffer[IVM_UART_SEND_QUENE_LEN *
                                                       sizeof(IVM_SEND_PACK)];
static LVP_QUEUE s_ivm_uart_send_pack_queue;

static void __hook_init(CommProtocolHooks *hooks) {
  /* 注册动态内存分配Hooks */
  hooks->malloc_fn = uni_malloc;
  hooks->free_fn = uni_free;
  hooks->realloc_fn = uni_realloc;

  /* 注册sleep Hook，以毫秒为单位，精度1ms */
  hooks->msleep_fn = NULL;

  /* 注册信号量相关的Hook */
  hooks->sem_alloc_fn = NULL;
  hooks->sem_destroy_fn = NULL;
  hooks->sem_init_fn = NULL;
  hooks->sem_post_fn = NULL;
  hooks->sem_wait_fn = NULL;
  hooks->sem_timedwait_fn = NULL;
}

static int _UniUartAsyncSendCallbackPayload(int port, void *priv) {
  is_send_done = true;
  return 0;
}

static int _UniUartAsyncSendCallback(int port, void *priv) {
  HeaderCbData *cb_data = (HeaderCbData *)priv;
  if (cb_data->buf != NULL) {
    uni_free(cb_data->buf);
  }
  if (cb_data->payload != NULL && cb_data->payload_len > 0) {
    uni_uart_async_send_buffer(s_record_port, (unsigned char *)cb_data->payload,
                              cb_data->payload_len,
                              _UniUartAsyncSendCallbackPayload, NULL);
  }
  return 0;
}

//调用串口写数据API，协议栈将通过该接口发送串口数据
static int __uart_write_api(char *buf, unsigned int len, char *payload,
                            unsigned int payload_len) {
  static HeaderCbData cb_data = {0};

  cb_data.buf = (unsigned char *)buf;
  cb_data.payload = (unsigned char *)payload;
  cb_data.payload_len = payload_len;
  uni_uart_async_send_buffer(s_record_port, (unsigned char *)buf, len,
                            _UniUartAsyncSendCallback, &cb_data);
  return 0;
}

static int _pull_noise_reduction_data_request(char *packet, int len) {
  ChnlPullNoiseReductionDataReq *pull = (ChnlPullNoiseReductionDataReq *)packet;
  printf("receive pull data request, mode=%d\n", pull->mode);
  s_push_data = pull->mode;
  return 0;
}

//协议栈解析接收到的串口数据
static void __recv_comm_packet(CommPacket *packet) {
  switch (packet->cmd) {
    case CHNL_MESSAGE_PULL_NOISE_REDUCTION_DATA_REQUEST:
      _pull_noise_reduction_data_request(packet->payload, packet->payload_len);
      break;
    default:
      break;
  }
}

static int _UniAppUartMessageAsyncRecvCallback(int port, int length,
                                               void *priv) {
  unsigned int read_len = 0;
  static unsigned char *recv_buffer = s_ivm_uart_recv_buffer;

  read_len = uni_uart_read(port, recv_buffer, length);
  if (read_len == 0) return 0;
  CommProtocolReceiveUartData(recv_buffer, length);
  return 0;
}

static int _UniUartMessageSendCheck(void) {
  IVM_SEND_PACK pack;
  int ret;

  if (LvpQueueIsEmpty(&s_ivm_uart_send_pack_queue)) {
    is_send_done = true;
    uni_uart_stop_async_send(s_record_port);
    return -1;
  }
  is_send_done = false;

  LvpQueueGet(&s_ivm_uart_send_pack_queue, (unsigned char *)&pack);
  ret = CommProtocolPacketAssembleAndSendV2(pack.type, (char *)pack.payload,
                                            (CommPayloadLen)pack.payload_len);
  if (ret < 0) {
    printf("send mic data error %d\n", ret);
  }

  return 0;
}

int UniUartRecordInit(void) {
  LvpQueueInit(&s_ivm_uart_send_pack_queue, s_ivm_uart_send_pack_queue_buffer,
               IVM_UART_SEND_QUENE_LEN * sizeof(IVM_SEND_PACK),
               sizeof(IVM_SEND_PACK));

  // step1. 注册协议栈依赖hooks
  CommProtocolHooks hooks;
  __hook_init(&hooks);
  CommProtocolRegisterHooks(&hooks);

  if (s_record_port == UNI_UART_PORT0) {
    padmux_set(5, 0);
    padmux_set(6, 0);
  }

  int32_t record_baudrate = CONFIG_LVP_UART_RECORD_PORT_BAUDRATE;
  uni_uart_async_send_buffer_stop(s_record_port);
  uni_uart_async_recv_buffer_stop(s_record_port);

  if (uni_uart_init(s_record_port, record_baudrate)) {
    printf(LOG_TAG "uart init failed, port = %d, baudrate = %d\n",
           s_record_port, record_baudrate);
    return -1;
  }
  uni_uart_start_async_recv(s_record_port, _UniAppUartMessageAsyncRecvCallback,
                           NULL);

  // step2. 协议栈初始化
  CommProtocolInit(__uart_write_api, __recv_comm_packet);
  printf(LOG_TAG "UniUartRecordInit init finished! \n");
  return 0;
}

unsigned char UniUartRecordisStart(void) { return s_push_data; }

int UniUartRecordTask(LVP_CONTEXT *context) {
  LVP_CONTEXT_HEADER *ctx_header = context->ctx_header;
  static IVM_SEND_PACK send_pack = {
    .type = CHNL_MESSAGE_NOISE_REDUCTION_RAW_DATA,
    .payload = NULL,
    .payload_len = 0,
  };

  unsigned int mic_buffer_len_per_context =
    ctx_header->pcm_frame_num_per_context * ctx_header->frame_length *
    ctx_header->sample_rate * 2 / 1000;
  unsigned char *cur_mic_buffer =
    (unsigned char *)ctx_header->mic_buffer +
    mic_buffer_len_per_context *
      (context->ctx_index % (ctx_header->mic_buffer_size / ctx_header->mic_num /
                             mic_buffer_len_per_context));
  uni_dcache_invalid_range((unsigned int *)cur_mic_buffer,
                          mic_buffer_len_per_context);
  send_pack.type = CHNL_MESSAGE_NOISE_REDUCTION_RAW_DATA;
  send_pack.payload = cur_mic_buffer;
  send_pack.payload_len = mic_buffer_len_per_context;
  LvpQueuePut(&s_ivm_uart_send_pack_queue, (unsigned char *)&send_pack);
  if (is_send_done) _UniUartMessageSendCheck();

  unsigned int send_len = ctx_header->logfbank_buffer_size /
                          ctx_header->logfbank_frame_num_per_channel *
                          ctx_header->pcm_frame_num_per_context;
  unsigned char *send_buffer =
    (unsigned char *)LvpGetLogfankBuffer(context, context->ctx_index);
  uni_dcache_invalid_range((unsigned int *)send_buffer, send_len);
  send_pack.type = CHNL_MESSAGE_LOGFBANK_DATA;
  send_pack.payload = send_buffer;
  send_pack.payload_len = send_len;
  LvpQueuePut(&s_ivm_uart_send_pack_queue, (unsigned char *)&send_pack);
  if (is_send_done) _UniUartMessageSendCheck();

  return 0;
}

int UniUartRecordTaskLoop(void) {
  if (is_send_done) _UniUartMessageSendCheck();
  return 0;
}
