/***************************************************************************************************
**    Copyright (C) 2018 - 2020 HMS Technology Center Ravensburg GmbH, all rights reserved
****************************************************************************************************
**
**        File: demo.c
**     Summary: Simple demo application, sending and receiving 11-bit CAN messages at 250 KBaud.
**              Adapt the COM/TTY port in the k_SERIAL_PORT define or disable the define
**              to use the 'retrieve_serial_port' function.
**              To abort the demo application, press any key.
**
****************************************************************************************************
**    This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY.
***************************************************************************************************/


/***************************************************************************************************
**    include-files
***************************************************************************************************/
#ifdef __unix__
#include <string.h>
#include <stdlib.h>

#include <sys/ioctl.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/file.h>
#include <errno.h>
#include <unistd.h>
#include <sys/time.h>
#include <time.h>
#include <signal.h>
#elif defined(_WIN32) || defined(WIN32)
#include <Windows.h>
#include <time.h>
#include <conio.h>
#endif

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>

#include "simply.h"
/***************************************************************************************************
**    global variables
***************************************************************************************************/


/***************************************************************************************************
**    static constants, types, macros, variables
***************************************************************************************************/
#ifdef __unix__
#define k_SERIAL_PORT      "/dev/ttyACM0"
#else
#define k_SERIAL_PORT      "COM5"
#endif
#define k_BAUDRATE         (250)

#ifdef __unix__
/* Sleep macro similar to the windows Sleep() */
#define Sleep(ms)             { struct timespec req = {(ms)/1000, ((ms)%1000)*1000000}, rem;  \
                                while( -1 == nanosleep(&req, &rem) )                          \
                                req = rem; }
#endif

/* Time over macro to check if a given time is expired */
#define TIME_OVER(start_time, timeout) ((tick() - start_time) >= timeout)

/***************************************************************************************************
**    static function-prototypes
***************************************************************************************************/
static bool status(void);
static uint8_t receive_messages(void);
static uint32_t tick();
#if defined(_WIN32) || defined(WIN32)
BOOL WINAPI CtrlHandler(DWORD fdwCtrlType);
#else
void CtrlHandler();
bool _kbhit();
char *rstrstr(char *s1, char *s2);
#endif
char* retrieve_serial_port(char* buffer, int size);

/***************************************************************************************************
**    global functions
***************************************************************************************************/

/***************************************************************************************************
Function:
  main

Description:
  The main entry-point function

Parameters:
  argc (IN)   - argument c
  argv (IN)   - argument v

Return value:
  -
***************************************************************************************************/
int main(int argc, char *argv[]) {
#ifdef __unix__
    static struct termios sOldt, sNewt;
#endif
    can_msg_t can_msg_tx;
    identification_t ident_msg;
    uint32_t last_sent = 0;
    simply_last_error_t error;
    uint8_t result = 0;

    /* register CtrlHandler (abort with ctrl+c) */
#if defined(_WIN32) || defined(WIN32)
    SetConsoleCtrlHandler(CtrlHandler, TRUE);
#else
    signal(SIGINT, CtrlHandler);
#endif

#ifdef __unix__
    /* disable canonical input */
    tcgetattr(STDIN_FILENO, &sOldt);
    sNewt = sOldt;
    sNewt.c_lflag &= ~(ICANON);
    tcsetattr(STDIN_FILENO, TCSANOW, &sNewt);

    int flags = fcntl(STDIN_FILENO, F_GETFL, 0);
    flags = flags | O_NONBLOCK;
    fcntl(STDIN_FILENO, flags);
#endif

    printf("\n#### simplyCAN Demo 1.0 (c) 2018-2019 HMS ####\n");

#ifdef k_SERIAL_PORT
    if (!simply_open(k_SERIAL_PORT)) {
        goto ErrorExit;
    }
#else
    {
      char buffer[32];

      printf("Retrieve serial port...");
      if(retrieve_serial_port(buffer, 32) == NULL) {
          goto ErrorExit;
      }
      if (!simply_open(buffer)) {
          goto ErrorExit;
      }
      printf("%s\n", buffer);
    }
#endif

    if(!simply_identify(&ident_msg)) {
        goto ErrorExit;
    }
    printf("Firmware version: %s\n", ident_msg.fw_version);
    printf("Hardware version: %s\n", ident_msg.hw_version);
    printf("Product version:  %s\n", ident_msg.product_version);
    printf("Product string:   %s\n", ident_msg.product_string);
    printf("Serial number:    %s\n", ident_msg.serial_number);

    if(!simply_initialize_can(k_BAUDRATE)) {
        goto ErrorExit;
    }
    if (!simply_set_filter(0, 0)) {
        goto ErrorExit;
    }
    if (!simply_start_can()) {
        goto ErrorExit;
    }

    /* generate tx CAN message */
    can_msg_tx.ident = 0x100;
    can_msg_tx.dlc = 4;
    can_msg_tx.payload[0] = 0x01;
    can_msg_tx.payload[1] = 0x02;
    can_msg_tx.payload[2] = 0x03;
    can_msg_tx.payload[3] = 0x04;

    printf("Run application...\n");
    while(!_kbhit()) {
        if(TIME_OVER(last_sent, 1000)) {
            last_sent = tick();

            /* send CAN message every second */
            if (!simply_send(&can_msg_tx)) {
                goto ErrorExit;
            }

            /* print status message*/
            if (!status()) {
                goto ErrorExit;
            }

        }
        /* handle received messages */
        result = receive_messages();
        if (result == 0) {
            /* no message received */
            Sleep(1);
        }
        else if (result == -1) {
            /* error occurred */
            goto ErrorExit;
        }
    }

    simply_stop_can();

ErrorExit:
    error = simply_get_last_error();
    if (error != SIMPLY_S_NO_ERROR) {
        printf("Error: %d\n", error);
        (void) simply_close();
        return -1;
    }
    else {
        (void) simply_close();
        return 0;
    }
}

/***************************************************************************************************
**    static functions
***************************************************************************************************/

/***************************************************************************************************
Function:
  status

Description:
  Get and print the status.

Parameters:
  -

Return value:
  true  - function succeeded
  false - error occurred

***************************************************************************************************/
static bool status(void) {
    can_sts_t can_sts;

    /* format and print CAN status */
    if(!simply_can_status(&can_sts)) {
        return false;
    }
    printf("CAN status: ");
    if (can_sts.sts & CAN_STATUS_RUNNING) {
        printf("--- ");
    }
    if (can_sts.sts & CAN_STATUS_RESET) {
        printf("RST ");
    }

    if (can_sts.sts & CAN_STATUS_BUSOFF) {
        printf("BOF ");
    } else {
        printf("--- ");
    }

    if (can_sts.sts & CAN_STATUS_ERRORSTATUS) {
        printf("ERR ");
    } else {
        printf("--- ");
    }

    if (can_sts.sts & CAN_STATUS_RXOVERRUN) {
        printf("RxO ");
    } else {
        printf("--- ");
    }

    if (can_sts.sts & CAN_STATUS_TXOVERRUN) {
        printf("TxO ");
    } else {
        printf("--- ");
    }

    if (can_sts.sts & CAN_STATUS_PENDING) {
        printf("PDG ");
    } else {
        printf("--- ");
    }
    printf("\n");

    return true;
}

/***************************************************************************************************
Function:
  receive_messages

Description:
  Get and print a received message.

Parameters:
  -

Return value:
  1   - message received
  0   - no message available
  -1  - error occurred

***************************************************************************************************/
static uint8_t receive_messages(void) {
    int8_t res = 0;
    can_msg_t can_msg_rx;
    uint8_t flag_string[5];

    res = simply_receive(&can_msg_rx);
    if (res == 1) {
        /* format and print received message */
        sprintf((char*) flag_string, " ");
        if (can_msg_rx.ident & 0x80000000) {
            sprintf((char*) flag_string, "E");
        }
        if (can_msg_rx.dlc & 0x80) {
            can_msg_rx.dlc = 1;
            strcat((char*) flag_string, "R");
        }
        printf("0x%-8X %-2s [%d] ", can_msg_rx.ident & 0x7FFFFFFF, flag_string, can_msg_rx.dlc);
        for (uint8_t byte=0; byte < can_msg_rx.dlc; byte++) {
            printf("%02X ",can_msg_rx.payload[byte]);
        }
        printf("\n");
        return 1;
    }
    else if (res == -1) {
        /* error occurred */
        return -1;
    }

    return 0;
}

/***************************************************************************************************
Function:
  tick

Description:
  Get the actual tick in ms resolution.

Parameters:
  -

Return value:
  current tick in ms

***************************************************************************************************/
static uint32_t tick()
{
  #ifdef __unix__
  {
    struct timespec s_tmr;

    clock_gettime(CLOCK_MONOTONIC, &s_tmr);
    return (uint32_t) ((s_tmr.tv_sec * 1000 + s_tmr.tv_nsec/1000000));
  }
  #elif defined(_WIN32) || defined(WIN32)
  {
    static bool initialized = false;
    static LARGE_INTEGER frequency;
    LARGE_INTEGER count;
    long sec = 0;
    long nsec = 0;

    if (!initialized)
    {
        initialized = true;

        if (0 == QueryPerformanceFrequency(&frequency))
        {
            frequency.QuadPart = 0;
        }
    }

    if ((frequency.QuadPart <= 0) ||
        (0 == QueryPerformanceCounter(&count)))
    {
        return -1;
    }

    sec = (long) (count.QuadPart / frequency.QuadPart);
    nsec = (long)(((count.QuadPart % frequency.QuadPart) * 1e9) / frequency.QuadPart);

    return (uint32_t) sec * 1000 + nsec / 1000000;
  }
  #endif
}

/***************************************************************************************************
Function:
  CtrlHandler

Description:
  Ctrl handler function

Parameters:
  fdwCtrlType (IN)  - control type

Return value:
  exit return value

***************************************************************************************************/
#if defined(_WIN32) || defined(WIN32)
BOOL WINAPI CtrlHandler(DWORD fdwCtrlType) {
#else
void CtrlHandler() {
#endif
    (void) simply_stop_can();
    (void) simply_close();
    exit(0);
}

/***************************************************************************************************
Function:
  _kbhit

Description:
  Check if any key is pressed

Parameters:
  -

Return value:
  true - key pressed
  false - no key pressed

***************************************************************************************************/
#if !defined(_WIN32) && !defined(WIN32)
bool _kbhit() {
    struct timeval tv;
    fd_set fds;

    tv.tv_sec = 0;
    tv.tv_usec = 0;
    FD_ZERO(&fds);
    FD_SET(STDIN_FILENO, &fds);

    select(STDIN_FILENO+1, &fds, NULL, NULL, &tv);
    return (FD_ISSET(0, &fds));
}
#endif

#ifdef __linux__ 

/***************************************************************************************************
Function:
  _kbhit

Description:
  The function finds the last occurrence of the substring s2 in the string s1.

Parameters:
  s1 − This is the main C string to be scanned.
  s2 − This is the small string to be searched within s1.

Return value:
  pointer to the last occurrence of s2 in s1, 
  or a null pointer if the sequence is not present in s1.

***************************************************************************************************/
char *rstrstr(char *s1, char *s2) {
  size_t  s1len = strlen(s1);
  size_t  s2len = strlen(s2);
  char *s;

  if(s2len > s1len) {
    return NULL;
  }
  for(s = s1 + s1len - s2len; s >= s1; --s) {
    if(strncmp(s, s2, s2len) == 0) {
      return s;
    }
  }
  return NULL;
}

/***************************************************************************************************
Function:
  retrieve_serial_port

Description:
  Retrieve the serial port where the simplyCAN device is connnected.

Parameters:
  buffer (OUT)  - memory buffer for the result string
  size   (IN)   - size of the buffer in bytes (32 bytes should be sufficient)

Return value:
  pointer to the result string (buffer)n or NULL

***************************************************************************************************/
char *retrieve_serial_port(char *buffer, int size) {
    FILE *fp;
    char *ptr1, *ptr2;

    if((fp = popen("/bin/ls -lA /dev/serial/by-id", "r" )) == NULL) {
       return NULL;
    }

    /* Read pipe until COM port is found. */
    while(fgets(buffer, size, fp)) {
        ptr1 = strstr(buffer, "IXXAT_simplyCAN");
        if(ptr1 != NULL) {
            ptr2 = rstrstr(ptr1, "tty");
            if(ptr2 != NULL) {
                // remove the new line char
                ptr2[strlen(ptr2) - 1] = 0;
                pclose(fp);
                strcpy(buffer, "/dev/");
                strcat(buffer, ptr2);
                return buffer;
            }
        }
   }
   pclose(fp);
   return NULL;
}

#elif _WIN32

/***************************************************************************************************
Function:
  retrieve_serial_port

Description:
  Retrieve the serial port where the simplyCAN device is connnected.

Parameters:
  buffer (OUT)  - memory buffer for the result string
  size   (IN)   - size of the buffer in bytes (32 bytes should be sufficient)

Return value:
  pointer to the result string (buffer)n or NULL

***************************************************************************************************/
char *retrieve_serial_port(char *buffer, int size) {
    FILE * fp;
    char *ptr1, *ptr2;

    if((fp = _popen("wmic path Win32_SerialPort Where \"PNPDeviceID like 'USB\\\\VID_08D8&PID_001D%%%%'\" Get DeviceID", "rt" )) == NULL) {
       return NULL;
    }

    /* Read pipe until COM port is found. */
    while(fgets(buffer, size, fp)) {
        ptr1 = strstr(buffer, "COM");
        if(ptr1 != NULL) {
            ptr2 = strchr(ptr1, ' ');
            if(ptr2 != NULL) {
                *ptr2 = '\0';
                feof(fp);
                return ptr1;
            }
        }
   }
   feof(fp);
   return NULL;
}

#endif
