/*
 * Copyright (c) 2006 by Wayne C. Gramlich.
 * All rights reserved.
 */

/*
 * This module implements an interface to a serail port for both Linux
 * and Windows.
 */

#include "stheaders.h"
#include <assert.h>
#include <unistd.h>

#define SERIAL_PORT_OK 0
#define SERIAL_PORT_CLOSED 1
#define SERIAL_PORT_OPEN_FAILED 2
#define SERIAL_PORT_UNKNOWN_BAUD_RATE 3
#define SERIAL_PORT_BAUD_RATE_ERROR 4
#define SERIAL_PORT_ATTRIBUTES_FETCH_ERROR 5
#define SERIAL_PORT_ATTRIBUTES_SET_ERROR 6


typedef struct serial_port Serial_port_struct, *Serial_port;
typedef char *Str;
typedef void *String;

extern Str string__unix_string(String);
extern module___object serial_port__module__object;
extern type___reference unix_system_type_ref;

extern unsigned serial_port__byte_read(Serial_port, unsigned);
extern void serial_port__byte_write(Serial_port, unsigned, unsigned);
extern void serial_port__close(Serial_port);
extern unsigned serial_port__error_number_get(Serial_port);
extern unsigned serial_port__identical(Serial_port, Serial_port);
extern void serial_port__flush(Serial_port);
extern Serial_port serial_port__open(String, unsigned);

static Serial_port_struct serial_port_struct;
Serial_port serial_port___initial = &serial_port_struct;

static object___object serial_port_initial_object = {
    "",					/* Package name */
    "serial_port",			/* Type name */
    "??",				/* Object name */
    (type___reference *)0,		/*XXX: Fix me */
    (int *)0,				/* Size of character object */
    0,					/* Static needs count */
    (need___entry *)0,			/*XXX: Fix me Static needs */
    0,					/* Parameter needs count */
    (need___entry *)0,			/* Parameter needs */
    (void **)&serial_port___initial,	/* Object pointer */
    (instantiation___object *)0		/* Instantiation list */
};

static object___object *object_list[1] = {
	&serial_port_initial_object,
};

void
serial_port__external__initialize(void)
{
    extern module___object unix_serial_port__module__object;

    serial_port__module__object.object_count = 1;
    serial_port__module__object.objects = object_list;
}

unsigned
serial_port__identical(
    Serial_port serial_port1,
    Serial_port serial_port2)
{
    return serial_port1 == serial_port2;
}

#ifdef LINUX

#include "termios.h"
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

extern void *malloc(int);

typedef struct name_value Name_value_struct, *Name_value;
typedef struct termios Termios_struct, *Termios;

#define BUFFER_SIZE 1024

struct serial_port {
    unsigned baud_rate;		/* Baud rate */
    unsigned blocked;		/* 1=>{fd} in blocked mode; 0=>non blocked */
    unsigned error_number; 	/* Error number */
    int fd;			/* File descriptor */
    unsigned char out_buffer[BUFFER_SIZE]; /* Output buffer */
    int out_count; 		/* Output count */
    Termios_struct termios;	/* Termios structure */
};

static serial_port__block(
    Serial_port serial_port,
    unsigned block)
{
    int fd;
    int flags;

    fd = serial_port->fd;
    if (serial_port->blocked && !block) {
	/* Force {fd} into blocking mode */
	flags = fcntl(fd, F_GETFL);
	flags &= ~O_NONBLOCK;
	fcntl(fd, F_SETFD, flags);
	serial_port->blocked = 1;
    } else if (!serial_port->blocked && block) {
	/* Force {fd} into non_blocking mode */
	flags = fcntl(fd, F_GETFL);
	flags |= O_NONBLOCK;
	fcntl(fd, F_SETFD, flags);
	serial_port->blocked = 0;
    }
}

unsigned
serial_port__byte_read(
    Serial_port serial_port,
    unsigned block)
{
    unsigned char buffer[10];
    int in_count;

    serial_port__block(serial_port, block);
        
    in_count = read(serial_port->fd, buffer, 1);
    assert (in_count >= 0 && (in_count == 1 || block));
    if (in_count == 0) {
	return 0xffffffff;
    }
    return buffer[0];
}

void
serial_port__byte_write(
    Serial_port serial_port,
    unsigned byte,
    unsigned flush)
{
    unsigned out_count;

    out_count = serial_port->out_count;
    serial_port->out_buffer[out_count++] = byte;
    serial_port->out_count = out_count;

    if (flush || (out_count >= BUFFER_SIZE)) {
	serial_port__flush(serial_port);
    }
}

void
serial_port__close(
    Serial_port serial_port)
{
    (void)close(serial_port->fd);
    serial_port->error_number = SERIAL_PORT_CLOSED;
}

unsigned
serial_port__error_number_get(
    Serial_port serial_port)
{
    return serial_port->error_number;
}

void
serial_port__flush(
    Serial_port serial_port)
{
    unsigned out_count;

    out_count = serial_port->out_count;
    serial_port__block(serial_port, 1);
    assert( write(serial_port->fd,
      serial_port->out_buffer, out_count) == out_count);
    serial_port->out_count = 0;
}

Serial_port
serial_port__open(
    String path,
    unsigned baud_rate)
{
    Serial_port serial_port;
    int fd;
    Str unix_path;
    int speed;
    struct termios *termios;

    /* Allocate the Serial port object */
    serial_port = (Serial_port)malloc(sizeof(Serial_port_struct));
    (void)memset((void *)serial_port, 0, sizeof(Serial_port_struct));

    /* Open the serial port */
    unix_path = string__unix_string(path);
    fd = open(unix_path, O_RDWR, 0);
    if (serial_port->fd < 0) {
	serial_port___initial->error_number = SERIAL_PORT_OPEN_FAILED;
	return serial_port___initial;
    }

    /* Figure out the baud rate */
    speed = B0;
    switch (baud_rate) {
      case 1200:
	speed = B1200;
	break;
      case 2400:
	speed = B2400;
	break;
      case 4800:
	speed = B4800;
	break;
      case 9600:
	speed = B9600;
	break;
      case 19200:
	speed = B19200;
	break;
      case 115200:
	speed = B115200;
	break;
      case 230400:
	speed = B230400;
	break;
      default:
	serial_port___initial->error_number = SERIAL_PORT_UNKNOWN_BAUD_RATE;
	return serial_port___initial;
    }

    /* Start mucking with the serial port parameters */
    termios = &serial_port->termios;
    if (tcgetattr(fd, termios) < 0) {
	serial_port___initial->error_number =
	  SERIAL_PORT_ATTRIBUTES_FETCH_ERROR;
	return serial_port___initial;
    }
    cfmakeraw(termios);
    if (cfsetispeed(termios, speed) < 0) {
	serial_port___initial->error_number = SERIAL_PORT_BAUD_RATE_ERROR;
	return serial_port___initial;
    }
    if (cfsetospeed(termios, speed) < 0) {
	serial_port___initial->error_number = SERIAL_PORT_BAUD_RATE_ERROR;
	return serial_port___initial;
    }

    if (tcsetattr(fd, TCSANOW, termios) < 0) {
	serial_port___initial->error_number = SERIAL_PORT_ATTRIBUTES_SET_ERROR;
	return serial_port___initial;
    }

    /* Initialize {serial_port} */;
    serial_port->baud_rate = baud_rate;
    serial_port->blocked = 1;
    serial_port->fd = fd;
    serial_port->error_number = SERIAL_PORT_OK;

    return serial_port;
}

#endif /* LINUX */

#ifdef WINDOWS

#include <assert.h>

#include "windows.h"

#include "stdio.h"

#define BUFFER_SIZE 1024

struct serial_port {
    unsigned char out_buffer[BUFFER_SIZE]; /* output buffer */
    int blocked;		/* 1=>I/O will block; 0=>I/O will not block */
    int out_count;		/* Characters in {out_buffer} */
    HANDLE serial;		/* Handle for serial port */
    DCB parameters;		/* Serial port parameters */
    COMMTIMEOUTS time_outs;	/* Serial port time outs */
    unsigned error_number;	/* Error number for serial port */
};


static serial_port__block(
    Serial_port serial_port,
    unsigned block)
{
    COMMTIMEOUTS *time_outs;
    unsigned blocked;
    unsigned set;

    time_outs = &serial_port->time_outs;
    blocked = serial_port->blocked;

    set = 0;
    if (blocked && !block) {
	/* Force into blocking mode */
	time_outs->ReadIntervalTimeout = 0;
	time_outs->ReadTotalTimeoutConstant = 0;
	time_outs->ReadTotalTimeoutMultiplier = 0;
	time_outs->WriteTotalTimeoutConstant = 0;
	time_outs->WriteTotalTimeoutMultiplier = 0;
	set = 1;
    } else if (!blocked && block) {
	/* Force into non_blocking mode */
	time_outs->ReadIntervalTimeout = MAXDWORD;
	time_outs->ReadTotalTimeoutConstant = 0;
	time_outs->ReadTotalTimeoutMultiplier = 0;
	time_outs->WriteTotalTimeoutConstant = 0;
	time_outs->WriteTotalTimeoutMultiplier = 0;
	set = 1;
    }
    if (set) {
	/* (void)printf("Serial port block mode = %d\n", block); */
	if (!SetCommTimeouts(serial_port->serial, time_outs)) {
	    char last_error[1024];
	    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
	      FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(),
	      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), last_error,
	      1024, NULL);
	    (void)printf("serial_port__block=%s\n", last_error);
	    (void)fflush(stdout);
	}
	serial_port->blocked = block;
    }
}

unsigned
serial_port__byte_read(
    Serial_port serial_port,
    unsigned block)
{
    DWORD bytes_read;
    unsigned char buffer[10];
    unsigned result;

    /* (void)fprintf(stderr, "=>serial_port__byte_read(0x%x, %d)\n",
      serial_port, block); */

    serial_port__block(serial_port, block);
    if (!ReadFile(serial_port->serial, buffer, 1, &bytes_read, NULL)) {
	result = 0xffff0000;
    } else {
	if (bytes_read == 0) {
	    result = 0xffffffff;
	} else {
	    result = buffer[0];
	}
    }

    /* (void)fprintf(stderr, "<=serial_port__byte_read(0x%x, %d)=>0x%x\n",
      serial_port, block, result); */

    return result;
}

void
serial_port__byte_write(
    Serial_port serial_port,
    unsigned byte,
    unsigned flush)
{
    int out_count;
    unsigned char *out_buffer;

    /* (void)fprintf(stderr, "=>serial_port__byte_write(0x%x, 0x%x, %d)\n",
      serial_port, byte, flush); */

    out_count = serial_port->out_count;
    out_buffer = serial_port->out_buffer;
    out_buffer[out_count++] = byte;
    serial_port->out_count = out_count;
    if (flush || (out_count >= BUFFER_SIZE)) {
	serial_port__flush(serial_port);
    }

    /* (void)fprintf(stderr, "<=serial_port__byte_write(0x%x, 0x%x, %d)\n",
      serial_port, byte, flush); */
}

void
serial_port__close(
    Serial_port serial_port)
{
    CloseHandle(serial_port->serial);
    serial_port->error_number = SERIAL_PORT_CLOSED;
}

unsigned
serial_port__error_number_get(
    Serial_port serial_port)
{
    return serial_port->error_number;
}

void
serial_port__flush(
    Serial_port serial_port)
{
    DWORD bytes_written;
    unsigned out_count;

    /* (void)fprintf(stderr, "=>serial_port__flush(0x%x)\n", serial_port); */

    out_count = serial_port->out_count;
    if (!WriteFile(serial_port->serial,
      serial_port->out_buffer, out_count, &bytes_written, NULL)) {
	assert(0);
    }
    assert(bytes_written == out_count);
    serial_port->out_count = 0;

    /* (void)fprintf(stderr, "<=serial_port__flush(0x%x)\n", serial_port); */
}

Serial_port
serial_port__open(
    String path,
    unsigned baud_rate)
{
    Serial_port serial_port;
    HANDLE serial;
    Str unix_path;
    DCB *parameters;
    int speed;
    COMMTIMEOUTS *time_outs;

    /* (void)fprintf(stderr,
         "=>serial_port__open(0x%x, %d)\n", path, baud_rate); */

    serial_port = (Serial_port)malloc(sizeof(Serial_port_struct));
    (void)memset((void *)serial_port, 0, sizeof(Serial_port));
    unix_path = string__unix_string(path);
    
    serial = CreateFile(unix_path, GENERIC_READ | GENERIC_WRITE, 0, 0,
      OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
    if (serial == INVALID_HANDLE_VALUE) {
	char last_error[1024];

	if (GetLastError() == ERROR_FILE_NOT_FOUND) {
	    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
	      FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(),
	      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), last_error,
	      1024, NULL);
	    (void)printf("Error1=%s\n", last_error);
	    (void)fflush(stdout);
	    serial_port___initial->error_number = SERIAL_PORT_OPEN_FAILED;
	    return serial_port___initial;
	}
	FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
	  FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(),
	  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), last_error,
	  1024, NULL);
	(void)printf("Error2=%s\n", last_error);
	(void)fflush(stdout);
	serial_port___initial->error_number = SERIAL_PORT_OPEN_FAILED;
	return serial_port___initial;
    }

    speed = 0;
    switch (baud_rate) {
      case 1200:
	speed = CBR_1200;
	break;
      case 2400:
	speed = CBR_2400;
	break;
      case 4800:
	speed = CBR_4800;
	break;
      case 9600:
	speed = CBR_9600;
	break;
      case 19200:
	speed = CBR_19200;
	break;
      case 115200:
	speed = CBR_115200;
	break;
      default:
	serial_port___initial->error_number = SERIAL_PORT_UNKNOWN_BAUD_RATE;
	return serial_port___initial;
    }
    parameters = &serial_port->parameters;
    parameters->DCBlength = sizeof(*parameters);

    if (!GetCommState(serial, parameters)) {
	char last_error[1024];
	FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
	  FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(),
	  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), last_error,
	  1024, NULL);
	(void)printf("Error3=%s\n", last_error);
	(void)fflush(stdout);
	serial_port___initial->error_number = SERIAL_PORT_BAUD_RATE_ERROR;
	return serial_port___initial;
    }

    parameters->BaudRate = speed;
    parameters->ByteSize = 8;
    parameters->StopBits = ONESTOPBIT;
    parameters->Parity = NOPARITY;

    if (!SetCommState(serial, parameters)) {
	char last_error[1024];
	FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
	  FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(),
	  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), last_error,
	  1024, NULL);
	(void)printf("Error4=%s\n", last_error);
	(void)fflush(stdout);
	serial_port___initial->error_number = SERIAL_PORT_BAUD_RATE_ERROR;
	return serial_port___initial;
    }

    /* Deal with time outs: */
    time_outs = &serial_port->time_outs;
    time_outs->ReadIntervalTimeout = 0;
    time_outs->ReadTotalTimeoutConstant = 0;
    time_outs->ReadTotalTimeoutMultiplier = 0;
    time_outs->WriteTotalTimeoutConstant = 0;
    time_outs->WriteTotalTimeoutMultiplier = 0;
    if (!SetCommTimeouts(serial, time_outs)) {
	char last_error[1024];
	FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
	  FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(),
	  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), last_error,
	  1024, NULL);
	(void)printf("ock=%s\n", last_error);
	(void)fflush(stdout);
	serial_port___initial->error_number = SERIAL_PORT_ATTRIBUTES_SET_ERROR;
	return serial_port___initial;
    }

    /* Do final setup of {serial_port}: */
    serial_port->serial = serial;
    serial_port->error_number = SERIAL_PORT_OK;
    serial_port->out_count = 0;
    serial_port->blocked = 1;

    serial_port__block(serial_port, 1);

    /* (void)fprintf(stderr, "<=serial_port__open(0x%x, %d)=>0x%x\n",
      path, baud_rate, serial_port); */

    return serial_port;

}
#ifdef UNUSED
void
serial_port__foo(void)
{
    HANDLE hSerial;
    DCB dcbSerialParams = {0};
    COMMTIMEOUTS timeouts = {0};
    unsigned char buffer[1024] = {0};
    DWORD dwBytesRead = 0;
    DWORD dwBytesWritten = 0;
    unsigned char lastError[1024];

    hSerial = CreateFile("COM1",
			 GENERIC_READ | GENERIC_WRITE,
			 0,
			 0,
			 OPEN_EXISTING,
			 FILE_ATTRIBUTE_NORMAL,
			 0);
    if (hSerial == INVALID_HANDLE_VALUE) {
	if (GetLastError() == ERROR_FILE_NOT_FOUND) {
	    assert(0);
	}
	assert(0);
    }

    dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
    if (!GetCommState(hSerial, &dcbSerialParams)) {
	assert(0);
    }

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

    if (!SetCommState(hSerial, &dcbSerialParams)) {
	assert(0);
    }

    timeouts.ReadIntervalTimeout = 50;
    timeouts.ReadTotalTimeoutConstant = 50;
    timeouts.ReadTotalTimeoutMultiplier = 10;
    timeouts.WriteTotalTimeoutConstant = 50;
    timeouts.WriteTotalTimeoutMultiplier = 10;

    if (!SetCommTimeouts(hSerial, &timeouts)) {
	assert(0);
    }

    if (!ReadFile(hSerial, buffer, 1023, &dwBytesRead, NULL)) {
	assert(0);
    }

    if (!WriteFile(hSerial, buffer, 1023, &dwBytesWritten, NULL)) {
	assert(0);
    }

    CloseHandle(hSerial);


    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
		  NULL,
		  GetLastError(),
		  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		  lastError,
		  1024,
		  NULL);


    if (EscapeCommFunction(hSerial, SETDTR)) {
	assert (0);
    }
}
#endif /* UNUSED */

#endif /* WINDOWS */
