/*
  v 1.0 2025.03.03 by Winson Chen
  Create.

  This demo only tests communication between CAN1 and CAN2 on the same device.
*/

#include <pthread.h>
#include "canfd.h"

struct thread_data {
  WORD hidnum;
  BYTE can_port;
  BOOL msg_counter;
  volatile int running;
};

static const char *ID_str[] = {"11 bit mode(0x000 ~ 0x7FF)", "29 bit mode(0x00000000 ~ 0x1FFFFFFF)"};
static pthread_t rx_threads[2];           //0: CAN1, 1: CAN2
static struct thread_data thread_data[2]; //0: CAN1, 1: CAN2

int get_data_length_from_dlc(int dlc) {
  static const int length_map[16] = {
        0,  1,  2,  3,  4,  5,  6,  7,
        8, 12, 16, 20, 24, 32, 48, 64
    };
    if (dlc < 0 || dlc > 15) {
        return -1; // Ivalid DLC
    }
    return length_map[dlc];
}

int range_check(DWORD item, DWORD Lvalue, DWORD Uvalue)
{
  if (item < Lvalue || item > Uvalue){
    printf("Invalid value. break to menu.\n");
    return FAILURE;
  }
  else
    return SUCCESS;
}

void* receive_can_msg_thread(void* arg) {
  struct thread_data* data = (struct thread_data*)arg;
  WORD hidnum = data->hidnum;
  BYTE i_byCANPort = data->can_port;
  BOOL msg_counter = data->msg_counter;
  int i;

  usleep(5000);
  //printf("Started CAN receive thread for CAN%d, hidnum=%d\n", i_byCANPort, hidnum);

  while (data->running) {
    DWORD o_dwCount = 0;
    int count_ret = CANFD_GetCANRxMsgCount(hidnum, i_byCANPort, &o_dwCount);

    if (count_ret != 0) {
      printf("CAN%d GetCANRxMsgCount failed, ret=%d, rechecking device\n", i_byCANPort, count_ret);
      continue;
    }

    if (o_dwCount > 0) {
      BYTE o_byType, o_byMode, o_byRTR, o_byFDF, o_byDlen, o_byData[64] = {0};
      DWORD o_dwID, o_dw_TimeStamp_s, o_dw_TimeStamp_us;

      int rx_ret = CANFD_GetCANRxMsg(hidnum, i_byCANPort, &o_byType, &o_byMode, &o_dwID, &o_byRTR, &o_byFDF, &o_byDlen, o_byData, &o_dw_TimeStamp_s, &o_dw_TimeStamp_us);

      if (rx_ret == 0) {
        msg_counter++;
        printf("CANPort  NO    Type  Mode  ID(hex)    RTR  FDF  DLC   Data\n");

        printf("%-8d %-5d %-5d %-5d 0x%-8x %-4d %-4d %-5d ", 
           i_byCANPort, msg_counter, o_byType, o_byMode, o_dwID, o_byRTR, o_byFDF, o_byDlen);

        int data_len = get_data_length_from_dlc(o_byDlen);
        if (data_len > 64) data_len = 64; // ..... 64 ..
        for (i = 0; i < data_len; i++) {
          printf("%02x ", o_byData[i]);
          if ((i + 1) % 8 == 0 && i + 1 < data_len) {
            printf("\n                                                      "); // 45 ...... Data
          }
        }

        printf("\nTimeStamp \n%u.%u\n\n", o_dw_TimeStamp_s, o_dw_TimeStamp_us);
      } 
      else {
        printf("Error reading message on CAN%d: %d\n", i_byCANPort, rx_ret);
      }
    }

  usleep(100);
  }
  //printf("CAN%d receive thread exiting\n", i_byCANPort);
  return NULL;
}

void SetCANOPMode(WORD hidnum)
{
	BYTE i_byCANPort;
	WORD i_wEnable, i_wBusMode, i_wISOMode;

  printf("Set CAN port enable/listen mode and CAN FD ISO/Non-ISO operation mode.\n");

  printf("Select CAN port(1 or 2):");
  scanf("%hhd",&i_byCANPort);
  if (range_check(i_byCANPort, 1, 2))
    return;
  
  printf("Set CAN port enable(1) or disable(0):");
  scanf("%hd",&i_wEnable); //1
  if (range_check(i_wEnable, 0, 1))
    return;

  printf("Set CAN port listen only mode(1) or normal mode(0):");
  scanf("%hd",&i_wBusMode);  //0
  if (range_check(i_wBusMode, 0, 1))
    return;

  printf("Set CAN port Bosch mode(1) or ISO11898-1 mode(0):");
  scanf("%hd",&i_wISOMode);  //0
  if (range_check(i_wISOMode, 0, 1))
    return;

	if (!CANFD_SetCANOPMode(hidnum, i_byCANPort, i_wEnable, i_wBusMode, i_wISOMode))
    printf("CAN%d setting OK\n",i_byCANPort);
  else
    printf("CAN%d setting Fail\n",i_byCANPort);
}

void GetCANOPMode(WORD hidnum)
{
  printf("Get CAN port enable/listen mode and CAN FD ISO/Non-ISO operation mode.\n");
  WORD o_wEnable, o_wBusMode, o_wISOMode;
	BYTE i_byCANPort;
  static const char *enable_str[] = {"disable", "enable"};
  static const char *bus_mode_str[] = {"send & receive mode", "listen only mode"};
  static const char *iso_mode_str[] = {"ISO11898-1 mode", "Bosch mode"};

  for (i_byCANPort = 1; i_byCANPort <= 2; i_byCANPort++){
    if (CANFD_GetCANOPMode(hidnum, i_byCANPort, &o_wEnable, &o_wBusMode, &o_wISOMode)) {
      printf("CAN%d Fail\n", i_byCANPort);
      return;
    }

    printf("CAN%d port %s, %s, %s\n", i_byCANPort,
           enable_str[o_wEnable ? 1 : 0],
           bus_mode_str[o_wBusMode ? 1 : 0],
           iso_mode_str[o_wISOMode ? 1 : 0]);
  }
}

void SetCANADBaudRate(WORD hidnum)
{
  BYTE i_byCANPort;
	DWORD i_dwArbitrBR, i_dwDataBR;
	WORD i_wArbitrBRSP, i_wDataBRSP;

	printf("Set CAN port baudrate.\n");

  printf("Select CAN port(1 or 2):");
  scanf("%hhd",&i_byCANPort);
  if (range_check(i_byCANPort, 1, 2))
    return;

  printf("Arbitration bit rate:(10000~1000000)");
  scanf("%d",&i_dwArbitrBR); //1000000
  if (range_check(i_dwArbitrBR, 10000, 1000000))
    return;
  
  printf("Data phase bit rate:(100000~10000000)");
  scanf("%d",&i_dwDataBR);  //1000000
  if (range_check(i_dwDataBR, 100000, 10000000))
    return;

  printf("Arbitration bit rate & sample point:(0~10000), Recommended range: 7500 ~ 8750");
  scanf("%hd",&i_wArbitrBRSP); //8750
  if (range_check(i_wArbitrBRSP, 0, 10000))
    return;

  printf("Data phase bit rate:& sample point:(0~10000), Recommended range: 7500 ~ 8750");
  scanf("%hd",&i_wDataBRSP);  //8750
  if (range_check(i_wDataBRSP, 0, 10000))
    return;

  if (!CANFD_SetCANADBaudRate(hidnum, i_byCANPort, i_dwArbitrBR, i_dwDataBR, i_wArbitrBRSP, i_wDataBRSP))
    printf("CAN%d setting OK\n",i_byCANPort);
  else
    printf("CAN%d setting Fail\n",i_byCANPort);
}

void GetCANADBaudRate(WORD hidnum)
{
	DWORD o_dwArbitrBR, o_dwDataBR;
	WORD o_wArbitrBRSP, o_wDataBRSP;
	BYTE i_byCANPort;

	printf("Get CAN port baudrate.\n");
  for (i_byCANPort = 1; i_byCANPort <= 2; i_byCANPort++){
    if (CANFD_GetCANADBaudRate(hidnum, i_byCANPort, &o_dwArbitrBR, &o_dwDataBR, &o_wArbitrBRSP, &o_wDataBRSP)){
      printf("CAN%d Fail\n", i_byCANPort);
      return;
    }

    printf("CAN%d Arbitration bit rate %d\n     Data phase bit rate %d\n     Arbitration bit rate & sample point %d\n     Data phase bit rate:& sample point %d\n", i_byCANPort, o_dwArbitrBR, o_dwDataBR, o_wArbitrBRSP, o_wDataBRSP);
    printf("\n");
  }
}

/*
i_byDlen            The message data length
(in hexadecimal).   (in decimal).
0x0 0   0x4 4   0x8 8   0xC 24
0x1 1   0x5 5   0x9 12  0xD 32
0x2 2   0x6 6   0xA 16  0xE 48
0x3 3   0x7 7   0xB 20  0xF 64
*/
void SetCANTxMsg(WORD hidnum)
{
  char choice;
  int i, default_ID = 0x100;
  WORD o_wSTDFIDListSize, o_wEXTFIDListSize, o_wSTDFID1[128], o_wSTDFID2[128];
	BYTE i_byCANPort, otherPort, i_byMode, i_byRTR, i_byFDF, i_byDlen, data_length, o_byRejectRFS, o_byRejectRFE;
	DWORD i_dwID, o_dwEXTFID1[64], o_dwEXTFID2[64];
	BYTE i_byData[64] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x8};  //send data by default.

	printf("Send a CAN/CAN FD message to the transmission buffer of the selected CAN port.\n");

  printf("Send a default message from CAN port1(ID 0x%x)?(Y/N)",default_ID);
  scanf(" %c", &choice);
  if (choice == 'Y' || choice == 'y') {
    printf("Sending default message by CAN1\n");
    i_byCANPort = 1;
    CANFD_SetCANTxMsg(hidnum, 1, 0, default_ID, 0, 0, 0x8, i_byData);
  } 
  else {
    printf("Select CAN port(1 or 2):");
    scanf("%hhd",&i_byCANPort);
    if (range_check(i_byCANPort, 1, 2))
    return;

    if(i_byCANPort >> 1)
      otherPort = 1;
    else
      otherPort = 2;

    printf("Type of CAN ID message to be sent. 11 bit(0) or 29 bit(1) CAN ID:");
    scanf("%hhd",&i_byMode);
    if (range_check(i_byMode, 0, 1))
     return;

    printf("CAN message ID parameter to be sent. %s:\n",ID_str[i_byMode ? 1 : 0]);
    CANFD_GetCANGlobalFilter(hidnum, otherPort, &o_byRejectRFS, &o_byRejectRFE, &o_wSTDFIDListSize, &o_wEXTFIDListSize);  //get o_wSTDFIDListSize & o_wEXTFIDListSize
    printf("CAN%d Allowed CAN ID:\n", otherPort);
    if (i_byMode){  //29 bit
      CANFD_GetCANEXTIDFilter(hidnum, otherPort, o_dwEXTFID1, o_dwEXTFID2);
      if (o_wEXTFIDListSize){
        for (i = 0; i < o_wEXTFIDListSize; i++)
          printf("set%d 0x%x start ID to 0x%x end ID\n", i, o_dwEXTFID1[i], o_dwEXTFID2[i]);
      }
      else
        printf("All IDs are allowed.\n");
    }
    else{ //11 bit
      CANFD_GetCANSTDIDFilter(hidnum, otherPort, o_wSTDFID1, o_wSTDFID2);
      if (o_wSTDFIDListSize){
        for (i = 0; i < o_wSTDFIDListSize; i++) 
          printf("set%d 0x%x start ID to 0x%x end ID\n",i, o_wSTDFID1[i], o_wSTDFID2[i]);
      }
      else
        printf("All IDs are allowed.\n");
    }
    scanf("%x",&i_dwID);
    if (range_check(i_dwID, 0, i_byMode ? 0x1FFFFFFF : 0x7FF))
      return;

    if (i_byMode){  //29 bit
      if (o_byRejectRFE)
        printf("%56sCAN%d reject extended rtr\n", "", otherPort);
    }
    else{  //11 bit
      if (o_byRejectRFS)
        printf("%56sCAN%d reject standard rtr\n", "", otherPort);
    }
    printf("Remote Transmission Request. send CAN data message(0) or send CAN RTR message(1):");
    scanf("%hhd",&i_byRTR); //0
    if (range_check(i_byRTR, 0, 1))
     return;

    printf("The parameters for the CAN/CAN FD message to be transmitted. CAN(0) or CNA FD(1):");
    scanf("%hhd",&i_byFDF);
    if (range_check(i_byFDF, 0, 1))
     return;

    if (i_byFDF == 0 && i_byRTR == 1){
      i_byDlen = 0;
    }
    else{
      printf("The parameter for the data length(DLC) of the CAN/CAN FD message to be transmitted (0~%d):", i_byFDF ? 15 : 8);
      scanf("%hhd",&i_byDlen);
      if (range_check(i_byDlen, 0, i_byFDF ? 15 : 8))
       return;

      data_length = get_data_length_from_dlc(i_byDlen);
      printf("data send (%d byte):", data_length);
      for (i = 0; i < data_length; i++)
        scanf("%hhd", &i_byData[i]);
    }

    CANFD_SetCANTxMsg(hidnum, i_byCANPort, i_byMode, i_dwID, i_byRTR, i_byFDF, i_byDlen, i_byData);
  }

  printf("Message sent on CAN%d\n", i_byCANPort);
}

void SetCANHWSendMsg(WORD hidnum)
{
	printf("Set the module to periodically transmit CAN/CANFD message data on the specified CAN port using the module's hardware clock.\n");
  char choice;
  int i, default_ID = 0x110;
  WORD o_wSTDFIDListSize, o_wEXTFIDListSize, o_wSTDFID1[128], o_wSTDFID2[128];
  BYTE i_byCANPort, otherPort, i_byMode, i_byRTR, i_byFDF, i_byDlen, data_length, o_byRejectRFS, o_byRejectRFE;
	DWORD i_dwID, i_dwPeriod/*unit 100us*/, i_dwCounter, o_dwEXTFID1[64], o_dwEXTFID2[64];
	BYTE i_byData[64] = {0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01};
	
  printf("Send a default message from CAN port1(ID 0x%x)?(Y/N)",default_ID);
  scanf(" %c", &choice);
  if (choice == 'Y' || choice == 'y') {
    printf("Set default message OK.\n");
    CANFD_SetCANHWSendMsg(hidnum, 1, 0, default_ID, 0, 0, 0x8, i_byData, 2, 50);
  }
  else {
    printf("Select CAN port(1 or 2):");
    scanf("%hhd",&i_byCANPort);
    if (range_check(i_byCANPort, 1, 2))
    return;

    if(i_byCANPort >> 1)
      otherPort = 1;
    else
      otherPort = 2;

    printf("otherPort is %d\n",otherPort);

    printf("Type of CAN ID message to be sent. 11 bit(0) or 29 bit(1) CAN ID:");
    scanf("%hhd",&i_byMode);
    if (range_check(i_byMode, 0, 1))
     return;

    printf("CAN message ID parameter to be sent. %s:\n",ID_str[i_byMode ? 1 : 0]);
    CANFD_GetCANGlobalFilter(hidnum, otherPort, &o_byRejectRFS, &o_byRejectRFE, &o_wSTDFIDListSize, &o_wEXTFIDListSize);  //get o_wSTDFIDListSize & o_wEXTFIDListSize
    printf("CAN%d Allowed CAN ID:\n", otherPort);
    if (i_byMode){  //29 bit
      CANFD_GetCANEXTIDFilter(hidnum, otherPort, o_dwEXTFID1, o_dwEXTFID2);
      if (o_wEXTFIDListSize){
        for (i = 0; i < o_wEXTFIDListSize; i++)
          printf("set%d 0x%x start ID to 0x%x end ID\n", i, o_dwEXTFID1[i], o_dwEXTFID2[i]);
      }
      else
        printf("All IDs are allowed.\n");
    }
    else{ //11 bit
      CANFD_GetCANSTDIDFilter(hidnum, otherPort, o_wSTDFID1, o_wSTDFID2);
      if (o_wSTDFIDListSize){
        for (i = 0; i < o_wSTDFIDListSize; i++) 
          printf("set%d 0x%x start ID to 0x%x end ID\n",i, o_wSTDFID1[i], o_wSTDFID2[i]);
      }
      else
        printf("All IDs are allowed.\n");
    }
    scanf("%x",&i_dwID);
    if (range_check(i_dwID, 0, i_byMode ? 0x1FFFFFFF : 0x7FF))
      return;

    if (i_byMode){  //29 bit
      if(o_byRejectRFE)
        printf("%56sCAN%d reject extended rtr\n", "", otherPort);
    }
    else{  //11 bit
      if(o_byRejectRFS)
        printf("%56sCAN%d reject standard rtr\n", "", otherPort);
    }
    printf("Remote Transmission Request. send CAN data message(0) or send CAN RTR message(1):");
    scanf("%hhd",&i_byRTR); //0
    if (range_check(i_byRTR, 0, 1))
     return;

    printf("The parameters for the CAN/CAN FD message to be transmitted. CAN(0) or CNA FD(1):");
    scanf("%hhd",&i_byFDF);
    if (range_check(i_byFDF, 0, 1))
     return;

    if (i_byFDF == 0 && i_byRTR == 1){
      i_byDlen = 0;
    }
    else{
      printf("The parameter for the data length(DLC) of the CAN/CAN FD message to be transmitted (0~%d):", i_byFDF ? 15 : 8);
      scanf("%hhd",&i_byDlen);
      if (range_check(i_byDlen, 0, i_byFDF ? 15 : 8))
       return;

      data_length = get_data_length_from_dlc(i_byDlen);
      printf("data send (%d byte):", data_length);
      for (i = 0; i < data_length; i++)
        scanf("%hhd", &i_byData[i]);
    }

    printf("The time cycle for transmitting the CAN/CAN FD message using the module's hardware clock. (unit 100us):");
    scanf("%d",&i_dwPeriod);

    printf("The number of times the CAN/CAN FD message is transmitted:");
    scanf("%d",&i_dwCounter);

    CANFD_SetCANHWSendMsg(hidnum, i_byCANPort, i_byMode, i_dwID, i_byRTR, i_byFDF, i_byDlen, i_byData, i_dwPeriod, i_dwCounter);
  }
}

void SetCANHWSendMode(WORD hidnum)
{
	printf("Enable/disable the module to periodically transmit CAN/CAN FD messages on the specified CAN port using the module's hardware clock.\n");
	BYTE i_byCANPort, i_byMode;

  printf("Select CAN port:");
  scanf("%hhd",&i_byCANPort);

  printf("Enable(1) or disable(0):");
  scanf("%hhd",&i_byMode);


	CANFD_SetCANHWSendMode(hidnum, i_byCANPort, i_byMode);
	printf("CAN%d ", i_byCANPort);
  if(i_byMode)
    printf("enable hardware timer.\n");
  else
    printf("disable hardware timer.\n");
}

void GetCANHWSendMode(WORD hidnum)
{
	printf("Get the enable/disable status of the module for periodically transmitting CAN/CAN FD messages on the specified CAN port using the module's hardware clock.()\n");
	BYTE i_byCANPort, o_byMode;
	
  for (i_byCANPort = 1; i_byCANPort <= 2; i_byCANPort++){
    CANFD_GetCANHWSendMode(hidnum, i_byCANPort, &o_byMode);
    printf("CAN%d hardware timer status ",i_byCANPort);
    if(o_byMode)
      printf("enable.\n");
    else
      printf("disable.\n");
  }
}

void GetCANRxFramePerSec(WORD hidnum)
{
	printf("Obtain the message reception traffic value of the CAN bus on the specified CAN port of the module.()\n");
	WORD o_wRxFPS;
	BYTE i_byCANPort;

  printf("Select CAN port:");
  scanf("%hhd",&i_byCANPort);

	CANFD_GetCANRxFramePerSec(hidnum, i_byCANPort, &o_wRxFPS);
	printf("CAN%d FPS %hu\n", i_byCANPort, o_wRxFPS);
}

void GetCANStatus(WORD hidnum)
{
	DWORD o_dwCANStatus, o_dwErrCnt, o_dwBufStatus;
	BYTE i_byCANPort;

  printf("Obtain the CAN bus status, the number of transmission/reception errors, and the buffer overflow status on the specified CAN port of the module.\n");

  for (i_byCANPort = 1; i_byCANPort <= 2; i_byCANPort++){
	  if (!CANFD_GetCANStatus(hidnum, i_byCANPort, &o_dwCANStatus, &o_dwErrCnt, &o_dwBufStatus))
  		printf("CAN%d status %d  Error count %d  Buffer status %d\n",i_byCANPort, o_dwCANStatus ,o_dwErrCnt, o_dwBufStatus);
    else
      printf("CANFD_GetCANStatus fail.\n");
  }
}

void CloseDevice(WORD hidnum)
{
  printf("Close CANFD device and quit demo.\n");
  CANFD_CloseDevice(hidnum);
}

void print_menu() {
  printf("\nChoose an option:\n");
  printf("a) Set CAN OPMode:\r\n"); 
  printf("b) Get CAN OPMode:\r\n"); 
  printf("c) Set CAN port baudrate:\r\n");
  printf("d) Get CAN port baudrate:\r\n");
  printf("e) Send CAN/CAN FD message:\r\n");
  printf("f) Set CAN/CAN FD message data using hardware clock:\r\n");
  printf("g) Enable/disable CAN/CAN FD message sending by using module hardware timer:\r\n");
  printf("h) Get the module's hardware clock status:\r\n");
  printf("i) Obtain the message reception traffic value of CAN port:\r\n");
  printf("j) Get CAN port status:\r\n");
  printf("p) print All function message:\r\n");
  printf("q) Shutdown and exit.\r\n");
}

int main() {
  int Ret;
  WORD hidnum;

  Ret = CANFD_ScanDevice();
  if (CANFD_ScanDevice())
    printf("CANFD_ScanDevice error 0x%x\n", Ret);

  Ret = CANFD_OpenDevice(&hidnum, 0x216, 0x67d7e644);  //PID,BID
  if (Ret){
    printf("CANFD_OpenDevice error 0x%x\n",Ret);
    printf("Check demo \"list\" first\n");
    return 0;
  }

  thread_data[0].hidnum = hidnum;
  thread_data[0].can_port = 1; // CAN1
  thread_data[0].running = 1;
  if (pthread_create(&rx_threads[0], NULL, receive_can_msg_thread, &thread_data[0]) != 0) {
    perror("Failed to create receive thread for CAN1");
    CloseDevice(hidnum);
    return 1;
  }

  thread_data[1].hidnum = hidnum;
  thread_data[1].can_port = 2; // CAN2
  thread_data[1].running = 1;
  if (pthread_create(&rx_threads[1], NULL, receive_can_msg_thread, &thread_data[1]) != 0) {
    perror("Failed to create receive thread for CAN2");
    thread_data[0].running = 0; // .. CAN1 ...
    pthread_join(rx_threads[0], NULL);
    CloseDevice(hidnum);
    return 1;
  }

  print_menu();
  char choice;
  while (1) {
    scanf(" %c", &choice);

    switch (choice) {
      case 'a': SetCANOPMode(hidnum); break;
      case 'b': GetCANOPMode(hidnum); break;
      case 'c': SetCANADBaudRate(hidnum); break;
      case 'd': GetCANADBaudRate(hidnum); break;
      case 'e': SetCANTxMsg(hidnum); break;
      case 'f': SetCANHWSendMsg(hidnum); break;
      case 'g': SetCANHWSendMode(hidnum); break;
      case 'h': GetCANHWSendMode(hidnum); break;
      case 'i': GetCANRxFramePerSec(hidnum); break;
      case 'j': GetCANStatus(hidnum); break;
      case 'p': print_menu();break;
      case 'q': 
                thread_data[0].running = 0;
                thread_data[1].running = 0;
                pthread_join(rx_threads[0], NULL);
                pthread_join(rx_threads[1], NULL);
                CloseDevice(hidnum);
                return 0;
      default: printf("error input\n");
    }
  }
  return 0;
}
