
#ifdef LAFEREVISION
char *LafeRevision=LAFEREVISION;
#else
char *LafeRevision="$Revision: 1.3 $";
#endif 

/* Latency Free Empire

   This is a client optimized for high latency links.  You can type
   in, edit, and send several commands before the server replies with
   output from the first command.  This allows you to make better use
   of the existing bandwidth than most other clients.  pei and eif are
   particularly susceptible to latency, as they require a complete
   exchange of data before prompting again.  This could also be called
   LAg Free Empire.  Empire 2 asynch features are supported.  Empire 3
   C_SYNC isn't supported yet, as this is a rather dumb client (in
   between emp_client and eif).  Readline is supported, and at the
   moment required.  If you want lafe without readline, use emp_client
   instead.

   -harmless@empire.net */



#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/wait.h>
#include <signal.h>
#include <netdb.h>
#include <curses.h>
#include <fcntl.h>
#include <arpa/inet.h>

/* include-file hell... I hate ifdefs... */
#if defined(LINUX)
#include <sys/time.h>
#include <unistd.h>
#else
#include <term.h>
#include <sys/select.h>
#endif

#include <readline/readline.h>
#include <readline/history.h>
#include "lafe.h"


/* some defaults in case nothing is specified.  Change as you wish. */
#define DEFPORT "6789"
#define DEFHOST "blitz.wolfpackempire.com"
#define DEFCOUNTRY "visitor"
#define DEFPASSWORD "visitor"

#ifndef LAFERC
#define LAFERC "~/.laferc"
#endif

int verbose=0;


/************************************************************************
 *   The filedescriptor of the empire server socket.  Had to make this
 * global in order to be accessible to event_hook().
 *   I always feel guilty when using globals...
 ************************************************************************/
int s=-1;

/************************************************************************
 *  The stream pointer of the log file.  Another global..
 ************************************************************************/
FILE *log=NULL;

/* process commands before sending to server on socket s.
 * handles aliases and local commands. Called by execute() and runfeed()
 * as well. */
extern void parse_command(char *);


/* the length of the prompt.  This plus rl_point is how far we have to
   go back to the beginning of the line. */
/* extern int rl_visible_prompt_length; */

/*  I'm cheating by using these.  */
/*
extern int _rl_horizontal_scroll_mode;
extern int _rl_vis_botlin, _rl_last_v_pos, _rl_last_c_pos;
*/

/* pointer to the readline prompt.  This can change in the middle of
 * readline() if a prompt arrives in the middle of typing. */
/* extern char *rl_prompt,*rl_display_prompt; */



/************************************************************************
 *   Interrupt count, incremented each time an interrupt is received.
 ************************************************************************/
static int interrupts=0;

void interrupt() {
  interrupts++;
}

/***************************************************************************
 *   A few routines to clear the readline-portion of the display.
 *   Some initialization to get the necesary termcap strings and a
 *   couple support routines.
 ***************************************************************************/

static char *tc_sc, *tc_rc, *tc_cd; /* save, restore, clear-to-eos */
static char *tc_LE, *tc_ce;
static char *tc_ch, *tc_UP;
static char *tc_so,*tc_se;	/* highlighting on and off */

extern char *tgetstr();

/* for those systems that think putchar is a macro ... */
int putcharf(int c) {
  return putchar(c);
}


void init_tc() {
  static char *termstrbuf;
  char **area;

  /* SGI and IBM docs say area is meaningless under terminfo emulation, but
     it crashes without a sizeable buffer on the SGI... */
  termstrbuf = malloc(2048);
  area = &termstrbuf;
  tgetent(termstrbuf,getenv("TERM"));

  tc_rc = tgetstr("rc",area); /* restore cursor */
  tc_cd = tgetstr("cd",area); /* clear eos */
  tc_sc = tgetstr("sc",area); /* save cursor */
  tc_LE = tgetstr("LE",area); /* left #1 */
  tc_ce = tgetstr("ce",area); /* clear to eol */
  tc_ch = tgetstr("ch",area); /* set horzontal cursor position */
  tc_UP = tgetstr("UP",area); /* up #1 */
  tc_se = tgetstr("se",area); /* turn off highlighting */
  tc_so = tgetstr("so",area); /* turn on highlighting */


  /* this section is just for debugging termcap capabilities */
#if 0
  if (tc_sc) fprintf(stderr,"tc: save cursor\n");
  else fprintf(stderr,"tc: no save cursor\n");

  if (tc_sc) fprintf(stderr,"tc: set horizontal cursor\n");
  else fprintf(stderr,"tc: no set horizontal cursor\n");

  if (tc_rc) fprintf(stderr,"tc: restore\n");
  else fprintf(stderr,"tc: no restore\n");

  if (tc_cd) fprintf(stderr,"tc: clear to end of screen\n");
  else fprintf(stderr,"tc: no clear to end of screen\n");

  if (tc_LE) fprintf(stderr,"tc: left param\n");
  else fprintf(stderr,"tc: no left param\n");

  if (tc_ce) fprintf(stderr,"tc: clear to end of line\n");
  else fprintf(stderr,"tc: no clear to end of line\n");
#endif

}

/* move to left edge and clear to end */
void clear_readline() {

  if (tc_ch) {
    /* extra 0 added to tparm() so tgoto() can be used interchangeably */
    tputs(tparm(tc_ch,0,0),1,putcharf);
#if 0  
  }
   else if (tc_LE) {
    tputs(tparm(tc_LE,_rl_last_c_pos,0),1,putcharf);
#endif
  } else if (tc_rc) {
    tputs(tc_rc,1,putcharf);
  } else {
    putcharf('\r');
  }

#if 0
  if (_rl_last_v_pos>0) {
    if (tc_UP) {
      tputs(tparm(tc_UP,_rl_last_v_pos,0),1,putcharf);
    } else {
      /* couldn't reach beginning, the display will look weird */
    }
  }
#endif

  if (tc_cd) {
    tputs(tc_cd,1,putcharf);
  } else if (tc_ce) {
    tputs(tc_ce,1,putcharf);
    /* may not have cleared whole screen */
  } else {
    printf("\n");
  }

}

/* save position in case we need to clear */
void save_readline() {
  if (tc_sc) {
    tputs(tc_sc,1,putcharf);
  }
}


void quit() {
  rl_cleanup_after_signal();
  exit(0);
}

  

/********************************************************
 *	Maximum number of commands on the output stack
 ********************************************************/
FILE *out;


/* current prompt mode.  Will be actual only if commands==0. */
char mode='\000';

static char *prompt=NULL;
static int timeleft,btus,teles=0;


/************************************************************
 *	Handle input lines depending on protocol number
 ************************************************************/
void process_line(char *buffer,int len) {
  int i,j,k;

  if (len<3) return;

  switch (buffer[0]) {

  case 'a':
  case '3':
    fwrite(buffer+2,1,len-2,out);
    if (log) fwrite(buffer+2,1,len-2,log);
    quit();
    break;

  case '1':
    /* if out is the terminal, attempt to send highlighting codes */
    if (out==stdout && tc_so && tc_se) {
      int i,lit=0;
      for (i=2;i<len;i++) {
	if (buffer[i] & 0x80) {
	  if (!lit) tputs(tc_so,1,putcharf);
	  putchar(buffer[i] & 0x7f);
	  if (log) putc(buffer[i] & 0x7f ,log);
	  lit=1;
	} else {
	  if (buffer[i]==' ' && i<len-1 && (buffer[i+1] & 0x80)) {
	    /* leave intervening space lit */
	  } else if (lit) tputs(tc_se,1,putcharf);
	  putchar(buffer[i]);
	  if (log) putc(buffer[i],log);
	  lit=0;
	}
      }
      if (lit) tputs(tc_se,1,putcharf);

    } else {		  
      int i;
      for (i=2;i<len;i++) buffer[i] &= 0x7f;
      fwrite(buffer+2,1,len-2,out);
      if (log) fwrite(buffer+2,1,len-2,log);
    }

    break;

  case 'g':  /* mirrored subprompt */
  case 'h':  /* mirrored command prompt */
  case 'i':  /* mirrored data */
    /* attempt to send highlighting codes, really only appropriate for 'g' */
    if (tc_so && tc_se) {
      int i,lit=0;
      for (i=2;i<len;i++) {
	if (buffer[i] & 0x80) {
	  if (!lit) tputs(tc_so,1,putcharf);
	  putchar(buffer[i] & 0x7f);
	  if (log) putc(buffer[i] & 0x7f ,log);
	  lit=1;
	} else {
	  if (buffer[i]==' ' && i<len-1 && (buffer[i+1] & 0x80)) {
	    /* leave intervening space lit */
	  } else if (lit) tputs(tc_se,1,putcharf);
	  putchar(buffer[i]);
	  if (log) putc(buffer[i],log);
	  lit=0;
	}
      }
      if (lit) tputs(tc_se,1,putcharf);

    } else {		  
      int i;
      for (i=2;i<len;i++) buffer[i] &= 0x7f;
      fwrite(buffer+2,1,len-2,stdout);
      if (log) fwrite(buffer+2,1,len-2,log);
    }

    break;

  case '0':
  case '2':
  case '7':
  case 'b':
    if (verbose) fwrite(buffer+2,1,len-2,out);
    if (log) fwrite(buffer+2,1,len-2,log);
    break;

  case 'd':
    fwrite(buffer+2,1,len-2,out);
    if (log) fwrite(buffer+2,1,len-2,log);
    break;

  case 'e':
    buffer[len-1]=0;
    /* rl_message(buffer+2); */
    if (len>3) {
      teles=1;
      sscanf(buffer+2,"%d new teles",&teles);
      putchar('\07');

      /* BUG: prompt may not be a command prompt */

      if (prompt) free(prompt);
      prompt=malloc(len+30);
      if (!commands) {
	sprintf(prompt,"%s [%d:%d] Command: ",buffer+2,timeleft,btus);
      } else {
	sprintf(prompt,"%s : ",buffer+2);
      }

      /* cheated a bit here again.. this isn't supposed to be allowed */
      /* rl_display_prompt=rl_prompt=prompt; */
      rl_set_prompt(prompt);
/*      rl_redisplay(); */
#if 0	
      rl_visible_prompt_length = rl_expand_prompt(rl_prompt);
#endif

    } else {
      teles=0;
    }


    break;

  case '4':
  case '5':
    mode=buffer[0];
    if (commands>1) {
	buffer[len-2]=0;
	fprintf(stdout,"%s %s\n",buffer+2,commandline[1]);
	if (log) fprintf(log,"%s %s\n",buffer+2,commandline[1]);
    } else {
      if (log) fwrite(buffer+2,1,len-3,log);
    }

    if (commands>0 && commandline[0]) {
      free(commandline[0]);
    }
    for (i=1;i<commands;i++) commandline[i-1]=commandline[i];
    if (commands>0) commands--;
    
    if (prompt) free(prompt);
    prompt=malloc(len);
    memcpy(prompt,buffer+2,len-3);
    prompt[len-3]=' ';
    prompt[len-2]=0;
    if (!commands) {
/*      rl_display_prompt=rl_prompt=prompt; */
	rl_set_prompt(prompt);
    } else {
/*      rl_display_prompt=rl_prompt=": "; */
	rl_set_prompt(": ");
    }
#if 0
    rl_visible_prompt_length = rl_expand_prompt(rl_prompt);
#endif
    break;

  case '6':
    mode=buffer[0];
    if (out!=stdout) {
      fclose(out);
      /* if pipe */
      wait(NULL);
    }
    out=stdout;
    sscanf(buffer+2,"%d %d",&timeleft,&btus);
    if (commands>1) {
      fprintf(stdout,"[%d:%d] Command: %s\n",timeleft,btus,commandline[1]);
      if (log)
	fprintf(log,"[%d:%d] Command: %s\n",timeleft,btus,commandline[1]);
    } else {
      if (log)
	fprintf(log,"[%d:%d] Command: ",timeleft,btus);
    }

    if (commands>0 && commandline[0]) {
      free(commandline[0]);
    }

    for (i=1;i<commands;i++) commandline[i-1]=commandline[i];
    if (commands>0) commands--;

    if (prompt) free(prompt);
    prompt=malloc(len+30);
    sprintf(prompt,"[%d:%d] Command: ",timeleft,btus);

    /* cheated a bit here again.. this isn't supposed to be allowed */
    if (!commands) {
      rl_set_prompt(prompt);
/*      rl_redisplay(); */
      /* rl_display_prompt=rl_prompt=prompt; */
    } else {
      /* rl_display_prompt=rl_prompt=": "; */
      rl_set_prompt(": ");
      rl_redisplay();
    }
/*    rl_visible<_prompt_length = rl_expand_prompt(rl_prompt); */

    break;

  case '8':
    for (i=2;(buffer[i]=='>') && i<len;i++) ;
    for (j=i;isspace(buffer[j]) && j<len;j++) ;
    for (k=j;!isspace(buffer[k]) && k<len;k++) ;
    buffer[k]=0;
    if (i>3) {
      out = fopen(buffer+j,"a");
    } else {
      out = fopen(buffer+j,"w");
    }
    if (!out) {
      perror("fopen");
      out=stdout;
    }
    break;

  case '9':
    for (i=2;(buffer[i]=='|' || isspace(buffer[i])) && i<len;i++) ;
    /*    for (j=i;!isspace(buffer[j]) && j<len;j++) ; */
    buffer[len-1]=0;
    out = popen(buffer+i,"w");
    if (!out) {
      perror("popen");
      out=stdout;
    }
    break;

  case 'c':
    fprintf(stderr,"C_EXEC should have been processed locally\n");
    write(s,"ctld\n",5);

    break;

    

  case 'f':
    /* C_SYNC protocol ... ignored for now: toggle sync off */
    /* glue to the client-lib when released */
    if (verbose) fwrite(buffer,1,len,out);
    break;

  default:
    fprintf(stderr,"Unimplemented protocol:\n");
    fwrite(buffer,1,len,out);
    break;
  }
}


/* process whole lines, transfer remainder to the top of the buffer
   and return the length of the remainder */
int output(char *buffer,int len) {
  int start,end;

  start=0;
  
  for (;;) {
    for (end=start;end<len;end++) {
      if (buffer[end]=='\n') break;
    }
    if (end<len) {
      process_line(buffer+start,end-start+1);
    } else {
      if (end!=start) {
	memcpy(buffer,buffer+start,end-start);
	buffer[end-start]=0;
	return end-start;
      } else {
	return 0;
      }
    }
    start=end+1;
  }
}

/***************************************************************************
 *     The guts of the asynchronous behavior of lbe.  Selects both the
 *  socket to the empire server and the tty to wait for input.
 *  Prints out any socket data (redrawing partial input lines if
 *  necessary).  Returns when tty input is found (so readline() can
 *  continue).
 *     The name of this routine is passed to readline through a global
 *  variable (rl_event_hook).
 ***************************************************************************/
void event_hook() {
  fd_set in;
  int i;
  int len;
  static char buffer[2048];
  static int offset=0;	/* unprocessed remnants in buffer */

  /* wait for something from stdin or s */
  FD_ZERO(&in);
  FD_SET(s,&in);
  FD_SET(fileno(stdin),&in);
  i=select( FD_SETSIZE,&in,NULL,NULL,NULL);

  if (i<=0) {
    if (interrupts>1) {
      printf("Exitting on multiple interrupts\n");
      rl_cleanup_after_signal();
      exit(-1);
    } else if (interrupts) {
      write(s,"aborted\n",8);
      printf("^C\n");
    } else {
      if (verbose) perror("select");
    }
  }

  if (i>0) {
    interrupts=0; /* typing or receiving any data clears the interrupt count */

    if (FD_ISSET(s,&in)) {
      int received=0;

      clear_readline();
      fflush(rl_outstream);

      while ( (len=recv(s,buffer+offset,sizeof(buffer)-offset,0)) > 0) {
	received+=len;
	offset=output(buffer,len+offset);
      }

      if (received==0) {
	if (errno==ENOENT) printf("Lost connection to server\n");
	else perror("recv");
	quit();
      }

      if (errno==EAGAIN) {
	/* no worries */
      } else {
	perror("recv2");
	quit();
      }

      rl_forced_update_display();
    }
  }
}



/***************************************************************************
 * similar to event_hook.  Keeps reading data until the connection is
 * resynchronized.  Call before running a tool that needs complete data
 * (cmvr's and the like).
 ***************************************************************************/
void waitsync(char *dummy) {
  fd_set in;
  int i;
  int len;
  static char buffer[2048];
  static int offset=0;	/* unprocessed remnants in buffer */

  while (commands>0) {

    /* wait for something from s */
    FD_ZERO(&in);
    FD_SET(s,&in);
    i=select( FD_SETSIZE,&in,NULL,NULL,NULL);

    if (i<=0) {
      if (interrupts>1) {
	printf("Exitting on multiple interrupts\n");
        rl_cleanup_after_signal();
	exit(-1);
      } else if (interrupts) {
	write(s,"aborted\n",8);
	printf("^C\n");
      } else {
	if (verbose) perror("select");
      }
    }

    if (i>0) {
      interrupts=0; /* typing or receiving any data clears the interrupt count */

      if (FD_ISSET(s,&in)) {
	int received=0;

	while ( (len=recv(s,buffer+offset,sizeof(buffer)-offset,0)) > 0) {
	  received+=len;
	  offset=output(buffer,len+offset);
	}

	if (received==0) {
	  if (errno==ENOENT) printf("Lost connection to server\n");
	  else perror("recv");
	  quit();
	}

	if (errno==EAGAIN) {
	  /* no worries */
	} else {
	  perror("recv2");
	  quit();
	}
      }
    }
  }
}




/************************************************************
 *    Open a socket to host/port and return filedescriptor.
 ************************************************************/
int empconnect(char *host,int port,char *user,char *country,char *pass) {
  struct	hostent *hp;
  struct	sockaddr_in sin;
  char buffer[1024];

  if (isdigit(*host)) {
    sin.sin_addr.s_addr = inet_addr(host);
  } else {
    hp = gethostbyname(host);
    if (hp == NULL) {
      fprintf(stderr, "%s: No such host\n", host);
      return 0;
    }
    memcpy( &sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
  }

  sin.sin_port = htons(port);

  s = socket(AF_INET, SOCK_STREAM, 0);
  if (s < 0) {
    perror("socket:");
    return -1;
  }
  sin.sin_family = AF_INET;
  if (connect(s, (struct sockaddr*)&sin, sizeof(sin)) < 0) {
    perror("connect");
    return -1;
  }

  fcntl(s,F_SETFL,FNONBLOCK);

  if (!user) user="nobody";

  sprintf(buffer,"user %s\ncoun \"%s\"\npass %s\nclient lafe %s\nplay\n",
	  user,country,pass,LafeRevision);
  write(s,buffer,strlen(buffer));

  return s;
}




/***********************************************************************
 *    Set up readline() and socket s to server.  Push each new line
 *  down the socket.
 ***********************************************************************/
int main(int argc, char **argv)
{
  char *line;
  char *host=NULL,*port=NULL,*country=NULL,*password=NULL,*logfile=NULL;

  out = stdout;
  rl_readline_name = argv[0];
  rl_event_hook = (Function *)event_hook;
  rl_startup_hook = (Function *)save_readline;


  init_tc();
  
  signal(SIGPIPE,SIG_IGN);	/* don't worry if a destination pipe closes */
  signal(SIGINT,interrupt);

  execute(LAFERC);


  if (argc>=5) {
    fprintf(stderr,"arguments may be visible\nmodifying your .laferc is the prefered method\n");
    country=strdup(argv[1]);
    password=strdup(argv[2]);
    host=strdup(argv[3]);
    port=strdup(argv[4]);
    if (argc>5) logfile=strdup(argv[5]); else logfile=NULL;
  } else if (argc>=3) {
    fprintf(stderr,"arguments may be visible\nmodifying your .laferc is the prefered method\n");
    country=strdup(argv[1]);
    password=strdup(argv[2]);
    host=getenv("EMPIREHOST");
    port=getenv("EMPIREPORT");
    if (argc>3) logfile=strdup(argv[3]); else logfile=NULL;
  } else if (argc==2) {
    int i;
    i = lookup_game(argv[1]);
    if (i>=0) {
      country = game[i].country;
      password = game[i].password;
      host = game[i].host;
      port = game[i].port;
      logfile = game[i].logfile;
      if (game[i].directory) {
	if (chdir(game[i].directory)) {
	  perror(game[i].directory);
	}
      }
    } else {
      fprintf(stderr,"%s not found in addgame database.  Edit ~/.laferc.\n",
	      argv[1]);
    }

  } else if (argc==1) {
    fprintf(stderr,"environment variables may be visible\nmodifying your .laferc is the prefered method\n");
    country=getenv("COUNTRY"); /* not advised for security reasons */
    password=getenv("REPRESENTATIVE");
    host=getenv("EMPIREHOST");
    port=getenv("EMPIREPORT");
    logfile=NULL;
  } else {
    fprintf(stderr,"Usage:\
\t%s host port country password [logfile]\n\
\t%s host port [logfile]\n\
\t%s [logfile]\n\
environment variables: EMPIREHOST EMPIREPORT COUNTRY REPRESENTATIVE\n",
	    argv[0],argv[0],argv[0]);
  }

  if (!host) { 
    host=DEFHOST;
    fprintf(stderr,"host defaulting to %s\n",host);
  }
  if (!port) {
    port=DEFPORT;
    fprintf(stderr,"port defaulting to %s\n",port);
  }
  if (!country) {
    country=DEFCOUNTRY;
    fprintf(stderr,"country defaulting to %s\n",country);
  }
  if (!password) {
    password=DEFPASSWORD;
    fprintf(stderr,"password defaulting to %s\n",password);
  }

  if (logfile) {
    log = fopen(logfile,"a");
    if (!log) perror("log fopen");
  }

  s=empconnect(host,atoi(port),getenv("USER"),country,password);

  if (s<0) {
    fprintf(stderr,"Unable to log in to %s at %s/%s\n",country,host,port);
    exit(-1);
  }

  /* clear command arguments.  Doesn't work on all systems. */
  {
    int i,j;
    for (i=0;i<argc;i++) {
      for (j=0;argv[i][j];j++) argv[i][j]=0;
      argv[i]=0;
    }
  }

  commands=1;
  command_stack=10;
  commandline=malloc(sizeof(*commandline)*command_stack);
  commandline[0]=NULL;

  prompt=NULL;
  

  /* Loop reading and executing lines until the user quits. */
  for ( ; ; ) {
    if (!commands) {
      line = readline(prompt);
    } else {
      line = readline(": ");
    }

    if (!line)
      break;
    
    add_history(line);


    /* command handler, most get written directly to socket s */
    parse_command(line);

  }

  if (log) {
    fprintf(log,"\n\n");
    fclose(log);
  }

  close(s);
  return 0;
}

