/*
 * 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.
 */
/* options.c
 */

#include <irda.h>

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>

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

#define DONGLE_NONE              0
#define DONGLE_TEKRAM            1
#define DONGLE_JETEYE            2
#define DONGLE_ACTISYS           3
#define DONGLE_ACTISYS_PLUS      4
#define DONGLE_LITELINK          5
#define DONGLE_GIRBIL            6
#define DONGLE_REDLINK           7

static const char* defaultPorts[] = {
  "/dev/tty02",
  "/dev/tty00",
  "/dev/ttyS0",
  0
};

#define VERBOSITY_INFO           1
#define VERBOSITY_IAS            2
#define VERBOSITY_FRAMES_IN      3
#define VERBOSITY_FRAMES_OUT     4
#define VERBOSITY_TIMERS         5
#define VERBOSITY_LOW_LEVEL_IO   6
#define VERBOSITY_EVENT_SELECT   7
#define VERBOSITY_EVENT_TIMERS   8

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

LAP* optLap;
IASServer* optIas;

void (*optLapConnected)(LAP* lap);
void (*optLapDisconnected)(LAP* lap);

static int verbosity;
static int tries;
static int address;

/**********************************************************************
 * Pty handling
 **********************************************************************/

static int openPty(const char* pty)
{
  if(freopen(pty,"r+",stdin)) {
    close(fileno(stdout));
    dup(fileno(stdin));
    setbuf(stdin,0);
    setbuf(stdout,0);

    return 1;
  }
  return 0;
}

#ifdef i386
/* PCVT conflicts with ttyv*. */
#define TTY_LETTERS "pqrstuwxyzPQRST"
#else
#define TTY_LETTERS "pqrstuvwxyzPQRST"
#endif /*i386*/

static int getPty(void)
{
  static char dev[]="/dev/ptyXX";
  char* c1;
  char* c2;

  for(c1=TTY_LETTERS;*c1;c1++) {
    dev[8]=*c1;
    for(c2="0123456789abcdef";*c2;c2++) {
      dev[9]=*c2;
      if(freopen(dev,"r+",stdin)) {
	char tty[11];

	strcpy(tty,dev);
	tty[5]='t';
	printf("%s\n",tty);
	fflush(stdout);

	close(fileno(stdout));
	dup(fileno(stdin));
	setbuf(stdin,0);
	setbuf(stdout,0);

	return 1;
      }	
      if(errno==ENOENT) return 0;
    }
  }
  return 0;
}

/**********************************************************************
 * Forking
 **********************************************************************/

static void forkDaemon(void)
{
  switch (fork()) {
  case -1:
    birda_log("Fork failed\n");
    exit(-4);
  case  0:
    break;
  default:
    _exit(0);
  }

  if(setsid()==-1) {
    birda_log("setsid failed\n");
    exit(-5);
  }

  chdir("/");
}

/**********************************************************************
 * Pid file
 **********************************************************************/

static char pidFile[256];

void removePidFile(void)
{
  unlink(pidFile);
}

static void terminateHandler(int sig)
{
  removePidFile();
  exit(0);
}

static void writePidFile(const char* prog)
{
  FILE* f;
  struct sigaction sa;
  const char* p;

  p=strrchr(prog,'/');
  if(p) p++; else p=prog;
  sprintf(pidFile,"/var/run/%s.pid",p);

  sa.sa_flags=0;
  sa.sa_handler=terminateHandler;
  sigemptyset(&sa.sa_mask);
  sigaction(SIGINT,&sa,0);
  sigaction(SIGTERM,&sa,0);

  atexit(removePidFile);

  f=fopen(pidFile,"w");
  if(f) { 
    fprintf(f,"%d\n",getpid());
    fclose(f);
  }
}

/**********************************************************************
 * LAP control
 **********************************************************************/

static void addHint(char** h, const char* hintbuf, char* name)
{
  char* p=*h;

  if(p!=hintbuf) { *p++=','; *p++=' '; }
  strcpy(p,name);
  p+=strlen(p);
  *h=p;
}

static void showDiscovered(int addr, int hints, int charset, const char* name, int len)
{
  int i;
  char namebuf[32];
  char hintbuf[128];
  char* p=hintbuf;

  if(charset==CHARSET_UNICODE) {
    /* Strip the high byte */
    for(i=0;2*i<len;i+=2) namebuf[i++]=name[2*i];
    namebuf[i]=0;
  } else {
    memcpy(namebuf,name,len);
    namebuf[len]=0;
  }

  if(hints&HINT_PNP)         addHint(&p,hintbuf,"PnP");
  if(hints&HINT_PDA)         addHint(&p,hintbuf,"PDA");
  if(hints&HINT_COMPUTER)    addHint(&p,hintbuf,"Computer");
  if(hints&HINT_PRINTER)     addHint(&p,hintbuf,"Printer");
  if(hints&HINT_MODEM)       addHint(&p,hintbuf,"Modem");
  if(hints&HINT_FAX)         addHint(&p,hintbuf,"Fax");
  if(hints&HINT_LAN_ACCESS)  addHint(&p,hintbuf,"LAN");
  if(hints&HINT_TELEPHONY)   addHint(&p,hintbuf,"Telephony");
  if(hints&HINT_FILE_SERVER) addHint(&p,hintbuf,"File server");
  if(hints&HINT_IRCOMM)      addHint(&p,hintbuf,"IrCOMM");
  if(hints&HINT_MESSAGE)     addHint(&p,hintbuf,"Message");
  if(hints&HINT_HTTP)        addHint(&p,hintbuf,"HTTP");
  if(hints&HINT_IROBEX)      addHint(&p,hintbuf,"IrOBEX");

  birda_log("discovered %s, address=%x, hints=%s\n",namebuf,addr,hintbuf);
}

static bool lapStatus(LAP* lap, int event, int addr,
		      int hints, int charset, char* name, int len)
{
  switch(event) {
  case LAP_DISCOVERING:
    if(verbosity>=VERBOSITY_INFO) birda_log("participating in discovery\n");
    return TRUE;
  case LAP_CONNECTING:
    if(verbosity>=VERBOSITY_INFO) birda_log("accepting connection\n");
    return TRUE;
  case LAP_DISCOVERED:
    if(verbosity>=VERBOSITY_INFO) showDiscovered(addr,hints,charset,name,len);
    if(!address) address=addr;
    break;
  case LAP_DISCOVERY_END:
    if(verbosity>=VERBOSITY_INFO) birda_log("query completed\n");
    if(address) {
      if(!lapConnect(lap,address)) {
	if(optLapDisconnected) optLapDisconnected(lap);
      }
    } else if(++tries<10) {
      lapDiscover(lap);      
    } else {
      birda_log("No peer station found\n");
      if(optLapDisconnected) optLapDisconnected(lap);
    }
    break;
  case LAP_CONNECTED:
    if(optLapConnected) optLapConnected(lap);
    break;
  case LAP_DISCONNECTED:
    if(optLapDisconnected) optLapDisconnected(lap);
    break;
  }
  return FALSE;
}

/**********************************************************************
 * Options processing
 **********************************************************************/

static void dumpHandler(int sig)
{
  showResources();
}

void doOptions(int argc, char* const argv[],
	       int optc, Option* options,
	       int* verbosityp, const char* desc)
{
  int c,i;
  char optbuf[256];
  char* p;
  int error=0;
  int usage=0;
  char* port=0;
  char* pty=0;
  char* logFile=0;
  int daemon=0;
  int dongle=DONGLE_NONE;
#ifdef IRDA_KERNEL_DRIVER
  int kdevice=1;
#else
  int kdevice=0;
#endif
  int maxspeed=0;
  struct sigaction sa;
  char* thisHost=0;
  char thisHostBuf[128];
  SerialPort* serPort;
  SerialDevice* serDev;
  FrameDevice* frameDev;

  /* Standard options */
  strcpy(optbuf,"d:hkKl:tjaARpgv:y:Ym:n:");

  /* User options */
  p=optbuf+strlen(optbuf);
  for(i=0;i<optc && p-optbuf<sizeof optbuf-2;i++) {
    Option* o=options+i;
    *p++=o->opt;
    if(o->type!=OPTION_BOOL) *p++=':';
  }
  *p=0;

  while((c=getopt(argc,argv,optbuf))!=-1) {
    switch(c) {
    case 'd': port=optarg; break;
    case 'h': usage=1; break;
    case 'K': kdevice=0; break;
    case 'k': kdevice=1; break;
    case 'l': logFile=optarg; break;
    case 't': dongle=DONGLE_TEKRAM; break;
    case 'j': dongle=DONGLE_JETEYE; break;
    case 'a': dongle=DONGLE_ACTISYS; break;
    case 'A': dongle=DONGLE_ACTISYS_PLUS; break;
    case 'p': dongle=DONGLE_LITELINK; break;
    case 'g': dongle=DONGLE_GIRBIL; break;
    case 'R': dongle=DONGLE_REDLINK; break;
    case 'v': if(sscanf(optarg,"%d",&verbosity)!=1) error=1; break;
    case 'y': pty=optarg; break;
    case 'Y': daemon=1; break;
    case 'm': if(sscanf(optarg,"%d",&maxspeed)!=1) error=1; break;
    case 'n': thisHost=optarg; break;
    case '?': error=1; break;
    default:
      for(i=0;i<optc;i++) {
	Option* o=options+i;
	if(o->opt==c) {
	  switch(o->type) {
	  case OPTION_STRING:
	    o->val.s=optarg;
	    break;
	  case OPTION_INT:
	    sscanf(optarg,"%d",&o->val.i);
	    break;
	  default: /*OPTION_BOOL*/
	    o->val.b=TRUE;
	    break;
	  }
	}
      }
      break;
    }
  }

  if(usage) {
    const char** dp;
    int first;

    if(desc) fprintf(stderr,"%s\n",desc);
    fprintf(stderr,"Usage: %s {options}\n",argv[0]);
    fprintf(stderr,"    -d dev    Device to use\n");
    fprintf(stderr,"    -m speed  Limit maximum baud rate\n");
    fprintf(stderr,"    -n name   Station name (defaults to host name)\n");
    fprintf(stderr,"    -l file   Log to file\n");
    fprintf(stderr,"    -y pty    Redirect stdin/stdout\n");
    fprintf(stderr,"    -v level  Verbosity\n");
    fprintf(stderr,"    -Y        Fork and run as daemon\n");
    fprintf(stderr,"    -t        Tekram IR-210B dongle\n");
    fprintf(stderr,"    -j        Extended Systems JetEye dongle\n");
    fprintf(stderr,"    -a        ACTiSYS IR-220L dongle\n");
    fprintf(stderr,"    -A        ACTiSYS IR-220L+ dongle\n");
    fprintf(stderr,"    -p        Parallax LiteLink dongle\n");
    fprintf(stderr,"    -g        Greenwich GIrBIL dongle\n");
    fprintf(stderr,"    -R        Redlink 105 dongle (ACTiSYS IR-200L)\n");
#ifdef IRDA_KERNEL_DRIVER
    fprintf(stderr,"    -K        Don't use kernel frame device\n");
#endif /* IRDA_KERNEL_DRIVER */
    fprintf(stderr,"    -h        Help\n");
    for(i=0;i<optc;i++) {
      Option* o=options+i;
      const char* arg="";
      const char* desc="";
      if(o->type!=OPTION_BOOL) arg=o->arg ? o->arg : "x";
      if(o->desc) desc=o->desc;
      fprintf(stderr,"    -%c %-6s %s\n",o->opt,arg,desc);
    }
    fprintf(stderr,"Serial port defaults: ");
    for(first=1,dp=defaultPorts;*dp;dp++) {
      fprintf(stderr,"%s%s",first ? "" : ", ", *dp);
      first=0;
    }
    fprintf(stderr,"\n");

    exit(0);
  }

  if(error || optind<argc) {
    fprintf(stderr,"Bad arguments (use -h for help)\n");
    exit(-1);
  }

  sa.sa_flags=0;
  sa.sa_handler=dumpHandler;
  sigemptyset(&sa.sa_mask);
  sigaction(SIGQUIT,&sa,0);

  if(logFile && !setFileLog(logFile)) {
    fprintf(stderr,"Cannot open log file\n");
    exit(-2);
  }

  if(pty && !openPty(pty)) {
    birda_log("Cannot open pty\n");
    exit(-6);
  }

  if(daemon) {
    if(!logFile) setSysLog();
    fclose(stderr);
    if(!pty) {
      if(!getPty()) {
	birda_log("No available pty\n");
	exit(-3);
      }
    }
    forkDaemon();
    writePidFile(argv[0]);
  }

  if(verbosity>=VERBOSITY_EVENT_TIMERS) evtDebug|=EVT_DEBUG_TIMERS;
  if(verbosity>=VERBOSITY_EVENT_SELECT) evtDebug|=EVT_DEBUG_SELECT;

  if (!kdevice) {
    if(port) {
      if(!(serPort=createSerialPort(port,maxspeed))) {
	birda_log("Bad serial port\n");
	exit(-7);
      } 
    } else {
      const char** dp;
      for(dp=defaultPorts;*dp;dp++)
	if((serPort=createSerialPort(*dp,maxspeed))) break;
      if(!*dp) {
	birda_log("Cannot default the port!\n");
	exit(-8);
      }
      if(verbosity>=VERBOSITY_INFO) birda_log("Using %s\n",*dp);
    }
    if(verbosity>=VERBOSITY_LOW_LEVEL_IO) serPort->debug|=SP_DEBUG_INPUT;
  }
  
#ifdef IRDA_KERNEL_DRIVER
  if (kdevice) {
    frameDev = createKernelFrameDevice(port,maxspeed);
  } else
#endif /* IRDA_KERNEL_DRIVER */
  {
    switch(dongle) {
    case DONGLE_TEKRAM:
      serDev=createTekram210Device(serPort);
      break;
    case DONGLE_JETEYE:
      serDev=createJetEyeDevice(serPort);
      break;
    case DONGLE_ACTISYS:
      serDev=createACTiSYS220Device(serPort,FALSE);
      break;
    case DONGLE_ACTISYS_PLUS:
      serDev=createACTiSYS220Device(serPort,TRUE);
      break;
    case DONGLE_LITELINK:
      serDev=createLiteLinkDevice(serPort);
      break;
    case DONGLE_GIRBIL:
      serDev=createGIrBILDevice(serPort);
      break;
    case DONGLE_REDLINK:
      serDev=createRedlinkDevice(serPort);
      break;
    default:
      serDev=createRawDevice(serPort);
      break;
    }
    frameDev=createSIRFrameDevice(serDev);
  }
  if(verbosity>=VERBOSITY_LOW_LEVEL_IO) frameDev->debug|=FRAME_DEBUG_INPUT;

  if(!thisHost) {
    thisHost=thisHostBuf;
    gethostname(thisHostBuf,sizeof thisHostBuf);
    thisHostBuf[sizeof thisHostBuf-1]=0;
  }

  optLap=createLAP(frameDev,CHARSET_ASCII,thisHost,strlen(thisHost));
  optLap->flags|=HINT_COMPUTER;
  optLap->status=lapStatus;
  if(verbosity>=VERBOSITY_TIMERS) optLap->debug|=LAP_DEBUG_TIMERS;
  if(verbosity>=VERBOSITY_FRAMES_OUT) optLap->debug|=LAP_DEBUG_FRAMES_OUT;
  if(verbosity>=VERBOSITY_FRAMES_IN) optLap->debug|=LAP_DEBUG_FRAMES_IN;
  if(verbosity>=VERBOSITY_INFO) optLap->debug|=LAP_DEBUG_INFO;

  optIas=createIASServer(optLap,CHARSET_ASCII,thisHost,strlen(thisHost));
  if(verbosity>1) optIas->debug|=IAS_DEBUG_INFO;

  if(verbosityp) *verbosityp=verbosity;
}

void doConnect(void)
{
  address=0;
  lapDiscover(optLap);
}
