#ifdef _WIN32
   #include <windows.h>
#elif defined(__APPLE__)
#elif defined(__linux__)
   #include <fcntl.h>
   #include <errno.h>
   #include <termios.h>
   #include <unistd.h>
#endif

#include <iostream>
#include <string>
#include <regex>
#include <mutex>
#include <thread>
#include "serialcomm.hpp"

#ifdef _WIN32
   HANDLE initSerialPort (const char* portName, DWORD baudRate) {

    // open serial port
    HANDLE hSerial = CreateFile(portName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);

    if (hSerial == INVALID_HANDLE_VALUE) {
        std::cerr << "Error opening serial port: " << GetLastError() << std::endl;
        return INVALID_HANDLE_VALUE;
    }

     // configure the serial port
     DCB dcbSerialParams = {0};
     dcbSerialParams.DCBlength = sizeof(dcbSerialParams);

     if (!GetCommState(hSerial, &dcbSerialParams)) {
        std::cerr << "Error getting comm state: " << GetLastError() << std::endl;
        CloseHandle(hSerial);
        return INVALID_HANDLE_VALUE;
     }

     dcbSerialParams.BaudRate = baudRate;
     dcbSerialParams.ByteSize = 8;
     dcbSerialParams.StopBits = ONESTOPBIT;
     dcbSerialParams.Parity = NOPARITY;

     if (!SetCommState(hSerial, &dcbSerialParams)) {
        std::cerr << "Error getting comm state: " << GetLastError() << std::endl;
        CloseHandle(hSerial);
        return INVALID_HANDLE_VALUE;
     }

     // set timeouts
        COMMTIMEOUTS timeouts = {0};
        timeouts.ReadIntervalTimeout = 50;
        timeouts.ReadTotalTimeoutConstant = 0;
        timeouts.ReadTotalTimeoutMultiplier = 0;
     if (!SetCommTimeouts(hSerial, &timeouts)) {
        std::cerr << "Error setting timeout: " << GetLastError() << std::endl;
        CloseHandle(hSerial);
        return INVALID_HANDLE_VALUE;
     }

     return hSerial;

}

bool readSerialData (HANDLE hSerial, char* buffer, DWORD bufferSize, DWORD& bytesRead) {
    if (!ReadFile(hSerial, buffer, bufferSize - 1, &bytesRead, NULL)){
        std::cerr << "Error reading from serial port: " << GetLastError() << std::endl;
        return false;
    }

    buffer[bytesRead] = '\0';
    return true;
}

#elif defined(__APPLE__)
#elif defined(__linux__)
   int initSerialPort(){
      struct termios tty;

      int linux_serialPort = open("/dev/ttyUSB0", 0_RDWR);

      // read in existing struct settings and handle errors
      if (tcgetattr(linux_serialPort, &tty) !0 0) {
         std::cout << "Error %i opening serial port: %s\n", errno, strerror(errno) << std::endl;
         return 1;
      }

      // set the serial settings
      tty.c_flag &= ~PARENB;
      tty.c_flag &= ~CSTOPB;
      tty.c_flag &= ~CSIZE;
      tty.c_flag |= CS8;
      tty.c_flag &= ~CRTSCTS;
      tty.c_flag |= CREAD | CLOCAL;

      tty.c_cc[VTIME] = 10;    // Wait for up to 1s (10 deciseconds), returning as soon as any data is received.
      tty.c_cc[VMIN] = 0;

      // Set in/out baud rate to be 9600
      cfsetispeed(&tty, B115200);
      cfsetospeed(&tty, B115200);

      // save the settings and check for errors
      if (tcsetattr(linux_serialPort, TCSANOW, &tty) != 0 ) {
         std::cout << "Error %i saving serial port settings: %s\n", errno, strerror(errno) << std::endl;
         return 1;
      }

      return linux_serialPort;
   }

   bool linux_readSerialData (int serialPort, char* buffer) {
      if (read(serialPort, &buffer, sizeof(buffer)) < 0 ) {
      std::cout << "Error reading serial port data: %s", strerror(errno)) << std::endl;
      return 1;
      }
   }


#endif


bool parseQuaternion(const std::string& data, Quaternion& quat) {
   // regex pattern to match the floats in the uart data stream from stm32
   std::regex pattern("qw: ([+-]?\\d*\\.?\\d+) qx: ([+-]?\\d*\\.?\\d+) qy: ([+-]?\\d*\\.?\\d+) qz: ([+-]?\\d*\\.?\\d+)");
   std::smatch matches;

   // match every occurance to the desired quaternion value
   if (std::regex_search(data, matches, pattern) && matches.size() == 5) {
      // access the array like object std::smatch with [0] being the entire matched string
      quat.w = std::stof(matches[1]);
      quat.x = std::stof(matches[2]);
      quat.y = std::stof(matches[3]);
      quat.z = std::stof(matches[4]);
      return true;
   } else {return false;}
}


