/*
  serial.c

  Copyright 20-Nov-2002 MBARI
  Written: 20-Nov-2002 Mark Sibenac
  Last mod: 20-Nov-2002 sib - creation

  Interface library to Windows serial ports
*/

#include <stdio.h>
#include <fcntl.h>
#include <sys/termio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netdb.h>
#include <ctype.h>

#include "serial/serial.h"
#include "error/error.h"
#include "util/util.h"

static int flushSend (int port);

typedef struct _Port_t {
  char isSocket;
  int  fd;
  int  fd_log;
  struct _Port_t *prev;
  struct _Port_t *next;
} Port_t;

Port_t *ports=0;

int connectAtPort(const char *prot, const char *machine, int port)
{
  int sd;
  struct hostent *hp;
  struct sockaddr_in server;
  
  memset((char *)&server, 0, sizeof(struct sockaddr_in));
  
  server.sin_family = AF_INET;
  server.sin_port = htons((u_short)port);

  if (isdigit(machine[0])) {
    server.sin_addr.s_addr = inet_addr(machine);
  } else {
    if ((hp = gethostbyname((char *)machine)) == NULL) {
      return -1;
    }
    memcpy((char *)&server.sin_addr, (char *)hp->h_addr,  hp->h_length);
  }
  
  if ((sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
    return -1;
  }
  
  /*
    if ((setsockopt(sd, SOL_SOCKET, SO_REUSEADDR,
    (char *)&item, sizeof(int))) < 0) {
    close(sd);
    return -1;
    }


    if ((setsockopt(socket, IPPROTO_TCP, TCP_NODELAY,
    (char *)&value, sizeof(int))) < 0) {
    close(*readSd);
    if (*readSd != *writeSd) {
    close(*writeSd);
    }
    *readSd = NO_FD;
    *writeSd = NO_FD;
    return -1;
    }
  */
    
  if (connect(sd, (struct sockaddr *)&server, sizeof(server)) < 0) {
    return -1;
  }

  DEBUG(1,"sd is %d\n", sd);
  return sd;
}

int sendStr (int port, char *str)
{
  int ret, len=strlen(str);

  DEBUG (4, "sendStr: Sending %d chars to serial port %d:%s\n", len, port, 
	 str);

  if ((ret = write (port, str, len)) < len)
    {
      ERROR ("err: sendStr wrote %d of %d bytes: %s\n", ret, len, 
	     strerror(errno));
      return ret;
    }

  if ((ret = flushSend (port)) < 0)
    {
      ERROR ("err: sendStr tcdrain failed: %s\n", strerror (errno));
      return ret;
    }

  {
    Port_t *pPort = ports;
    time_t t = time(0);
    char str2[strlen(str)+15];

    sprintf (str2, "S%ld: %s", (long int)t, str);
						
    while (pPort->fd != port)
      pPort= pPort->next;
		
    if (pPort->fd_log)
      write (pPort->fd_log, str2, strlen(str2));
  }
		
  msleep (100); // sleep for a little to let things digest a bit

  return 0;
}

/* read in a '\n' or '\r' terminated string up to max_len in timeout_msec 
   amount of time */
int recvStr (int port, char *str, int max_len)
{
  int ret, len_left=max_len;
  struct timeval tv;
  fd_set readFds;
  Port_t *pPort = ports;
	
  while (pPort->fd != port)
    pPort= pPort->next;
		
  *str = 0;

  DEBUG (5, "recvStr: Starting while select loop...\n");

  while (len_left > 0)
    {
      tv.tv_sec = 1; // set timeout to 1 second
      tv.tv_usec = 1;
      
      FD_ZERO (&readFds);
      FD_SET (port, &readFds);
      
      if ((ret = select (port+1, &readFds, 0, 0, &tv)) < 0)
	{ 
	  ERROR ("err: recvStr select failed %d: %s\n", ret, strerror(errno));
	  return -1;
	}
      else if (ret == 0) // timeout
	{
	  ERROR ("err: recvStr select failed with timeout\n");
	  return -1;
	}
      else // we got something
	{
	  if ((ret = read (port, str+(max_len-len_left), len_left)) < 0)
	    {
	      ERROR ("err: recvStr read failed: %s\n", strerror(errno));
	      return -1;
	    }
	  str[max_len-len_left+ret] = 0;
	  DEBUG (2, "recvStr: just read in %d chars: %s\n", ret, str);
	  len_left -= ret;
	  if (str[max_len-len_left-1] == 0x0a || 
	      str[max_len-len_left-1] == 0x0d)
	    {
	      DEBUG (2, "recvStr: read in %d chars: %s\n", max_len-len_left, 
		     str);
	      {
		Port_t *pPort = ports;
		time_t t = time(0);
		char str2[strlen(str)+15];
											
		sprintf (str2, "R%ld: %s", (long int)t, str);
											
		while (pPort->fd != port)
		  pPort= pPort->next;
											
		if (pPort->fd_log)
		  write (pPort->fd_log, str2, strlen(str2));
	      }
	      return max_len-len_left;
	    }
	}
    }

  return -1; // ran out of buffer
}

int openPort (char *port_name, int *port, speed_t baud, int databits, 
	      int stop, char *parity)
{
  struct termios termios_p;
  int fd, tcpport;
  char *protocol, *machine;
  Port_t *pPort=ports;
	
  *port = 0;
  if (pPort) {
    while (pPort->next) // find last ports pointer
      pPort = pPort->next;
    pPort->next = malloc (sizeof(Port_t));
    pPort->next->prev = pPort;
    pPort=pPort->next;
    pPort->next = 0;
    pPort->fd = pPort->fd_log = 0;
  } else {
    ports = pPort = malloc (sizeof(Port_t));
    pPort->next = 0;
    pPort->prev = 0;
    pPort->fd = pPort->fd_log = 0;
  }
	
  if (!strncmp(port_name, "tcp", 3)) {
    char *serverString = strdup(port_name);
    pPort->isSocket = 1;
    DEBUG(1,"server string is %s\n", serverString);
    protocol = strtok(serverString, ":\0");
    machine = strtok(NULL, ":\0");
    tcpport = atoi(strtok(NULL,":\0"));
		
    *port = pPort->fd = fd = connectAtPort(protocol, machine, tcpport);
    if (*port < 0) {
      ERROR ("open of protocol %s server %s port %d failed", protocol, machine, *port);
      free(serverString);
      if (pPort->prev)
	pPort->prev->next = 0;
      free(pPort);
      *port = 0;
      return -1;
    }
    free(serverString);
  } else { // if (...tcp...)
    pPort->isSocket = 0;
    if ((fd  = open (port_name, O_RDWR | O_SYNC | O_NOCTTY)) <= 0)
      {
	*port = 0;
	if (pPort->prev)
	  pPort->prev->next = 0;
	free(pPort);
	ERROR ("err: openPort: could not open \"%s\": %s\n", port_name,
	       strerror (errno));
	return fd;
      }

    if (tcgetattr (fd, &termios_p) < 0)
      {
	ERROR ("err: openPort: could not tcgetattr on \"%s\": %s\n", port_name,
	       strerror (errno));
	return -1;
      }
    
    tcflush (fd, TCIOFLUSH);
    
    cfsetispeed(   &termios_p, baud);
    cfsetospeed(   &termios_p, baud);
    
    if (stop ==2)
      termios_p.c_cflag |= CSTOPB;
    else
      termios_p.c_cflag &= ~CSTOPB;
    
    if (databits == 5)
      termios_p.c_cflag = (termios_p.c_cflag & ~CSIZE) | CS5;
    else if (databits == 6)
      termios_p.c_cflag = (termios_p.c_cflag & ~CSIZE) | CS6;
    else if (databits == 7)
      termios_p.c_cflag = (termios_p.c_cflag & ~CSIZE) | CS7;
    else if (databits == 8)
      termios_p.c_cflag = (termios_p.c_cflag & ~CSIZE) | CS8;
    else
      {
	ERROR ("err: openPort: port \"%s\" has bad databits=%d\n", port_name,
	       databits);
	return -1;
      }
    
    if (!stricmp(parity, "NONE"))
      termios_p.c_cflag &= ~PARENB;
    else if (!stricmp(parity, "EVEN"))
      termios_p.c_cflag |= PARENB, termios_p.c_cflag &= ~PARODD; 
    else if (!stricmp(parity, "ODD"))
      termios_p.c_cflag |= PARENB, termios_p.c_cflag |= PARODD; 
    else
      {
	ERROR ("err: openPort: port \"%s\" has bad parity=%s\n", port_name,
	       parity);
	return -1;
      }
    
    termios_p.c_cc[VMIN] = 1;     // block read until at least 0 char is in
    termios_p.c_cc[VTIME] = 0;    // 0 deciseconds block after last char read in
    
                                  // Leave end-of-line unprocessed 
    termios_p.c_iflag &= ~IGNCR;  // Ignore CR on input
    termios_p.c_iflag &= ~ICRNL;  // Don't map CR to NL
    termios_p.c_iflag &= ~INLCR;  // Don't map NL to CR
    
    termios_p.c_lflag &= ~ICANON; // Disable Line Oriented Mode 
    termios_p.c_lflag &= ~ECHO;	// Disable input to output echo
  
    termios_p.c_cflag |= CLOCAL;	// Ignore modem status lines
    termios_p.c_cflag |= CREAD;	// Enable Receiver
  
    /* don't have these in cygwin */
    /*  termios_p.c_cflag &= ~IHFLOW;	// Ignore modem status lines
	termios_p.c_cflag &= ~OHFLOW;	// Ignore modem status lines
    */ 
    
    if (tcsetattr (fd, TCSANOW, &termios_p) < 0)
      {
	ERROR ("err: openPort: tcsetattr failed on \"%s\": %s\n", port_name,
	       strerror(errno));
	return -1;
      }
  } // if (...tcp...)

  return (*port = pPort->fd = fd);
}

int closePort (int port)
{
  return close (port);
}

int flushSend (int port)
{
  int ret = 0;
  Port_t *pPort = ports;
	
  while (pPort->fd != port)
    pPort= pPort->next;

  if (pPort->isSocket)
    return 0;
	
  if ((ret = tcdrain (port)) < 0)
    {
      ERROR ("err: flushRecv failed: %s\n", strerror (errno));
      return ret;
    }

  return ret;
}

int flushRecv (int port)
{
  int ret = 0;
  Port_t *pPort = ports;
	
  while (pPort->fd != port)
    pPort= pPort->next;

  if (pPort->isSocket)
    return 0;
	
  if ((ret = tcflush (port, TCIFLUSH)) < 0)
    {
      ERROR ("err: flushRecv failed: %s\n", strerror (errno));
      return ret;
    }

  return ret;
}

int openLogFile (int port , char *filename)
{
  int ret;
  Port_t *pPort = ports;
  time_t t;
  char filename_time[256];
		
  while (pPort->fd != port)
    pPort= pPort->next;

  t = time(0);
  sprintf (filename_time, "%s_%ld.log", filename, (long int)t);
		
  if ((ret = open (filename_time, O_WRONLY | O_CREAT)) < 0)
    {
      ERROR ("openLogFile: cannot create '%s' for writing:%s\n",
	     filename_time,	strerror(errno));
      pPort->fd = 0;
      return -1;
    }

  printf ("Opened log file '%s' for port fd=%d\n", filename_time, port);
  return pPort->fd_log = ret;
}

int closeLogFile (int port)
{
  Port_t *pPort = ports;
	
  while (pPort->fd != port)
    pPort= pPort->next;

  return (close (pPort->fd_log));
}
