/*
    RS232 - Map the serial port of the PSX to a PC COMx
  ================================================================
  This code is done for making an easy conversion to plugin format
  ================================================================
*/
#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "fpse.h"

//#define DEBUG_SERIAL

#ifndef DEBUG_SERIAL
#define SERPORT     FPSEIni.ComName
#else
#define SERPORT     DefaultSerialPort
#endif

static HANDLE    h;
static HANDLE    hcom;
static int       rs232=0;
static SIO_Type *SioRs232;
static char     *DefaultSerialPort = "COM1";

DWORD CALLBACK ComThread( HANDLE h )
{
 DWORD      mask;
 DWORD      read,ret;
 DWORD      dwModemStatus;
 OVERLAPPED osRead = { 0 };
 char       ch;

// wait for received characters
 if( !SetCommMask(h,EV_CTS | EV_DSR | EV_RXCHAR) ) {
    printf("SetCommMask failed\n");
    return FPSE_ERR;
 }

 while (!rs232) ;

 do {
// get the event mask
    if( WaitCommEvent(h, &mask, NULL) ) {
#ifdef DEBUG_SERIAL
        printf("event happen\n");
#endif
        read=0;
        if (mask & EV_RXCHAR) {
            do {
                ret = ReadFile(h,&ch,1,&read,&osRead);
                if (!ret && GetLastError() != ERROR_IO_PENDING) break;
            } while (!ret);
            if (!ret) {
                printf("Error in ReadFile(%s)\n",SERPORT);
                read = 0;
            }
        }

        if ( mask & (EV_CTS|EV_DSR) ) {
            if (!GetCommModemStatus(h, &dwModemStatus)) {
                printf("Error in GetCommModemStatus\n");
                break;
            }
            if (mask & EV_CTS) {
                SioRs232->Delta |= CTS;
                if (MS_CTS_ON & dwModemStatus) SioRs232->Status |= CTS;
                                          else SioRs232->Status &= ~CTS;
            }
            if (mask & EV_DSR) {
                SioRs232->Delta |= DSR;
                if (MS_DSR_ON & dwModemStatus) SioRs232->Status |= DSR;
                                          else SioRs232->Status &= ~DSR;
            }
#ifdef DEBUG_SERIAL
            if (SioRs232->Status & CTS) printf("CTS ON\n");
                                   else printf("CTS OFF\n");
            if (SioRs232->Status & DSR) printf("DSR ON\n");
                                   else printf("DSR OFF\n");
#endif
        }
#ifndef DEBUG_SERIAL
        sio_async(SioRs232,&ch,read);
#endif
        mask = 0;
    }
 } while(rs232);

// tell terminal thread to quit
 printf("Shutdown serial emulator.\n");
 SetCommMask(h,0);

 return FPSE_OK;
}

int RS232_Init(SIO_Type *sio)
{
    DWORD        id;
    COMMTIMEOUTS timeouts;

// check if the driver is already installed
    if (rs232) return FPSE_WARN;

#ifndef DEBUG_SERIAL
    if (SERPORT == NULL) SERPORT = DefaultSerialPort;
#endif

// open port for overlapped I/O
    h = CreateFile(SERPORT,
                   GENERIC_READ|GENERIC_WRITE,
                   0,NULL,
                   OPEN_EXISTING,
                   FILE_FLAG_OVERLAPPED,
                   NULL);

    if (h == INVALID_HANDLE_VALUE) return FPSE_ERR;

// set time-out
    timeouts.ReadIntervalTimeout         = 0;
    timeouts.ReadTotalTimeoutMultiplier  = 0;
    timeouts.ReadTotalTimeoutConstant    = 0;
    timeouts.WriteTotalTimeoutMultiplier = 0;
    timeouts.WriteTotalTimeoutConstant   = 0;

    if (!SetCommTimeouts(h, &timeouts)) {
        printf("Error setting time-outs.\n");
        CloseHandle(h);
        return FPSE_ERR;
    }

// Set default rate at 9600, 8bit, 1stop
    sio->Baud = SIOBAUDCLOCK/9600;
    sio->Mode = PRESCALER1 | BYTE8BIT | STOP1BIT;
    sio->IrqSrc = SIO_IRQ_IMM;

// Update physical port
    RS232_UpdateStatus(sio);

// Create Thread
    SioRs232 = sio;
    hcom = CreateThread(NULL,0,ComThread,h,0,&id);
    if( hcom == INVALID_HANDLE_VALUE ) {
        printf("CreateThread failed\n");
        return FPSE_ERR;
    }

// Now the driver is correctly installed
    rs232 = 1;

    return FPSE_OK;
}

void RS232_Close(SIO_Type *sio)
{
 if (!rs232) return;

 TerminateThread(hcom,0);
 CloseHandle(hcom);
 CloseHandle(h);
 rs232 = 0;
}

static BOOL WriteABuffer(char * lpBuf, DWORD dwToWrite)
{
   OVERLAPPED osWrite = {0};
   DWORD dwWritten;
   DWORD dwRes;
   BOOL fRes;

   // Create this write operation's OVERLAPPED structure's hEvent.
   osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
   if (osWrite.hEvent == NULL)
      // error creating overlapped event handle
      return FALSE;

   // Issue write.
   if (!WriteFile(h, lpBuf, dwToWrite, &dwWritten, &osWrite)) {
      if (GetLastError() != ERROR_IO_PENDING) { 
         // WriteFile failed, but isn't delayed. Report error and abort.
         fRes = FALSE;
      }
      else {
         // Write is pending.
         dwRes = WaitForSingleObject(osWrite.hEvent, INFINITE);
         switch(dwRes)
         {
            // OVERLAPPED structure's event has been signaled. 
            case WAIT_OBJECT_0:
                 if (!GetOverlappedResult(h, &osWrite, &dwWritten, TRUE)) // FALSE))
                       fRes = FALSE;
                 else
                  // Write operation completed successfully.
                  fRes = TRUE;
                 break;
            
            default:
                 // An error has occurred in WaitForSingleObject.
                 // This usually indicates a problem with the
                // OVERLAPPED structure's event handle.
                 fRes = FALSE;
                 break;
         }
      }
   }
   else
      // WriteFile completed immediately.
      fRes = TRUE;

   CloseHandle(osWrite.hEvent);
   return fRes;
}

void RS232_WriteData(SIO_Type *sio)
{
    if ( WriteABuffer(sio->TxBuf,sio->TxNum) != TRUE) {
        printf("WriteFile failed\n");
    }
}

void RS232_UpdateStatus(SIO_Type *sio)
{
    DCB dcb;

// set DCB
    memset(&dcb,0,sizeof(dcb));
    dcb.DCBlength = sizeof(dcb);

// Setup baud rate
    switch (sio->Mode & 3) {
    case 0:
    case 1:
        dcb.BaudRate = SIOBAUDCLOCK / sio->Baud;
        break;
    case 2:
        dcb.BaudRate = SIOBAUDCLOCK / (sio->Baud*16);
        break;
    case 3:
        dcb.BaudRate = SIOBAUDCLOCK / (sio->Baud*64);
        break;
    }

// Setup parity
    if (sio->Mode & PARITY) {
        if (sio->Mode & PARITY_ODDEVEN) dcb.Parity = ODDPARITY;
                                   else dcb.Parity = EVENPARITY;
    } else dcb.Parity = NOPARITY;

// Setup byte lenght
    dcb.ByteSize = 5 + ((sio->Mode >> 2) & 0x3);

// Setup stop bit lenght
    switch (sio->Mode & 0xC0) {
    case 0:
    case STOP1BIT:
        dcb.StopBits = ONESTOPBIT;
        break;
    case STOP15BIT:
        dcb.StopBits = ONE5STOPBITS;
        break;
    case STOP2BIT:
        dcb.StopBits = TWOSTOPBITS;
        break;
    }

    dcb.fBinary = 1;
    if (sio->Ctrl.Control8.Line & DTR) dcb.fDtrControl = DTR_CONTROL_ENABLE;
                                  else dcb.fDtrControl = DTR_CONTROL_DISABLE;

    if (sio->Ctrl.Control8.Line & RTS) dcb.fRtsControl = RTS_CONTROL_ENABLE;
                                  else dcb.fRtsControl = RTS_CONTROL_DISABLE;

    dcb.fOutxCtsFlow = FALSE;
    dcb.fOutxDsrFlow = FALSE;

    if(!SetCommState(h,&dcb))
        printf("RS232_UpdateStatus() failed\n");
}

#ifdef DEBUG_SERIAL
#include <conio.h>
char str[]={"ciao"};
void main()
{
 SIO_Type sio;

 sio.FifoSize = 4;
 if (RS232_Init(&sio) != FPSE_OK) {
    printf("non configuro il sio\n");
    return;
 }

 memcpy(sio.TxBuf,str,4);
 sio.TxNum = 4;
 RS232_WriteData(&sio);
 while (getch() != 27);

 RS232_Close(&sio);
}
#endif
