#include "can_driver.h" //  CAN ͷļ
#include "string.h"     // ַͷļ
#include "stdlib.h"
#include "crc8.h"

/**
 * @brief CAN commĽ״̬buffer
 *
 * @param ins Ҫõʵ
 */
static void FDCANCommResetRx(FDCANCommInstance *ins)
{
  // ǰѾյbuffer
  memset(ins->raw_recvbuf, 0, ins->cur_recv_len);
  ins->recv_state   = 0; // ״̬
  ins->cur_recv_len = 0; // ǰѾյĳ
}

/**
 * @brief fdcancommĽջص
 *
 * @param _instance
 */
static void FDCANCommRxCallback(FDCANInstance *_instance)
{
  // idԽ FDCANCommInstance ݴݸ FDCANInstance Ļص
  FDCANCommInstance *comm = (FDCANCommInstance *)_instance->id; // עд,fdcan instanceidǿתΪFDCANCommInstance*

  /* ǰ״̬ж */
  if (_instance->rx_buff[0] == FDCAN_COMM_HEADER && comm->recv_state == 0) // ֮ǰδʼҴ˴ΰһλ֡ͷ
  {
    if (_instance->rx_buff[1] == comm->recv_data_len) // һdatalenҲ趨ճ(Ϊʱֶ֧̬)
    {
      comm->recv_state = 1; // ý״̬Ϊ1,˵Ѿʼ
    }
    else
      return; // ֱ
  }

  if (comm->recv_state) // Ѿյ֡ͷ
  {
    // Ѿյĳȼϵǰһĳȴbuf len,˵մ
    if (comm->cur_recv_len + _instance->rx_len > comm->recv_buf_len)
    {
      FDCANCommResetRx(comm);
      return; // ״̬Ȼ󷵻
    }

    // ֱӰѵǰյݽӵbuffer
    memcpy(comm->raw_recvbuf + comm->cur_recv_len, _instance->rx_buff, _instance->rx_len);
    comm->cur_recv_len += _instance->rx_len;

    // һԺպõbuf len,˵Ѿ
    if (comm->cur_recv_len == comm->recv_buf_len)
    {
      // buffﱾtailλõCAN_COMM_TAIL
      if (comm->raw_recvbuf[comm->recv_buf_len - 1] == FDCAN_COMM_TAIL)
      { // ͨУ,ݵunpack_data
        if (comm->raw_recvbuf[comm->recv_buf_len - 2] == crc_8(comm->raw_recvbuf + 2, comm->recv_data_len))
        { // ĻʹDMA
          memcpy(comm->unpacked_recv_data, comm->raw_recvbuf + 2, comm->recv_data_len);
          comm->update_flag = 1;           // ݸflagΪ1
          DaemonReload(comm->comm_daemon); // daemon,ݸºһֱȡݸ²ʱ
        }
      }
      FDCANCommResetRx(comm);
      return; // ״̬Ȼ󷵻
    }
  }
}

static void FDCANCommLostCallback(void *cancomm)
{
  FDCANCommInstance *comm = (FDCANCommInstance *)cancomm;
  FDCANCommResetRx(comm);
}

FDCANCommInstance *FDCANCommInit(FDCANComm_Init_Config_s *comm_config)
{
  FDCANCommInstance *ins = (FDCANCommInstance *)malloc(sizeof(FDCANCommInstance));
  memset(ins, 0, sizeof(FDCANCommInstance));

  ins->recv_data_len  = comm_config->recv_data_len;
  ins->recv_buf_len   = comm_config->recv_data_len + FDCAN_COMM_OFFSET_BYTES; // head + datalen + crc8 + tail
  ins->send_data_len  = comm_config->send_data_len;
  ins->send_buf_len   = comm_config->send_data_len + FDCAN_COMM_OFFSET_BYTES;
  ins->raw_sendbuf[0] = FDCAN_COMM_HEADER;          // head,ֱñÿηͶҪ¸ֵ,tailͬ
  ins->raw_sendbuf[1] = comm_config->send_data_len; // datalen
  ins->raw_sendbuf[comm_config->send_data_len + FDCAN_COMM_OFFSET_BYTES - 1] = FDCAN_COMM_TAIL;
  // can instance
  comm_config->can_config.id = ins;                                    // FDCANCommʵָΪFDCANInstanceid,صлõ
  comm_config->can_config.fdcan_module_callback = FDCANCommRxCallback; // ջص
  ins->can_ins = FDCANRegister(&comm_config->can_config);              // עCANʵ

  Daemon_Init_Config_s daemon_config = {
      .callback     = FDCANCommLostCallback,
      .owner_id     = (void *)ins,
      .reload_count = comm_config->daemon_count,
  };
  ins->comm_daemon  = DaemonRegister(&daemon_config);
  return ins;
}

void FDCANCommSend(FDCANCommInstance *instance, uint8_t *data)
{
  static uint8_t crc8;
  static uint8_t send_len;
  // data copyraw_sendbuf,crc8
  memcpy(instance->raw_sendbuf + 2, data, instance->send_data_len);
  crc8 = crc_8(data, instance->send_data_len);
  instance->raw_sendbuf[2 + instance->send_data_len] = crc8;

  // CANηΪ8ֽ,8ֽ,Ҫְ
  for (size_t i = 0; i < instance->send_buf_len; i += 8)
  { // һ,send lenС8,Ҫ޸CANtxconfеDLCλ,bsp_canṩĽӿڼ
    send_len = instance->send_buf_len - i >= 8 ? 8 : instance->send_buf_len - i;
    FDCANSetDLC(instance->can_ins, send_len);
    memcpy(instance->can_ins->tx_buff, instance->raw_sendbuf + i, send_len);
    FDCANTransmit(instance->can_ins, 2);
  }
}

void *FDCANCommGet(FDCANCommInstance *instance)
{
  instance->update_flag = 0; // ȡ󽫸flagΪ0
  return instance->unpacked_recv_data;
}

uint8_t FDCANCommIsOnline(FDCANCommInstance *instance)
{
  return DaemonIsOnline(instance->comm_daemon);
}
