// © Copyright The University of New South Wales 2018
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/.
//
//
// adapted from firmata_test project
// https://github.com/firmata/firmata_test
// Copyright 2010, Paul Stoffregen (paul@pjrc.com)
// with permission to redistribute and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.


#include "serial.h"

#if defined(MACOSX)
#include <sys/ioctl.h>                  // required for: ioctl function
#include <sys/_select.h>                // required for: select function
#include <IOKit/serial/IOSerialKeys.h>  // required for: kIOCalloutDeviceKey, kCFAllocatorDefault consts

#elif defined(WINDOWS)
#include <windows.h>
#define win32_err(s) FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, \
GetLastError(), 0, (s), sizeof(s), NULL)
#define QUERYDOSDEVICE_BUFFER_SIZE 262144

#else
#error "This platform is unsupported, sorry"

#endif


// Internals
void serial_inputdiscard(serial_t *port);
void serial_outputflush(serial_t *port);
int  serial_setcontrol(serial_t *port, int dtr, int rts);
// int  macos_ports(io_iterator_t *PortIterator, char **list, int max_ports, int index);


#if defined(MACOSX)
int macos_ports(io_iterator_t *PortIterator, char **list, int max_ports, int index)
{
    io_object_t modemService;
    CFTypeRef nameCFstring;
    char s[MAXPATHLEN];
    int numPorts = index;
    
    if( ! PortIterator || ! list) return -1;
    
    
    while ((modemService = IOIteratorNext(*PortIterator)) && numPorts < max_ports) {
        nameCFstring = IORegistryEntryCreateCFProperty(modemService,
                                                       CFSTR(kIOCalloutDeviceKey), kCFAllocatorDefault, 0);
        if (nameCFstring) {
            if (CFStringGetCString((const struct __CFString *)nameCFstring,
                                   s, sizeof(s), kCFStringEncodingASCII)) {
                list[numPorts] = (char*) malloc((MAXPATHLEN) * sizeof(char));
                strcpy(list[numPorts], s);
                numPorts++;
            }
            CFRelease(nameCFstring);
        }
        IOObjectRelease(modemService);
    }
    return numPorts;
}
#endif /* MACOSX */

// Return a list of all serial ports
int serial_portlist(char **list, int max_ports)
{
 
    int numPorts = 0;
    
#if defined(MACOSX)
    
    // adapted from SerialPortSample.c, by Apple
    // http://developer.apple.com/samplecode/SerialPortSample/listing2.html
    // and also testserial.c, by Keyspan
    // http://www.keyspan.com/downloads-files/developer/macosx/KesypanTestSerial.c
    // www.rxtx.org, src/SerialImp.c seems to be based on Keyspan's testserial.c
    // neither keyspan nor rxtx properly release memory allocated.
    // more documentation at:
    // http://developer.apple.com/documentation/DeviceDrivers/Conceptual/WorkingWSerial/WWSerial_SerialDevs/chapter_2_section_6.html
    
    mach_port_t masterPort;
    CFMutableDictionaryRef classesToMatch;
    io_iterator_t serialPortIterator;
    
    if (IOMasterPort(0, &masterPort) != KERN_SUCCESS) return 0;
    
    // a usb-serial adaptor is usually considered a "modem",
    // especially when it implements the CDC class spec
    classesToMatch = IOServiceMatching(kIOSerialBSDServiceValue);
    if (!classesToMatch) return 0;
    CFDictionarySetValue(classesToMatch, CFSTR(kIOSerialBSDTypeKey),
                         CFSTR(kIOSerialBSDModemType));
    if (IOServiceGetMatchingServices(masterPort, classesToMatch,
                                     &serialPortIterator) != KERN_SUCCESS) return 0;
    numPorts += macos_ports(&serialPortIterator, list, max_ports, numPorts);
    IOObjectRelease(serialPortIterator);

    /*
    // but it might be considered a "rs232 port", so repeat this
    // search for rs232 ports
    classesToMatch = IOServiceMatching(kIOSerialBSDServiceValue);
    if (!classesToMatch) return 0;
    CFDictionarySetValue(classesToMatch, CFSTR(kIOSerialBSDTypeKey),
                         CFSTR(kIOSerialBSDRS232Type));
    if (IOServiceGetMatchingServices(masterPort, classesToMatch,
                                     &serialPortIterator) != KERN_SUCCESS) return 0;
    numPorts += macos_ports(&serialPortIterator, list, max_ports, numPorts);
    IOObjectRelease(serialPortIterator);
    */
    
#elif defined(WINDOWS)
    // http://msdn.microsoft.com/en-us/library/aa365461(VS.85).aspx
    // page with 7 ways - not all of them work!
    // http://www.naughter.com/enumser.html
    // may be possible to just query the windows registary
    // http://it.gps678.com/2/ca9c8631868fdd65.html
    // search in HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM
    // Vista has some special new way, vista-only
    // http://msdn2.microsoft.com/en-us/library/aa814070(VS.85).aspx
    char *buffer, *p;
    //DWORD size = QUERYDOSDEVICE_BUFFER_SIZE;
    DWORD ret;
    
    buffer = (char *)malloc(QUERYDOSDEVICE_BUFFER_SIZE);
    if (buffer == NULL) return 0;
    
    
    
    memset(buffer, 0, QUERYDOSDEVICE_BUFFER_SIZE);
    ret = QueryDosDeviceA(NULL, buffer, QUERYDOSDEVICE_BUFFER_SIZE);
    if (ret) {
        printf("Detect Serial using QueryDosDeviceA: ");
        for (p = buffer; *p; p += strlen(p) + 1) {
            printf(":  %s", p);
            if (strncmp(p, "COM", 3)) continue;
            numPorts++;
            //list.Add(wxString(p) + ":");  // TODO fix the list orly
        }
    } else {
        char buf[1024];
        win32_err(buf);
        printf("QueryDosDeviceA failed, error \"%s\"\n", buf);
        printf("Detect Serial using brute force GetDefaultCommConfig probing: ");
        for (int i=1; i<=32; i++) {
            printf("try  %s", buf);
            COMMCONFIG cfg;
            DWORD len;
            snprintf(buf, sizeof(buf), "COM%d", i);
            if (GetDefaultCommConfig(buf, &cfg, &len)) {
                wxString name;
                name.Printf("COM%d:", i);
                //list.Add(name); // TODO fix the list orly
                numPorts++;
                printf(":  %s", buf);
            }
        }
    }
    free(buffer);

#endif /* MACOSX or WINDOWS */
  
    return numPorts;    
}

int serial_isopen(serial_t *port)
{
    if(port == NULL) return 0;
    return port->port_is_open;
}

// Open a port, by name.  Return 0 on success, non-zero for error
int serial_open(serial_t *port, char *name)
{
    if(port == NULL) return -1;    
    
    if (name == NULL) {
        sprintf(port->error_msg, "serial_open: No port name provided");
        return -1;
    }
    
    serial_close(port);
    
#if defined(MACOSX)
    
    int bits = 0;
    int port_fd = open(name, O_RDWR | O_NOCTTY | O_NONBLOCK);
    if (port_fd < 0) {
        sprintf(port->error_msg, "serial_open: Unable to open %s, %s\n", name, strerror(errno));
        //printf("Unable to open %s, %s\n", name, strerror(errno));
        return -1;
    }
    if (ioctl(port_fd, TIOCEXCL) == -1) {
        close(port_fd);
        sprintf(port->error_msg, "serial_open: Unable to get exclussive access to port %s\n", name);
        //printf("Unable to get exclussive access to port %s\n", name);
        return -1;
    }
    
    if (ioctl(port_fd, TIOCMGET, &bits) < 0) {
        close(port_fd);
        sprintf(port->error_msg, "serial_open: Unable to query serial port signals on %s\n", name);
        //printf("Unable to query serial port signals on %s\n", name);
        return -1;
    }
    
    bits &= ~(TIOCM_DTR | TIOCM_RTS);
    if (ioctl(port_fd, TIOCMSET, &bits) < 0) {
        close(port_fd);
        sprintf(port->error_msg, "serial_open: Unable to control serial port signals on %s\n", name);
        //printf("Unable to control serial port signals on %s\n", name);
        return -1;
    }
    if (tcgetattr(port_fd, &(port->settings_orig)) < 0) {
        close(port_fd);
        sprintf(port->error_msg, "serial_open: Unable to access baud rate on port %s\n", name);
        //printf("Unable to access baud rate on port %s\n", name);
        return -1;
    }
    
    port->port_fd = port_fd;
    memset(&(port->settings), 0, sizeof(port->settings));
    port->settings.c_cflag = CS8 | CLOCAL | CREAD | HUPCL;
    port->settings.c_iflag = IGNBRK | IGNPAR;
    serial_setbaud(port, port->baud_rate);
    tcflush(port->port_fd, TCIFLUSH);
    
#elif defined(WINDOWS)
    COMMCONFIG cfg;
    COMMTIMEOUTS timeouts;
    int got_default_cfg=0, port_num;
    char buf[1024], name_createfile[64], name_commconfig[64], *p;
    DWORD len;
    
    snprintf(buf, sizeof(buf), "%s", name.c_str());
    p = strstr(buf, "COM");
    if (p && sscanf(p + 3, "%d", &port_num) == 1) {
        printf("port_num = %d\n", port_num);
        snprintf(name_createfile, sizeof(name_createfile), "\\\\.\\COM%d", port_num);
        snprintf(name_commconfig, sizeof(name_commconfig), "COM%d", port_num);
    } else {
        snprintf(name_createfile, sizeof(name_createfile), "%s", name.c_str());
        snprintf(name_commconfig, sizeof(name_commconfig), "%s", name.c_str());
    }
    len = sizeof(COMMCONFIG);
    if (GetDefaultCommConfig(name_commconfig, &cfg, &len)) {
        // this prevents unintentionally raising DTR when opening
        // might only work on COM1 to COM9
        got_default_cfg = 1;
        memcpy(&(port->port_cfg_orig), &cfg, sizeof(COMMCONFIG));
        cfg.dcb.fDtrControl = DTR_CONTROL_DISABLE;
        cfg.dcb.fRtsControl = RTS_CONTROL_DISABLE;
        SetDefaultCommConfig(name_commconfig, &cfg, sizeof(COMMCONFIG));
    } else {
        printf("error with GetDefaultCommConfig\n");
    }
    port->port_handle = CreateFile(name_createfile, GENERIC_READ | GENERIC_WRITE,
                             0, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
    if (port->port_handle == INVALID_HANDLE_VALUE) {
        win32_err(buf);
        sprintf(port->error_msg, "Unable to open %s, %s", name, buf); //error_msg =  "Unable to open " + name + ", " + buf;
        return -1;
    }
    len = sizeof(COMMCONFIG);
    if (!GetCommConfig(port->port_handle, &(port->port_cfg), &len)) {
        CloseHandle(port->port_handle);
        win32_err(buf);
        sprintf(port->error_msg,"Unable to read communication config on %s, %s", name, buf); // error_msg = "Unable to read communication config on " + name + ", " + buf;
        return -1;
    }
    if (!got_default_cfg) {
        memcpy(&(port->port_cfg_orig), &(port->port_cfg), sizeof(COMMCONFIG));
    }
    // http://msdn2.microsoft.com/en-us/library/aa363188(VS.85).aspx
    port->port_cfg.dcb.BaudRate = baud_rate;
    port->port_cfg.dcb.fBinary = TRUE;
    port->port_cfg.dcb.fParity = FALSE;
    port->port_cfg.dcb.fOutxCtsFlow = FALSE;
    port->port_cfg.dcb.fOutxDsrFlow = FALSE;
    port->port_cfg.dcb.fDtrControl = DTR_CONTROL_DISABLE;
    port->port_cfg.dcb.fDsrSensitivity = FALSE;
    port->port_cfg.dcb.fTXContinueOnXoff = TRUE;
    port->port_cfg.dcb.fOutX = FALSE;
    port->port_cfg.dcb.fInX = FALSE;
    port->port_cfg.dcb.fErrorChar = FALSE;
    port->port_cfg.dcb.fNull = FALSE;
    port->port_cfg.dcb.fRtsControl = RTS_CONTROL_DISABLE;
    port->port_cfg.dcb.fAbortOnError = FALSE;
    port->port_cfg.dcb.ByteSize = 8;
    port->port_cfg.dcb.Parity = NOPARITY;
    port->port_cfg.dcb.StopBits = ONESTOPBIT;
    if (!SetCommConfig(port->port_handle, &(port->port_cfg), sizeof(COMMCONFIG))) {
        CloseHandle(port->port_handle);
        win32_err(buf);
        sprintf(port->error_msg,"Unable to write communication config to %s, %s", name, buf);// error_msg = "Unable to write communication config to " + name + ", " + buf;
        return -1;
    }
    if (!EscapeCommFunction(port->port_handle, CLRDTR | CLRRTS)) {
        CloseHandle(port->port_handle);
        win32_err(buf);
        sprintf(port->error_msg, "Unable to control serial port signals on %s, %s", name, buf); // error_msg = "Unable to control serial port signals on " + name + ", " + buf;
        return -1;
    }
    // http://msdn2.microsoft.com/en-us/library/aa363190(VS.85).aspx
    // setting to all zeros means timeouts are not used
    //timeouts.ReadIntervalTimeout		= 0;
    timeouts.ReadIntervalTimeout		= MAXDWORD;
    timeouts.ReadTotalTimeoutMultiplier	= 0;
    timeouts.ReadTotalTimeoutConstant	= 0;
    timeouts.WriteTotalTimeoutMultiplier	= 0;
    timeouts.WriteTotalTimeoutConstant	= 0;
    if (!SetCommTimeouts(port->port_handle, &timeouts)) {
        CloseHandle(port->port_handle);
        win32_err(buf);
        sprintf(port->error_msg, "Unable to write timeout settings to %s, %s", name, buf); // error_msg = "Unable to write timeout settings to " + name + ", " + buf;
        return -1;
    }
#endif /* MACOSX or WINDOWS */
    

    strncpy(port->port_name, name, MAXPATHLEN);
    port->port_is_open = 1;

    sprintf(port->error_msg, "");
    return 0;
}

const char *serial_getname(serial_t *port)
{
    if ( ! port || ! port->port_is_open) return "";
    return port->port_name;
}

// set the baud rate
int serial_setbaud(serial_t *port, int baud)
{
    if (port == NULL || baud <= 0) return -1;
    if (!port->port_is_open) return -1;
    
#if defined(MACOSX)
    if (-1 == cfsetospeed(&(port->settings), (speed_t)baud)) {
        sprintf(port->error_msg, "serial_setbaud: Couldn't set BAUD rate");
        return -1;
    }
    if (-1 == cfsetispeed(&(port->settings), (speed_t)baud)) {
        sprintf(port->error_msg, "serial_setbaud: Couldn't set BAUD rate");
        return -1;
    }
    
    if (-1 == tcsetattr(port->port_fd, TCSANOW, &(port->settings))) {
        sprintf(port->error_msg, "serial_setbaud: Couldn't set BAUD rate");
        return -1;
    }
    
#elif defined(WINDOWS)
    DWORD len = sizeof(COMMCONFIG);
    port->port_cfg.dcb.BaudRate = baud;
    SetCommConfig(port->port_handle, &(port->port_cfg), len);
    
#endif /* MACOSX or WINDOWS */
   
    port->baud_rate = baud;
    sprintf(port->error_msg, "");
    return 0;
}

// Read from the serial port.  Returns only the bytes that are
// already received, up to count.  This always returns without delay,
// returning 0 if nothing has been received
long serial_read(serial_t *port, void *ptr, int count)
{
    if (port == NULL || !port->port_is_open) return -1;
    if (count <= 0 || ptr == NULL) return 0;
    
#if defined(MACOSX)
    long n;
    n = read(port->port_fd, ptr, count);
    //printf("serial_read %ld bytes\n", n);
    if (n < 0 && (errno == EAGAIN || errno == EINTR)) return 0;
    return n;
    
#elif defined(WINDOWS)
    // first, we'll find out how many bytes have been received
    // and are currently waiting for us in the receive buffer.
    //   http://msdn.microsoft.com/en-us/library/ms885167.aspx
    //   http://msdn.microsoft.com/en-us/library/ms885173.aspx
    //   http://source.winehq.org/WineAPI/ClearCommError.html
    COMSTAT st;
    DWORD errmask=0, num_read, num_request;
    OVERLAPPED ov;
    int r;
    if (!ClearCommError(port->port_handle, &errmask, &st)) return -1;
    //printf("Read, %d requested, %lu buffered\n", count, st.cbInQue);
    if (st.cbInQue <= 0) return 0;
    // now do a ReadFile, now that we know how much we can read
    // a blocking (non-overlapped) read would be simple, but win32
    // is all-or-nothing on async I/O and we must have it enabled
    // because it's the only way to get a timeout for WaitCommEvent
    num_request = ((DWORD)count < st.cbInQue) ? (DWORD)count : st.cbInQue;
    ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (ov.hEvent == NULL) return -1;
    ov.Internal = ov.InternalHigh = 0;
    ov.Offset = ov.OffsetHigh = 0;
    if (ReadFile(port->port_handle, ptr, num_request, &num_read, &ov)) {
        // this should usually be the result, since we asked for
        // data we knew was already buffered
        //printf("Read, immediate complete, num_read=%lu\n", num_read);
        r = num_read;
    } else {
        if (GetLastError() == ERROR_IO_PENDING) {
            if (GetOverlappedResult(port->port_handle, &ov, &num_read, TRUE)) {
                //printf("Read, delayed, num_read=%lu\n", num_read);
                r = num_read;
            } else {
                //printf("Read, delayed error\n");
                r = -1;
            }
        } else {
            //printf("Read, error\n");
            r = -1;
        }
    }
    CloseHandle(ov.hEvent);
    // TODO: how much overhead does creating new event objects on
    // every I/O request really cost?  Would it be better to create
    // just 3 when we open the port, and reset/reuse them every time?
    // Would mutexes or critical sections be needed to protect them?
    return r;
    
#endif /* MACOSX or WINDOWS */
    
}

// Write to the serial port.  This blocks until the data is
// sent (or in a buffer to be sent).  All bytes are written,
// unless there is some sort of error.
long serial_write(serial_t *port, const void *ptr, int len)
{
    //printf("Write %d\n", len);
    if (port == NULL || !port->port_is_open) return -1;
    if (len == 0 || ptr == NULL) return 0;
    
#if defined(MACOSX)
    long n, written=0;
    fd_set wfds;
    struct timeval tv;
    while (written < len) {
        n = write(port->port_fd, (const char *)ptr + written, len - written);
        if (n < 0 && (errno == EAGAIN || errno == EINTR)) n = 0;
        //printf("Write, n = %d\n", n);
        if (n < 0) return -1;
        if (n > 0) {
            written += n;
        } else {
            tv.tv_sec = 10;
            tv.tv_usec = 0;
            FD_ZERO(&wfds);
            FD_SET(port->port_fd, &wfds);
            n = select(port->port_fd+1, NULL, &wfds, NULL, &tv);
            if (n < 0 && errno == EINTR) n = 1;
            if (n <= 0) return -1;
        }
    }
    return written;

#elif defined(WINDOWS)
    DWORD num_written;
    OVERLAPPED ov;
    int r;
    ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (ov.hEvent == NULL) return -1;
    ov.Internal = ov.InternalHigh = 0;
    ov.Offset = ov.OffsetHigh = 0;
    if (WriteFile(port->port_handle, ptr, len, &num_written, &ov)) {
        //printf("Write, immediate complete, num_written=%lu\n", num_written);
        r = num_written;
    } else {
        if (GetLastError() == ERROR_IO_PENDING) {
            if (GetOverlappedResult(port->port_handle, &ov, &num_written, TRUE)) {
                //printf("Write, delayed, num_written=%lu\n", num_written);
                r = num_written;
            } else {
                //printf("Write, delayed error\n");
                r = -1;
            }
        } else {
            //printf("Write, error\n");
            r = -1;
        }
    };
    CloseHandle(ov.hEvent);
    return r;
    
#endif /* MACOSX or WINDOWS */
    
}

// Wait up to msec for data to become available for reading.
// return 0 if timeout, or non-zero if one or more bytes are
// received and can be read.  -1 if an error occurs
int serial_inputwait(serial_t *port, int msec)
{
    if (port == NULL || !port->port_is_open) return -1;

#if defined(MACOSX)
    fd_set rfds;
    struct timeval tv;
    tv.tv_sec = msec / 1000;
    tv.tv_usec = (msec % 1000) * 1000;
    FD_ZERO(&rfds);
    FD_SET(port->port_fd, &rfds);
    return select(port->port_fd+1, &rfds, NULL, NULL, &tv);
    
#elif defined(WINDOWS)
    // http://msdn2.microsoft.com/en-us/library/aa363479(VS.85).aspx
    // http://msdn2.microsoft.com/en-us/library/aa363424(VS.85).aspx
    // http://source.winehq.org/WineAPI/WaitCommEvent.html
    COMSTAT st;
    DWORD errmask=0, eventmask=EV_RXCHAR, ret;
    OVERLAPPED ov;
    int r;
    // first, request comm event when characters arrive
    if (!SetCommMask(port->port_handle, EV_RXCHAR)) return -1;
    // look if there are characters in the buffer already
    if (!ClearCommError(port->port_handle, &errmask, &st)) return -1;
    //printf("Input_wait, %lu buffered, timeout = %d ms\n", st.cbInQue, msec);
    if (st.cbInQue > 0) return 1;
    
    ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (ov.hEvent == NULL) return -1;
    ov.Internal = ov.InternalHigh = 0;
    ov.Offset = ov.OffsetHigh = 0;
    if (WaitCommEvent(port->port_handle, &eventmask, &ov)) {
        //printf("Input_wait, WaitCommEvent, immediate success\n");
        r = 1;
    } else {
        if (GetLastError() == ERROR_IO_PENDING) {
            ret = WaitForSingleObject(ov.hEvent, msec);
            if (ret == WAIT_OBJECT_0) {
                //printf("Input_wait, WaitCommEvent, delayed success\n");
                r = 1;
            } else if (ret == WAIT_TIMEOUT) {
                //printf("Input_wait, WaitCommEvent, timeout\n");
                GetCommMask(port->port_handle, &eventmask);
                r = 0;
            } else {  // WAIT_FAILED or WAIT_ABANDONED
                //printf("Input_wait, WaitCommEvent, delayed error\n");
                r = -1;
            }
        } else {
            //printf("Input_wait, WaitCommEvent, immediate error\n");
            r = -1;
        }
    }
    SetCommMask(port->port_handle, 0);
    CloseHandle(ov.hEvent);
    return r;
    
#endif /* MACOSX or WINDOWS */
}

// Discard all received data that hasn't been read
void serial_inputdiscard(serial_t *port)
{
    if (port == NULL || !port->port_is_open) return;
    
#if defined(MACOSX)
    tcflush(port->port_fd, TCIFLUSH);
    
#elif defined(WINDOWS)
    PurgeComm(port->port_handle, PURGE_RXCLEAR);
    
#endif /* MACOSX or WINDOWS */
}

// Wait for all transmitted data with Write to actually leave the serial port
void serial_outputflush(serial_t *port)
{
    if (port == NULL || !port->port_is_open) return;
    
#if defined(MACOSX)
    tcdrain(port->port_fd);
    
#elif defined(WINDOWS)
    FlushFileBuffers(port->port_handle);
    
#endif /* MACOSX or WINDOWS */
}

// set DTR and RTS,  0 = low, 1=high, -1=unchanged
int serial_setcontrol(serial_t *port, int dtr, int rts)
{
    if (port == NULL || !port->port_is_open) return -1;
    
#if defined(MACOSX)
    // on Mac OS X, "man 4 tty"
    int bits;
    if (ioctl(port->port_fd, TIOCMGET, &bits) < 0) return -1;
    if (dtr == 1) {
        bits |= TIOCM_DTR;
    } else if (dtr == 0) {
        bits &= ~TIOCM_DTR;
    }
    if (rts == 1) {
        bits |= TIOCM_RTS;
    } else if (rts == 0) {
        bits &= ~TIOCM_RTS;
    }
    if (ioctl(port->port_fd, TIOCMSET, &bits) < 0) return -1;;

#elif defined(WINDOWS)
    // http://msdn.microsoft.com/en-us/library/aa363254(VS.85).aspx
    if (dtr == 1) {
        if (!EscapeCommFunction(port->port_handle, SETDTR)) return -1;
    } else if (dtr == 0) {
        if (!EscapeCommFunction(port->port_handle, CLRDTR)) return -1;
    }
    if (rts == 1) {
        if (!EscapeCommFunction(port->port_handle, SETRTS)) return -1;
    } else if (rts == 0) {
        if (!EscapeCommFunction(port->port_handle, CLRRTS)) return -1;
    }
    
#endif /* MACOSX or WINDOWS */
    
    return 0;
}

// Close the port
void serial_close(serial_t *port)
{
    if ( ! port || !port->port_is_open) return;
    
    serial_outputflush(port);
    serial_inputdiscard(port);
    port->port_is_open = 0;
    port->baud_rate = 0;
    sprintf(port->port_name, "");
    
#if defined(MACOSX)
    tcsetattr(port->port_fd, TCSANOW, &(port->settings_orig));
    close(port->port_fd);

#elif defined(WINDOWS)
    //SetCommConfig(port->port_handle, &port_cfg_orig, sizeof(COMMCONFIG));
    CloseHandle(port->port_handle);

#endif /* MAXOSX or WINDOWS */

    sprintf(port->error_msg, "");
    port->port_fd = 0;
}




















