#include "lruserialport.h"

#include <QEvent>

#include <qDebug>

static int lru_serial_port_id=1;

//#define USE_QT_PORT

#ifdef USE_QT_PORT
class LruSerialPort::Implementation
{
public:
    QSerialPort realPort;

    Implemetation(LruSerialPort&)
    {
    }
};

#else

#include <QThread>
#include <windows.h>

#include <string.h>

#define QGSERIALPORT_RX_BUFFER_SIZE 128

enum { RX_BUFFER_SIZE=QGSERIALPORT_RX_BUFFER_SIZE, RX_BUFFERS=32, RX_BUFFER_MASK=(RX_BUFFERS-1)};

static QEvent::Type rxEventType=QEvent::None;

class ReceiverHander
{
public:
  virtual bool process_data(char* data, unsigned int len)=0;
};

static QString getLastErrorMsg() {
    LPWSTR bufPtr = NULL;
    DWORD err = GetLastError();
    FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
                   FORMAT_MESSAGE_FROM_SYSTEM |
                   FORMAT_MESSAGE_IGNORE_INSERTS,
                   NULL, err, 0, (LPWSTR)&bufPtr, 0, NULL);
    const QString result =
        (bufPtr) ? QString::fromUtf16((const ushort*)bufPtr).trimmed() :
                   QString("Unknown Error %1").arg(err);
    LocalFree(bufPtr);
    return result;
}


static QString portFullPath(const QString& port)
{
  return QString("\\\\.\\")+port;
}

class QgSerialPort: public QThread
{
public:
  struct {
    volatile HANDLE hCom;
    HANDLE hEvent;
    HANDLE hBreak;
    HANDLE hRead;
    HANDLE hWrite;
    HANDLE hEmpty;
    OVERLAPPED rx_overlap;
    OVERLAPPED tx_overlap;
    OVERLAPPED tx_empty_overlap;

    DWORD waitCommEvent;

    QObject* listener;

    ReceiverHander* receiverHandler;
  } cx;

  LruSerialPort& sp;
  QString portName;
  bool is_open;

  DWORD s_parity;
  DWORD s_baud;

  QgSerialPort(LruSerialPort& sport):
    sp(sport),
    is_open(false),
    s_parity(EVENPARITY),
    s_baud(CBR_9600)
  {
    memset(&cx, 0, sizeof cx);

    cx.hEvent=CreateEvent(0, FALSE, FALSE, 0);
    cx.hRead=CreateEvent(0, TRUE, FALSE, 0);
    cx.hWrite=CreateEvent(0, TRUE, TRUE, 0);
    cx.hBreak=CreateEvent(0, FALSE, FALSE, 0);
    cx.hEmpty=CreateEvent(0, TRUE, FALSE, 0);

    cx.rx_overlap.hEvent=cx.hRead;
    cx.tx_overlap.hEvent=cx.hWrite;

    if (rxEventType==QEvent::None)
      rxEventType=(QEvent::Type)QEvent::registerEventType();

    cx.hCom=INVALID_HANDLE_VALUE;
  }

  void close()
  {
      please_terminate=true;
      CloseHandle(cx.hWrite);
      CloseHandle(cx.hRead);
  }

  ~QgSerialPort()
  {
      close();
  }

  void setPortName(const QString name)
  {
      portName=name;
  }
  void setFlowControl(QSerialPort::FlowControl)
  {
  }

  void setDataBits(QSerialPort::DataBits)
  {
  }

  void setReadBufferSize(int/*n*/)
  {
  }

  void setParity(QSerialPort::Parity p)
  {
      s_parity= p==QSerialPort::EvenParity ? EVENPARITY : NOPARITY;
  }


  void setBaudRate(QSerialPort::BaudRate b)
  {
      s_baud= b==QSerialPort::Baud9600 ? CBR_9600: CBR_115200;
  }

  bool open(QIODevice::OpenMode)
  {
    QString name=portFullPath(portName);
    cx.hCom=CreateFileA(portName.toLatin1().constData(),
                         GENERIC_READ|GENERIC_WRITE,
                         0,
                         0,
                         OPEN_EXISTING,
                         FILE_FLAG_OVERLAPPED,
                         0);
    if (cx.hCom==INVALID_HANDLE_VALUE)
    {
        qDebug()<<"POER Error:"<<name<<getLastErrorMsg();
        return false;
    }
    DCB dcb;
    /*BOOL fSuccess=*/GetCommState(cx.hCom, &dcb);
    dcb.fOutxCtsFlow=FALSE;
    dcb.fOutxDsrFlow=FALSE;
    dcb.fDtrControl=FALSE;
    dcb.BaudRate=s_baud; //CBR_9600;
    dcb.ByteSize=8;
    dcb.Parity=s_parity; //EVENPARITY;
    dcb.StopBits=ONESTOPBIT;
    SetCommState(cx.hCom, &dcb);

    COMMTIMEOUTS tm;
    memset(&tm, 0, sizeof tm);
    tm.ReadIntervalTimeout=10; //MAXDWORD; //50;
    tm.ReadTotalTimeoutMultiplier=10; //MAXDWORD; //50;
    tm.ReadTotalTimeoutConstant=100000;

    tm.WriteTotalTimeoutConstant=10;
    tm.WriteTotalTimeoutMultiplier=10;
    /*
    tm.ReadIntervalTimeout=100;
    tm.ReadTotalTimeoutConstant=10000;
    tm.ReadTotalTimeoutMultiplier=10;
    tm.WriteTotalTimeoutConstant=1000;
    tm.WriteTotalTimeoutMultiplier=10;
    */
    SetCommTimeouts(cx.hCom, &tm);
    SetupComm(cx.hCom, 2048, 2048);

    is_open=true;
    start();

    SetEvent(cx.hEvent);

    return true;
  }

  bool isOpen() const
  {
      return is_open;
  }

  bool write_immediate(unsigned char c)
  {
    DWORD write_out;
    BOOL res=WriteFile(cx.hCom, &c, 1, &write_out, &cx.tx_overlap);
    return res || ERROR_IO_PENDING==GetLastError();
  }

  bool write_(const void* data, unsigned int len)
  {
    static bool pending_op=false;
    static char buffer[3000];
    const void* p;

    if (cx.hCom==INVALID_HANDLE_VALUE)
      return false;

    DWORD write_out;

    if (pending_op)
    {
      BOOL res=GetOverlappedResult(cx.hCom, &cx.tx_overlap, &write_out, TRUE);
      if (!res)
        return false;
    }

    pending_op=false;

    if (len<=sizeof buffer)
    {
      memcpy(buffer, data, len);
      p=buffer;
    }
    else
      p=data;

    BOOL res=WriteFile(cx.hCom, p, len, &write_out, &cx.tx_overlap);
    if (!res)
    {
      if (ERROR_IO_PENDING==GetLastError())
      {
        if (p==data)  //Wait immediatly
        {
          BOOL res=GetOverlappedResult(cx.hCom, &cx.tx_overlap, &write_out, TRUE);
          if (res && write_out==len)
            return true;
          else
            return false;
        }
        else
          pending_op=true;
      }
      return true;
    }
    else if (write_out==len)
      return true;

    return false;
  }

  volatile int rx_consumed[RX_BUFFERS];
  volatile bool please_terminate;
  volatile bool please_break;

  void run()
  {
    please_terminate=false;
    please_break=false;

    char buffer[RX_BUFFERS][RX_BUFFER_SIZE];
    unsigned int current_buffer=0;

    memset((void*)rx_consumed, 0, sizeof rx_consumed);

    //unsigned int max_in_len=1;
    //setPriority(QThread::HighPriority);
    HANDLE waitHandles[4];

    waitHandles[0]=cx.hRead;
    waitHandles[1]=cx.hBreak;
    waitHandles[2]=cx.hEmpty;

    waitHandles[2]=0;
    const unsigned int wait_event_num=2;

    unsigned int tempWaitCommEvent=0;

    for(;!please_terminate;)
    {
      qDebug()<<"SP Listener: wait...";

      BOOL res=WaitForSingleObject(cx.hEvent, INFINITE);
      if (res!=WAIT_OBJECT_0)
        return;

      please_break=false;
      qDebug()<<"SP Listener: started...";

      for(;cx.hCom;)
      {
        DWORD read_in=0;
        DWORD ok=ReadFile(cx.hCom, buffer[current_buffer], sizeof buffer[0], &read_in, &cx.rx_overlap);
        if (!ok)
        {
          read_in=0;
          DWORD err=GetLastError();
          if (err==ERROR_IO_PENDING)
          {
            //ok=WaitForSingleObject(cx.hRead, INFINITE);
            ok=WaitForMultipleObjects(wait_event_num, waitHandles, FALSE, INFINITE);
            if (ok==WAIT_OBJECT_0)
            {
              err=GetOverlappedResult(cx.hCom, &cx.rx_overlap, &read_in, FALSE);
              if (!err)
                read_in=0;
            }
            else  if (ok==WAIT_OBJECT_0+2) //ComEvent
            {
              err=GetOverlappedResult(cx.hCom, &cx.tx_empty_overlap, &read_in, FALSE);
              if (!err)
                read_in=0;
              else
              {
                if (cx.waitCommEvent & EV_TXEMPTY)
                  tempWaitCommEvent=1;
                else
                  tempWaitCommEvent=0;
              }
            }
            else//if (ok>=WAIT_ABANDONED_0 && ok<=WAIT_ABANDONED_0+1)
              break;
          }
        }

        if (cx.hCom==INVALID_HANDLE_VALUE && please_break)
          break;
        if (please_terminate)
          break;

        if (read_in)
        {
          DWORD errorWord=0;
          ClearCommError(cx.hCom, &errorWord, 0);
          errorWord&=~(CE_OVERRUN|CE_RXOVER);
          if (errorWord)
          {
            tempWaitCommEvent=0;
            continue;
          }
          if (cx.receiverHandler)
          {
            if (!cx.receiverHandler->process_data(buffer[current_buffer], read_in))
            {
              tempWaitCommEvent=0;
              continue;
            }
          }

          rx_consumed[current_buffer]=1;
          Q_EMIT sp.asynchReadyRead(current_buffer, buffer[current_buffer], read_in, tempWaitCommEvent);
          //sp.propagateData(current_buffer, buffer[current_buffer], read_in, tempWaitCommEvent);
          unsigned int next_buffer=(current_buffer+1) & RX_BUFFER_MASK;
          if (rx_consumed[next_buffer]==0)
            current_buffer=next_buffer;
        }
        tempWaitCommEvent=0;
      }
    }
  }

  QByteArray pendingData;

  void flush()
  {
  }

  int bytesAvailable() const
  {
      return pendingData.size();
  }

  QByteArray readAll()
  {
     QByteArray tmp=pendingData;
     pendingData.clear();
     return tmp;
  }

  qint64 write(const QByteArray& d)
  {
      write_(d.constData(), d.size());
      return d.size();
  }
  qint64 write(const char* d, int len)
  {
      write_(d, len);
      return len;
  }
};

class LruSerialPort::Implementation
{
public:
    QgSerialPort realPort;

    Implementation(LruSerialPort& lru):
        realPort(lru)
    {
    }
};

#endif

LruSerialPort::LruSerialPort(const QString & name, deviceID device, QSerialPort::BaudRate baud, QSerialPort::Parity parity):
    //QSerialPort(),
    port_name(name),
    portId(lru_serial_port_id),
    realPortOk(false),
    inFilter(0),
    enabled(true),
    chars_in(0),
    chars_out(0),
    p_(* new Implementation(*this))
{
    if (!name.isEmpty() && (!name.startsWith('<')))
    {
        p_.realPort.setPortName(name);
        p_.realPort.setFlowControl(QSerialPort::NoFlowControl);
        p_.realPort.setDataBits(QSerialPort::Data8);
        p_.realPort.setReadBufferSize(256);
        p_.realPort.setParity(parity);
        p_.realPort.setBaudRate(baud);
        p_.realPort.open(QIODevice::ReadWrite);
        realPortOk=p_.realPort.isOpen();
        qDebug()<<"Open"<<name<<baud<<parity<<realPortOk;
#ifdef USE_QT_PORT
        connect(&p_.realPort, &QSerialPort::readyRead, this, [this]()
        {
            Q_EMIT readyRead();
        });
#else
        connect(this, &LruSerialPort::asynchReadyRead, this, [this](unsigned int index, const char* data,int len, unsigned int)
        {
            p_.realPort.pendingData.append(data, len);
            p_.realPort.rx_consumed[index]=0;
            Q_EMIT readyRead();
        }, Qt::QueuedConnection);
#endif
    }
    ++lru_serial_port_id;
    numDevice = device;
}

void LruSerialPort::close()
{
    p_.realPort.close();
}

void LruSerialPort::setEnabled(bool enable)
{
    enabled=enable;
}

void LruSerialPort::setFilter(LruSerialFilter* filter)
{
    inFilter=filter;
}

QByteArray LruSerialPort::readAll()
{
    if (p_.realPort.bytesAvailable()<1)
        return QByteArray();

    return p_.realPort.readAll();
}

QByteArray LruSerialPort::readData()
{
    if (!realPortOk)
        return QByteArray();

    if (p_.realPort.bytesAvailable()<1)
        return QByteArray();

    QByteArray data=p_.realPort.readAll();
    chars_in+=data.size();
    if (enabled)
    {
        bool ok=true;
        if (inFilter)
            ok=inFilter->serialFilterIn(*this, data);
        if (ok)
            return data;
    }
    return QByteArray();
}

qint64 LruSerialPort::write(const QByteArray& data)
{
    chars_out+=data.size();
    if (enabled && realPortOk)
        return p_.realPort.write(data);
    else
        return 0;
}

void LruSerialPort::writeChar(char c)
{
    ++chars_out;
    if (enabled && realPortOk)
        p_.realPort.write(&c, 1);
}

void LruSerialPort::flush()
{
    if (realPortOk)
        p_.realPort.flush();
}
