/****************************************************************************
 * This file is part of the project AqHome.
 * AqHome (c) by 2025 Martin Preuss, all rights reserved.
 *
 * The license for this file can be found in the file COPYING which you
 * should have received along with this file.
 ****************************************************************************/

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include "./tty_object.h"
#include <aqhome/events2/fdobject.h>

#include <gwenhywfar/debug.h>
#include <gwenhywfar/text.h>

#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <time.h>



#define AQH_TTYOBJECT_BAUDRATE            B19200
#define AQH_TTYOBJECT_FLUSH_LIMIT            500
#define AQH_TTYOBJECT_FLUSH_QUIET_TIME_MS    200


/* ------------------------------------------------------------------------------------------------
 * forward declarations
 * ------------------------------------------------------------------------------------------------
 */

static int _cbStartMsg(AQH_OBJECT *o);
static void _cbEndMsg(AQH_OBJECT *o);
static int _cbFlush(AQH_OBJECT *o);
static int _getAttn(int fd);
static int _setAttn(int fd, int val);
static int _fdSetBlocking(int sk, int fl);
//static int _msleep(long int msec);



/* ------------------------------------------------------------------------------------------------
 * implementations
 * ------------------------------------------------------------------------------------------------
 */

AQH_OBJECT *AQH_TtyObject_new(AQH_EVENT_LOOP *eventLoop, int fd, int fdMode)
{
  AQH_OBJECT *o;

  o=AQH_FdObject_new(eventLoop, fd, fdMode);
  AQH_FdObject_SetStartMsgFn(o, _cbStartMsg);
  AQH_FdObject_SetEndMsgFn(o, _cbEndMsg);
  AQH_FdObject_SetFlushFn(o, _cbFlush);

  _cbFlush(o);

  return o;
}



int _cbStartMsg(AQH_OBJECT *o)
{
  if (o) {
    if (AQH_Object_GetFlags(o) & AQH_TTYOBJECT_FLAGS_NOATTN)
      return 0;
    else {
      int fd;
      int rv;
  
      fd=AQH_FdObject_GetFd(o);
      if (fd==-1)
        return GWEN_ERROR_IO;
      rv=_getAttn(fd);
      if (rv<0) {
        DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv);
        return rv;
      }
      else if (rv==0) {
        DBG_ERROR(NULL, "Line busy");
        return GWEN_ERROR_TRY_AGAIN;     /* line busy */
      }
      else {
        _setAttn(fd, 0);                 /* set ATTN low */
        return 0;
      }
    }
  }
  else
    return GWEN_ERROR_INVALID;
}



void _cbEndMsg(AQH_OBJECT *o)
{
  if (o) {
    if (!(AQH_Object_GetFlags(o) & AQH_TTYOBJECT_FLAGS_NOATTN)) {
      int fd;

      fd=AQH_FdObject_GetFd(o);
      if (fd>=0) {
        _setAttn(fd, 1);                 /* set ATTN high */
      }
    }
  }
}



int _cbFlush(AQH_OBJECT *o)
{
  int fd;
  int rv;
  int i;

  DBG_ERROR(AQH_LOGDOMAIN, "Flush");
  fd=AQH_FdObject_GetFd(o);
  if (fd>=0) {
    usleep(1000);
    rv=ioctl(fd, TCFLSH, 2); // flush receive
    if (rv!=0) {
      if (errno!=EINTR && errno!=EWOULDBLOCK && errno!=EAGAIN) {
	DBG_ERROR(AQH_LOGDOMAIN, "Error on ioctl: %s (%d)", strerror(errno), errno);
	return GWEN_ERROR_IO;
      }
    }

    for (i=0;i<AQH_TTYOBJECT_FLUSH_LIMIT;i++) {
      uint8_t buffer[32];

      /* wait for timeout */
      rv=AQH_FdObject_WaitForActivity(o, AQH_TTYOBJECT_FLUSH_QUIET_TIME_MS);
      if (rv==0) {
	DBG_ERROR(AQH_LOGDOMAIN, "No data for some time, assuming flushed");
	return 0;
      }

      /* data available, read */
      rv=read(fd, buffer, sizeof(buffer));
      if (rv==0) {
	DBG_ERROR(AQH_LOGDOMAIN, "EOF met [%d]", i);
	return GWEN_ERROR_EOF;
      }
      else if (rv<0) {
	if (errno!=EINTR && errno!=EWOULDBLOCK && errno!=EAGAIN) {
	  DBG_ERROR(AQH_LOGDOMAIN, "Error on read: %s (%d) [%d]", strerror(errno), errno, i);
	  return GWEN_ERROR_IO;
	}
	else
	  break;
      }
      else {
	DBG_ERROR(AQH_LOGDOMAIN, "Flushed %d bytes [%d]", rv, i);
	GWEN_Text_LogString((const char*)buffer, rv, AQH_LOGDOMAIN, GWEN_LoggerLevel_Error);
      }
    }
    if (i>=AQH_TTYOBJECT_FLUSH_LIMIT) {
      DBG_ERROR(AQH_LOGDOMAIN, "Still data after reading for some time, assuming bad connection");
      AQH_FdObject_Abort(o);
      return GWEN_ERROR_IO;
    }
  }

  return 0;
}



int _getAttn(int fd)
{
  int rv;
  int status;

  rv=ioctl(fd, TIOCMGET, &status);
  if (rv<0) {
    DBG_ERROR(AQH_LOGDOMAIN, "Error on ioctl: %s (%d)", strerror(errno), errno);
    return GWEN_ERROR_IO;
  }
  return (status & TIOCM_CTS)?0:1;
}



int _setAttn(int fd, int val)
{
  int rv;
  int status;

  rv=ioctl(fd, TIOCMGET, &status);
  if (rv<0) {
    DBG_ERROR(AQH_LOGDOMAIN, "Error on ioctl: %s (%d)", strerror(errno), errno);
    return GWEN_ERROR_IO;
  }

  if (val)
    status &= ~ (TIOCM_DTR | TIOCM_RTS);    /* attn high, clear the DTR pin (cave: signals inverted!) */
  else
    status |= TIOCM_DTR | TIOCM_RTS;        /* attn low, set the DTR pin (cave: signals inverted!) */

  rv=ioctl(fd, TIOCMSET, &status);
  if (rv<0) {
    DBG_ERROR(AQH_LOGDOMAIN, "Error on ioctl: %s (%d)", strerror(errno), errno);
    return GWEN_ERROR_IO;
  }
  DBG_ERROR(NULL, "Set ATTN to %d", val);
  return 0;
}



int AQH_TtyObject_OpenAndInitDevice(const char *device, struct termios *initialTermiosState)
{
  int fd;
  struct termios options;
  int rv;

  fd=open(device, O_NOCTTY | O_NDELAY | O_RDWR);
  if (fd<0) {
    DBG_ERROR(AQH_LOGDOMAIN, "Error on open(%s): %s (%d)", device, strerror(errno), errno);
    return GWEN_ERROR_IO;
  }
  DBG_INFO(AQH_LOGDOMAIN, "Device %s open (socket %d)", device, fd);

  _fdSetBlocking(fd, 0);

  rv=tcgetattr(fd, &options);
  if (initialTermiosState)
    *initialTermiosState=options;

#if 0
  options.c_cflag=CLOCAL | CREAD | CS8;
  options.c_iflag=IGNPAR | IGNBRK;
  options.c_oflag=0;
  options.c_lflag&= ~(ICANON | ECHO | ECHOE | ISIG);
  options.c_cc[VTIME]=0;  /* read timeout in deciseconds */
  options.c_cc[VMIN]=0;   /* no minimum number of receive bytes */
#endif
  cfmakeraw(&options);

  rv=cfsetispeed(&options, AQH_TTYOBJECT_BAUDRATE);
  if (rv<0) {
    DBG_ERROR(AQH_LOGDOMAIN, "Error on cfsetispeed(%s): %s (%d)", device, strerror(errno), errno);
    return GWEN_ERROR_IO;
  }
  rv=cfsetospeed(&options, AQH_TTYOBJECT_BAUDRATE);
  if (rv<0) {
    DBG_ERROR(AQH_LOGDOMAIN, "Error on cfsetospeed(%s): %s (%d)", device, strerror(errno), errno);
    return GWEN_ERROR_IO;
  }

  rv=tcflush(fd, TCIOFLUSH);
  if (rv<0) {
    DBG_ERROR(AQH_LOGDOMAIN, "Error on tcflush(%s): %s (%d)", device, strerror(errno), errno);
    return GWEN_ERROR_IO;
  }

  rv=tcsetattr(fd, TCSANOW, &options);
  if (rv<0) {
    DBG_ERROR(AQH_LOGDOMAIN, "Error on tcsetattr(%s): %s (%d)", device, strerror(errno), errno);
    return GWEN_ERROR_IO;
  }

  rv=_setAttn(fd, 1);
  if (rv<0) {
    DBG_ERROR(AQH_LOGDOMAIN, "Error on setAttn(%s): %s (%d)", device, strerror(errno), errno);
    return GWEN_ERROR_IO;
  }

  return fd;
}



int _fdSetBlocking(int fd, int fl)
{
  int prevFlags;
  int newFlags;

  /* get current socket flags */
  prevFlags=fcntl(fd, F_GETFL);
  if (prevFlags==-1) {
    DBG_INFO(AQH_LOGDOMAIN, "fcntl(): %s", strerror(errno));
    return GWEN_ERROR_IO;
  }

  /* set nonblocking/blocking */
  if (fl)
    newFlags=prevFlags&(~O_NONBLOCK);
  else
    newFlags=prevFlags|O_NONBLOCK;

  if (-1==fcntl(fd, F_SETFL, newFlags)) {
    DBG_INFO(AQH_LOGDOMAIN, "fcntl(): %s", strerror(errno));
    return GWEN_ERROR_IO;
  }
  prevFlags=fcntl(fd, F_GETFL);
  if (prevFlags!=newFlags) {
    DBG_ERROR(AQH_LOGDOMAIN, "fcntl() did not set flags correctly (%08x!=%08x)", prevFlags, newFlags);
    return GWEN_ERROR_IO;
  }

  return 0;
}




#if 0
int _msleep(long int msec)
{
  struct timespec ts;
  int rv;

  if (msec<0) {
    errno=EINVAL;
    return -1;
  }

  ts.tv_sec=msec/1000;
  ts.tv_nsec=(msec%1000)*1000000;

  do {
    rv=nanosleep(&ts, &ts);
  } while (rv && errno==EINTR);

  return rv;
}
#endif


