/*
 * Copyright (c) 2001 Tommy Bohlin <tommy@gatespace.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
/* unix.c
 */

#include <irda.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>

/**********************************************************************
 * Constants
 **********************************************************************/

#ifdef IRDA_KERNEL_DRIVER

static const char id_kernel_framedevice[]="kernel frame device";
static const char id_kfdev_inbuffer[]="kernel frame device in buffer";
static const char id_kfdev_outbuffer[]="kernel frame device out buffer";

static const char* defaultKernelPort = "/dev/irframe0";

#endif /* IRDA_KERNEL_DRIVER */

/**********************************************************************
 * Data structures
 **********************************************************************/

typedef struct Memory {
  struct Memory* next;
  const char* id;
  void* ptr;
  int size;
} Memory;

typedef struct Timer {
  struct Timer* next;
  struct timeval expire;
  void (*func)(void*);
  void* handle;
} Timer;

typedef struct Source {
  struct Source* next;
  int fd;
  void (*func)(void* handle);
  void* handle;
} Source;

typedef struct UNIXSerialPort {
  SerialPort serial;
  struct UNIXSerialPort* next;
  int speedMask;
  int fd;
  u_char inBuf[1024];
  int inHead;
  int inFree;
  int stopped;
  u_char outBuf[1024];
  int outFree;
} UNIXSerialPort;

#ifdef IRDA_KERNEL_DRIVER

typedef struct KernelFrameDevice {
  FrameDevice frame;
  char *devname;
  int fdesc;
  u_char* inBuf;
  u_char* outBuf;
  int maxSize;
  int maxOutSize;
  int maxspeed;
} KernelFrameDevice;

#endif /* IRDA_KERNEL_DRIVER */

/**********************************************************************
 * State
 **********************************************************************/

int                    evtDebug;

static bool            sysLog=FALSE;
static FILE*           logFile=NULL;

static Source*         sources;
static UNIXSerialPort* ports;
static Timer*          timers;
static Memory*         allocs;

/**********************************************************************
 * Support functions
 **********************************************************************/

static void addTime(struct timeval* t, int ms)
{
  t->tv_sec+=ms/1000;
  t->tv_usec+=1000*(ms%1000);
  if(t->tv_usec<0) {
    t->tv_sec-=1;
    t->tv_usec+=1000000;
  } else if(t->tv_usec>=1000000) {
    t->tv_sec+=1;
    t->tv_usec-=1000000;
  }
}

static int cmpTime(const struct timeval* t1, const struct timeval* t2)
{
  long t=t1->tv_sec-t2->tv_sec;
  return t ? t : t1->tv_usec-t2->tv_usec;
}

static Timer* unlinkTimer(void (*func)(void* handle), void* handle)
{
  Timer** th=&timers;
  Timer* t;

  while((t=*th)) {
    if(t->func==func && t->handle==handle) {
      *th=t->next;
      return t;
    }
    th=&t->next;
  }
  return 0;
}

static void linkTimer(Timer* e) {
  Timer** th=&timers;
  Timer* t;

  while((t=*th)) {
    if(cmpTime(&e->expire,&t->expire)<0) break;
    th=&t->next;
  }
  e->next=*th;
  *th=e;
}

void evtRemoveSource(int fd)
{
  Source** sh=&sources;
  Source* s;

  while((s=*sh)) {
    if(s->fd==fd) {
      *sh=s->next;
      free(s);
      break;
    }
    sh=&s->next;
  }
}

void evtAddSource(int fd, void (*func)(void* handle), void* handle)
{
  Source* s;

  evtRemoveSource(fd);

  s=malloc(sizeof(Source));
  s->fd=fd;
  s->func=func;
  s->handle=handle;
  s->next=sources;
  sources=s;
}

/**********************************************************************
 * Serial I/O
 **********************************************************************/

static void serialSetSpeed(SerialPort* sp, int speed) {
  UNIXSerialPort* usp=(UNIXSerialPort*)sp;
  struct termios t;
  int speedcode;

  if(tcgetattr(usp->fd,&t)) {
    birda_log("tcgetattr failed\n");
    return;
  }
  switch(speed) {
  case 115200: speedcode=B115200; break;
  case 57600:  speedcode=B57600;  break;
  case 38400:  speedcode=B38400;  break;
  case 19200:  speedcode=B19200;  break;
  case 9600:   speedcode=B9600;   break;
  case 2400:   speedcode=B2400;   break;
  default:
    birda_log("unsupported speed\n");    
    return;
  }
  cfsetispeed(&t,speedcode);
  cfsetospeed(&t,speedcode);
  if(tcsetattr(usp->fd,TCSADRAIN,&t)) {
    birda_log("tcsetattr failed\n");
  }
}

static void serialSetLine(SerialPort* sp, int line) {
  UNIXSerialPort* usp=(UNIXSerialPort*)sp;
  int flags;

  if(ioctl(usp->fd,TIOCMGET,&flags)==-1) {
    birda_log("ioctl failed %d\n", __LINE__);
  }

  flags&=~(TIOCM_DTR|TIOCM_RTS);
  if(line&LINE_DTR) flags|=TIOCM_DTR;
  if(line&LINE_RTS) flags|=TIOCM_RTS;

  if(ioctl(usp->fd,TIOCMSET,&flags)==-1) {
    birda_log("ioctl failed %d\n", __LINE__);
  }
}

static int serialGetSpeedMask(SerialPort* sp)
{
  UNIXSerialPort* usp=(UNIXSerialPort*)sp;

  return usp->speedMask;
}

static void serialInput(void* sp)
{
  UNIXSerialPort* usp=(UNIXSerialPort*)sp;
  int empty;
  
  int k=usp->inHead<=usp->inFree ?
    sizeof usp->inBuf-usp->inFree : usp->inHead-usp->inFree-1;
  int n=read(usp->fd,usp->inBuf+usp->inFree,k);

  if(usp->serial.debug&SP_DEBUG_INPUT) birda_log("serial port read %d bytes\n",n);
  if(n<=0) return;
  empty=usp->inHead==usp->inFree;
  usp->inFree=(usp->inFree+n)%sizeof usp->inBuf;
  if(((usp->inFree+1)%sizeof usp->inBuf)==usp->inHead) {
    usp->stopped=1;
    evtRemoveSource(usp->fd);
  }
  if(empty && usp->serial.status)
    usp->serial.status(&usp->serial, SERIAL_INPUT_AVAILABLE);
}

static int serialGetChar(SerialPort* sp)
{
  UNIXSerialPort* usp=(UNIXSerialPort*)sp;
  int c;
  int nchars=usp->inFree-usp->inHead;
  if(nchars<0) nchars+=sizeof usp->inBuf;

  if(nchars==0) {
    usp->inHead=usp->inFree=0;
    return SERIAL_INBUF_EMPTY;
  }
  if(usp->stopped && nchars<sizeof usp->inBuf/4) {
    usp->stopped=0;
    evtAddSource(usp->fd,serialInput,usp);
  }
  c=usp->inBuf[usp->inHead];
  usp->inHead=(usp->inHead+1)%sizeof usp->inBuf;
  return c;
}

static void flush(UNIXSerialPort* usp)
{
  int n=usp->outFree;
  int k=0;
  while(k<n) {
    int i=write(usp->fd,usp->outBuf+k,n-k);
    if(i>0) k+=i;
    else if(i==0 || (errno!=EAGAIN && errno!=EINTR)) break;
  }
  usp->outFree=0;
}

static void serialOutput(void* sp)
{
  UNIXSerialPort* usp=(UNIXSerialPort*)sp;
  if(evtDebug&EVT_DEBUG_TIMERS) birda_log("serialOutput\n");
  if(usp->outFree>0) flush(usp);
}

static void serialPutChar(SerialPort* sp, int c)
{
  UNIXSerialPort* usp=(UNIXSerialPort*)sp;

  usp->outBuf[usp->outFree++]=c;
  if(usp->outFree==sizeof usp->outBuf) flush(usp);
  else evtSetTimer(0,serialOutput,usp);
}

static void serialClose(SerialPort* sp)
{
  UNIXSerialPort* usp=(UNIXSerialPort*)sp;
  UNIXSerialPort** uh=&ports;
  UNIXSerialPort* u;

  while((u=*uh)) {
    if(u==usp) {
      *uh=u->next;
      break;
    } else {
      uh=&u->next;
    }
  }
  if(!u) {
    birda_log("ERROR: closing unallocated UNIX serial port\n");
    return;
  }

  evtCancelTimer(serialOutput,usp);
  evtRemoveSource(usp->fd);
  free(usp);
}

/**********************************************************************
 * External interface
 **********************************************************************/

bool setFileLog(const char* file)
{
  FILE* f=fopen(file,"a");

  if(!f) return FALSE;
  logFile=f;
  return TRUE;
}

void setSysLog(void)
{
  sysLog=TRUE;
}

void birda_log(const char* fmt, ...)
{
  va_list ap;

  va_start(ap,fmt);
  if(sysLog) {
    vsyslog(LOG_INFO,fmt,ap);    
  } else {
    if(!logFile) logFile=stderr;
    vfprintf(logFile,fmt,ap);
    fflush(logFile);
  }
  va_end(ap);
}

void* allocMem(const char* id, int size)
{
  void* ptr=malloc(size);
  Memory* m=malloc(sizeof(Memory));

  if(!ptr || !m) {
    birda_log("PANIC: malloc failed\n");
    return 0;
  }

  m->ptr=ptr;
  m->id=id;
  m->size=size;
  m->next=allocs;
  allocs=m;

  return ptr;
}

void* growMem(void* ptr, int size)
{
  Memory* m=allocs;
  void* nptr;

  while(m && m->ptr!=ptr) m=m->next;
  if(!m) {
    birda_log("ERROR: growing unallocated memory\n");
    return 0;
  }

  nptr=realloc(ptr,size);
  if(!nptr) {
    birda_log("PANIC: realloc failed\n");
    return 0;
  }

  m->ptr=nptr;
  m->size=size;

  return nptr;
}

void freeMem(void *ptr)
{
  Memory** mh=&allocs;
  Memory* m;

  while((m=*mh)) {
    if(m->ptr==ptr) {
      *mh=m->next;
      free(m);
      free(ptr);
      return;
    } else {
      mh=&m->next;
    }
  }
  birda_log("ERROR: freeing unallocated memory\n");
}

void showResources(void)
{
  int n,b;
  Source* s;
  UNIXSerialPort* u;
  Timer* t;
  Memory* m;

  birda_log("-----------------\n");

  for(n=0,u=ports;u;u=u->next) n++;
  birda_log("%d unixSerialPorts\n",n);

  for(n=0,s=sources;s;s=s->next) if(s->func!=serialInput) n++;
  birda_log("%d other sources\n",n);

  for(n=0,t=timers;t;t=t->next) n++;
  birda_log("%d timers\n",n);

  birda_log("Memory:\n");
  for(n=0,b=0,m=allocs;m;m=m->next) {
    birda_log("  addr=%p size=%5d id=%s\n",m->ptr,m->size,m->id);
    n++;
    b+=m->size;
  }
  birda_log("  total: %d blocks, %d bytes\n",n,b);
  birda_log("-----------------\n");
}

unsigned getRandom(unsigned max)
{
  static bool inited=FALSE;
  unsigned mask;

  if(max==0) return 0;

  if(!inited) {
    struct timeval t;
    gettimeofday(&t,0);
    srandom(t.tv_sec^t.tv_usec);
    inited=TRUE;
  }

  for(mask=1;mask<max;) mask=(mask<<1)|1;
  for(;;) {
    int val=random()&mask;
    if(mask&0x80000000) val^=random()<<1;
    if(val<=max) return val;
  }
}

SerialPort* createSerialPort(const char* dev, int maxspeed)
{
  int fd;
  struct termios t;
  UNIXSerialPort* usp;
  int speedMask=
    SPEED_2400|SPEED_9600|SPEED_19200|SPEED_38400|SPEED_57600|SPEED_115200;

  if(maxspeed>0) {
    if(maxspeed<115200) speedMask&=~SPEED_115200;
    if(maxspeed< 57600) speedMask&=~SPEED_57600;
    if(maxspeed< 38400) speedMask&=~SPEED_38400;
    if(maxspeed< 19200) speedMask&=~SPEED_19200;
    if(maxspeed<  9600) return 0;
  }

  if((fd=open(dev,O_RDWR|O_NOCTTY|O_NONBLOCK))<0) return 0;

  t.c_iflag=0;
  t.c_oflag=0;
  t.c_cflag=CREAD|CS8|CLOCAL;
  t.c_lflag=0;
  t.c_cc[VMIN]=1;
  t.c_cc[VTIME]=0;

  cfsetispeed(&t,B9600);
  cfsetospeed(&t,B9600);
  if(tcsetattr(fd,TCSANOW,&t) || fcntl(fd,F_SETFL,0)==-1) {
    close(fd);
    return 0;
  }

  usp=malloc(sizeof(UNIXSerialPort));
  usp->serial.close=serialClose;
  usp->serial.setSpeed=serialSetSpeed;
  usp->serial.setLine=serialSetLine;
  usp->serial.getSpeedMask=serialGetSpeedMask;
  usp->serial.getChar=serialGetChar;
  usp->serial.putChar=serialPutChar;
  usp->serial.debug=0;
  usp->serial.handle=0;
  usp->serial.status=0;
  usp->speedMask=speedMask;
  usp->fd=fd;

  usp->inHead=usp->inFree=0;
  usp->outFree=0;
  evtAddSource(usp->fd,serialInput,usp);

  usp->next=ports;
  ports=usp;

  return &usp->serial;
}

void evtCancelTimer(void (*func)(void*), void* handle)
{
  Timer* t=unlinkTimer(func,handle);
  if(t) free(t);
}

void evtSetTimer(int delay, void (*func)(void*), void* handle)
{
  Timer* t=unlinkTimer(func,handle);
  if(!t) {
    t=malloc(sizeof(Timer));
    t->func=func;
    t->handle=handle;
  }
  gettimeofday(&t->expire,0);
  addTime(&t->expire,delay);
  linkTimer(t);
}

void evtLoop(void)
{
  while(timers || sources) {
    int fdmax;
    fd_set fds;
    struct timeval t;
    Source* s;

    if(timers) {
      gettimeofday(&t,0);
      t.tv_sec=timers->expire.tv_sec-t.tv_sec;
      t.tv_usec=timers->expire.tv_usec-t.tv_usec;
      if(t.tv_usec<0) {
	t.tv_sec-=1;
	t.tv_usec+=1000000;
      }
      if(t.tv_sec<0) {
	Timer* ti=timers;
	timers=ti->next;
	if(evtDebug&EVT_DEBUG_TIMERS)
	  birda_log("timer called %p %p\n",ti->func,ti->handle);
	ti->func(ti->handle);
	if(evtDebug&EVT_DEBUG_TIMERS) birda_log("timer completed\n");
	free(ti);
	continue;
      }
    }

    fdmax=0;
    FD_ZERO(&fds);
    for(s=sources;s;s=s->next) {
      FD_SET(s->fd,&fds);
      if(fdmax<=s->fd) fdmax=s->fd+1;
    }

    if(evtDebug&EVT_DEBUG_SELECT)
      birda_log("select %s timers\n",timers ? "with" : "without");

    if(select(fdmax,&fds,0,0,timers ? &t : 0)==-1 && errno!=EINTR)
      perror("select");

    if(evtDebug&EVT_DEBUG_SELECT)
      birda_log("wakeup\n");

    for(s=sources;s && !FD_ISSET(s->fd,&fds);s=s->next);
    if(s) s->func(s->handle);
  }
}

/**********************************************************************
 * Kernel frame device support
 **********************************************************************/

#ifdef IRDA_KERNEL_DRIVER

static void kfd_close(FrameDevice* fd)
{
  KernelFrameDevice* kfd = (KernelFrameDevice*)fd;
  evtRemoveSource(kfd->fdesc);
  close(kfd->fdesc);
  freeMem(kfd);
}

static int kfd_setParams(FrameDevice* fd, int baud, int ebofs, int maxSize)
{
  KernelFrameDevice* kfd = (KernelFrameDevice*)fd;
  struct irda_params params;
  params.speed = baud;
  params.ebofs = ebofs;
  params.maxsize = maxSize;

  if (ioctl(kfd->fdesc, IRDA_SET_PARAMS, &params) == -1) {
    birda_log("ioctl failed %d\n", __LINE__);
  }

  if(kfd->maxSize != maxSize) {
    kfd->maxSize = maxSize;
    if (kfd->inBuf)
      freeMem(kfd->inBuf);
    kfd->inBuf = kfd->maxSize ? allocMem(id_kfdev_inbuffer, maxSize+2) : 0;
  }    

  return 0;
}

static void kfd_resetParams(FrameDevice* fd)
{
  KernelFrameDevice* kfd = (KernelFrameDevice*)fd;

  if (ioctl(kfd->fdesc, IRDA_RESET_PARAMS) == -1) {
    birda_log("ioctl failed %d\n", __LINE__);
  }
}

static void kfd_sendFrame(FrameDevice* fd, const void* buf0, int len)
{
  KernelFrameDevice* kfd = (KernelFrameDevice*)fd;
#if 0
  len = createSIRFrame(kfd->ebofs, buf0, len, kfd->outBuf, kfd->maxOutSize);
  write(kfd->fdesc, kfd->outBuf, len);
#else
  write(kfd->fdesc, buf0, len);
#endif
}

static int kfd_getSpeedMask(FrameDevice* fd)
{
  KernelFrameDevice* kfd = (KernelFrameDevice*)fd;
  int maxspeed = kfd->maxspeed;
  int speedmask;

  if (ioctl(kfd->fdesc, IRDA_GET_SPEEDMASK, &speedmask) == -1) {
    birda_log("ioctl failed %d\n", __LINE__);
  }
  
  if(maxspeed>0) {
    if(maxspeed<16000000) speedmask&=~SPEED_16000000;
    if(maxspeed<4000000) speedmask&=~SPEED_4000000;
    if(maxspeed<1152000) speedmask&=~SPEED_1152000;
    if(maxspeed<576000) speedmask&=~SPEED_576000;
    if(maxspeed<115200) speedmask&=~SPEED_115200;
    if(maxspeed< 57600) speedmask&=~SPEED_57600;
    if(maxspeed< 38400) speedmask&=~SPEED_38400;
    if(maxspeed< 19200) speedmask&=~SPEED_19200;
    if(maxspeed<  9600) speedmask&=~SPEED_9600;
  }
  return speedmask;
}

static int kfd_getMinTurnaroundMask(FrameDevice* fd)
{
  KernelFrameDevice* kfd = (KernelFrameDevice*)fd;
  int tamask;

  if (ioctl(kfd->fdesc, IRDA_GET_TURNAROUNDMASK, &tamask) == -1) {
    birda_log("ioctl failed %d\n", __LINE__);
  }

  return tamask;
}

static void kfd_readFrame(void *handle)
{
  KernelFrameDevice* kfd = (KernelFrameDevice*)handle;
  ssize_t len = read(kfd->fdesc, kfd->inBuf, (ssize_t)kfd->maxSize);
  if (len < 0) {
    perror("kfd_read");
  }
  if (len > 0) { /* Must be? */
    if (kfd->frame.frame)
      kfd->frame.frame(&kfd->frame, kfd->inBuf, len); /* dev driver skip ck */
  }
}

FrameDevice* createKernelFrameDevice(char *dev, int maxspeed)
{
  KernelFrameDevice *kfd = 
    allocMem(id_kernel_framedevice, sizeof(KernelFrameDevice));
  
  if (!dev) {
    dev = (char *)defaultKernelPort;
  }
  kfd->devname = dev;
  kfd->maxSize = 4096;
  kfd->maxspeed = maxspeed;

  if ((kfd->fdesc = open(kfd->devname, O_RDWR|O_NONBLOCK)) < 0) {
    freeMem(kfd);
    birda_log("Cannot open port %s\n", dev);
    exit(-16);
  }
  
  kfd->frame.close = kfd_close;
  kfd->frame.setParams = kfd_setParams;
  kfd->frame.resetParams = kfd_resetParams;
  kfd->frame.sendFrame = kfd_sendFrame;
  kfd->frame.getSpeedMask = kfd_getSpeedMask;
  kfd->frame.getMinTurnaroundMask = kfd_getMinTurnaroundMask;
  kfd->frame.debug = 0;
  kfd->frame.handle = 0;
  kfd->frame.frame = 0;

  kfd->maxOutSize = 8192;
  kfd->inBuf = allocMem(id_kfdev_inbuffer, kfd->maxSize+2);
  kfd->outBuf = allocMem(id_kfdev_outbuffer, kfd->maxOutSize);

  evtAddSource(kfd->fdesc, kfd_readFrame, kfd);

  return &kfd->frame;
}

#endif /* IRDA_KERNEL_DRIVER */
