/* 
   Copyright (C) 2006 Stephen Jungels

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */      

/* Written by Stephen Jungels.  */

#include <stdio.h>
#include <string.h>

static void usage();
static void version();
static void process_line(char *);

/* the name this program was called by */
char *me;

/* set for non-fatal error */
static int exitstatus;

/* An array of file handles */
FILE *inputfile, **inputfiles;
size_t ninputs;
size_t inputindex;

/* Expandable argument list */
char **myargv;
size_t myargc;
size_t nargs;

/* skip hints array */
char **skiphints;
size_t nskiphints;
size_t skipindex;

/* play hints array */
char **playhints;
size_t nplayhints;
size_t playindex;

/* the tty */
FILE *tty_in, *tty_out;

/* todo: make some of these static local */
#define BUFLEN 512
char all[2];              /* this choice applies to all remaining lines */
char prefix[BUFLEN];      /* remove this prefix from each line */
char search[BUFLEN];      /* a search string */
char savesearch[BUFLEN];  /* the last search, if any */
char levelsep[BUFLEN];    /* the separator character for levels in the hierarchy */
char verb[BUFLEN];        /* the action word preceding each line */
int state;                /* 0 means init state of process_line */
int levelindex;           /* level in the (directory?) hierarchy */


/* insert an argument into the expandable argument list */
int
insert_arg (char *arg, size_t index)
{
  int i;

  if (index > myargc)
    return (-1);
  myargc++;
  if (myargc > nargs)
    {
      myargv = realloc (myargv, 2 * nargs * sizeof *myargv);
      nargs *= 2;
    }
  for (i = index; i < myargc; i++)
    {
      char *temp;
      temp = myargv[i];
      myargv[i] = arg;
      arg = temp;
    }
  return (myargc);
}


int
main (int argc, char **argv)
{
  char line[BUFLEN];

  me = argv[0];
  exitstatus = 0;

  ninputs=10;
  inputindex=0;
  inputfiles = malloc (ninputs * sizeof *inputfiles);

  strcpy(prefix, "");
  strcpy(levelsep, "/");
  strcpy(verb, "Include");

  /* copy arguments to expandable list */

  nargs = 20;
  myargc = 0;
  myargv = malloc (nargs * sizeof *myargv);
  int i; 
  for (i = 0; i < argc; i++)
    insert_arg (argv[i], myargc);

  i=1; 
  while (i<myargc)
    {
      if (strcmp ("-h", myargv[i]) == 0 || strcmp ("--help", myargv[i]) == 0)
	{
	  usage();
	  exit (0);
	}
      else if (strcmp ("-v", myargv[i]) == 0 || strcmp ("--version", myargv[i]) == 0)
	{
	  version();
	  exit (0);
	}
      else if (strcmp ("-p", myargv[i]) == 0)
	{
	  if (i<myargc-1) 
	    {
	      i++;
	      strncpy(prefix, (myargv[i]), BUFLEN);
	      prefix[BUFLEN-1] = '\0';
	    }
	  else
	    {
	      fprintf (stderr,
		       "interactive: no argument supplied for -p\n");
	    }
	}
      else if (strncmp ("--prefix=", myargv[i], 9) == 0)
	{
	  strncpy(prefix, myargv[i]+9, BUFLEN);
	  prefix[BUFLEN-1] = '\0';
	}

      else if (strcmp ("-l", myargv[i]) == 0)
	{
	  if (i<myargc-1) 
	    {
	      i++;
	      strncpy(levelsep, (myargv[i]), BUFLEN);
	      levelsep[BUFLEN-1] = '\0';
	    }
	  else
	    {
	      fprintf (stderr,
		       "interactive: no argument supplied for -l\n");
	    }
	}
      else if (strncmp ("--levelsep=", myargv[i], 11) == 0)
	{
	  strncpy(levelsep, myargv[i]+11, BUFLEN);
	  levelsep[BUFLEN-1] = '\0';
	}

      else if (strcmp ("-a", myargv[i]) == 0)
	{
	  if (i<myargc-1) 
	    {
	      i++;
	      strncpy(verb, (myargv[i]), BUFLEN);
	      verb[BUFLEN-1] = '\0';
	    }
	  else
	    {
	      fprintf (stderr,
		       "interactive: no argument supplied for -a\n");
	    }
	}
      else if (strncmp ("--action=", myargv[i], 9) == 0)
	{
	  strncpy(verb, myargv[i]+9, BUFLEN);
	  verb[BUFLEN-1] = '\0';
	}

      else if (strncmp ("-", myargv[i], 1) == 0 && strlen (myargv[i]) > 2)
	{
	  /* blindly expand grouped options to individual options */
	  int j = i + 1;
	  int k = 1;
	  while (k < strlen (myargv[i]))
	    {
	      int c = *(myargv[i]+k);
	      char *arg = malloc (3 * sizeof *arg);
	      *(arg) = '-';
	      *(arg+1) = c;
	      *(arg+2) = '\0';
	      insert_arg (arg, j);
	      /*dump_myargs();*/
	      j++;
	      k++;
	    }
	}

      else /* anything else is taken as a file */
	{
	  inputfile = fopen (myargv[i], "r");
	  if (inputfile==NULL)
	    {
	      fprintf (stderr,
		       "interactive: could not open file %s for reading\n", myargv[i]);
	      exit (1);
	    }
	  else
	    {
	      if (inputindex == ninputs)
		{
		  inputfiles = realloc (inputfiles, 2 * ninputs * sizeof *inputfiles);
		  ninputs *= 2;
		}
	      inputfiles[inputindex++] = inputfile;
	    }
	}

      i++;
    }

  if (inputindex == 0)
    {
      inputfiles[inputindex++] = stdin;
    }


  skipindex = 0;
  playindex = 0;
  strcpy(all, "");
  strcpy(search, "");
  strcpy(savesearch, "");
  state = 0;
  levelindex = 0;

  nskiphints=10;
  skipindex=0;
  skiphints = malloc (nskiphints * sizeof *skiphints);

  nplayhints=10;
  playindex=0;
  playhints = malloc (nplayhints * sizeof *playhints);

  tty_in = fopen ("/dev/tty", "r");
  if (tty_in==NULL)
    {
      fprintf (stderr,
	       "interactive: could not open file /dev/tty for reading\n");
      exit (1);
    }
  tty_out = fopen ("/dev/tty", "w");
  if (tty_out==NULL)
    {
      fprintf (stderr,
	       "interactive: could not open file /dev/tty for writing\n");
      exit (1);
    }

  for (i=0; i<inputindex; i++)
    {
      while (fgets(line, BUFLEN, inputfiles[i]) != NULL)
	{
	  process_line(line);
	}
      fclose (inputfiles[i]);
    }

  fclose (tty_in);
  fclose (tty_out);
  exit (exitstatus);
}


void 
process_line (char* line)
{
  char action[2];
  char response[BUFLEN];
  char str[BUFLEN];
  size_t i;
  size_t len;
  int levels;
  char *briefparts[256];
  char line2[BUFLEN];
  char *line3;

  if (state==0)
    {
      fprintf (tty_out,
	       "y: yes\nn: no\nY: yes to all\nN: no to all\n");
      fprintf (tty_out,
	       "u: up a level\nd: down a level\n/: search\n");
      state = 1;
    }

  // fprintf (tty_out, "Len: %d\n", strlen(line));
  line[strlen(line)-1] = '\0';
  strcpy (action, "");
  strcpy (response, "");

  // take the advice of the longest (most specific) hint

  len = 0;
  for (i=0; i<skipindex; i++)
    {
      if ((strstr(line, skiphints[i]) != NULL ) && (strlen(skiphints[i]) > len))
	{
	  strcpy(action, "n");
	  len = strlen(skiphints[i]);
	}
    }
  for (i=0; i<playindex; i++)
    {
      if ((strstr(line, playhints[i]) != NULL ) && (strlen(playhints[i]) > len))
	{
	  strcpy(action, "y");
	  break;
	}
    }

  if ((strcmp(action, "") == 0) && (strcmp(all, "") != 0))
    {
      strcpy(action, all);
    }

  if ((strcmp(action, "") == 0) && (strcmp(search, "") != 0))
    {
      // should use tolower here
      if (strstr(line, search) != NULL)
	{
	  strcpy(search, "");
	}
      else
	{
	  strcpy(action, "n");
	}
    }

  if (strcmp(action, "y") == 0)
    {
      printf("%s\n", line);
    }

  // if hints and "all" gave no instruction, ask the user

  else if (strcmp(action, "") == 0)
    {
      levels = 0;
      strncpy(line2, line, BUFLEN);
      line2[BUFLEN-1] = '\0';
      line3 = line2;
      // fprintf (tty_out, "Still alive...\n");

      // remove prefix, if found

      if (strncmp(line2, prefix, strlen(prefix)) == 0)
	{
	  line3 = line3 + strlen(prefix);
	}

      // split the line

      if ((briefparts[0] = strtok(line3, levelsep)) != NULL)
	{
	  levels = 1;
	  while (levels < 256)
	    {
	      if ((briefparts[levels] = strtok(NULL, levelsep))==NULL)
		break;
	      else
		levels++;
	    }
	}

      // fprintf (tty_out, "Still alive 2...\n");

      // bounds check levelindex

      if (levels + levelindex < 1)
	{
	  levelindex = 1 - levels;
	}
      if (levels == 0)
	{
	  levelindex = 0;
	}


      // loop while moving up and down in the hierarchy

      int cont = 1;
      while (cont==1)
	{
	  char sep[BUFLEN];

	  cont = 0;
	  strcpy (str, "");
	  strcpy (sep, "");
	  for (i=0; i<levels+levelindex; i++)
	    {
	      strcat(str, sep);
	      strcat(str, briefparts[i]);
	      strcpy(sep, levelsep);
	    }
	  fprintf(tty_out, "%s %s (ynYNud)? ", verb, str);
	  fgets(response, BUFLEN, tty_in);
	  response[strlen(response)-1] = '\0';

	  if (strcmp(response, "u")==0 || strcmp(response, "up")==0)
	    {
	      cont = 1;
	      levelindex -= 1;
	      if (levels + levelindex < 1)
		{
		  levelindex = 1 - levels;
		}
	    }
	  if (strcmp(response, "d")==0 || strcmp(response, "down")==0)
	    {
	      cont = 1;
	      levelindex += 1;
	      if (levelindex > 0)
		{
		  levelindex = 0;
		}
	    }
	}

      // finished moving up and down, so check for other responses

      if (strcmp(response, "Y")==0 || strcmp(response, "Yes")==0)
	{
	  strcpy(response, "y");
	  strcpy(all, "y");
	}
      else if (strcmp(response, "N")==0 || strcmp(response, "No")==0)
	{
	  strcpy(response, "n");
	  strcpy(all, "n");
	}
      else if (strncmp(response, "/", 1)==0)
	{
	  char *s2 = response;
	  s2++;
	  strncpy(search, s2, BUFLEN);
	  search[BUFLEN-1] = '\0';
	  if (strlen(search) == 0)
	    {
	      strcpy(search, savesearch);
	    }
	  if (strlen(search) == 0)
	    {
	      fprintf(tty_out, "Search string: ");
	      fgets(search, BUFLEN, tty_in);
	      search[strlen(search)-1] = '\0';
	    }
	  strcpy(savesearch, search);
	  strcpy(response, "n");
	}

      if (strcmp(response, "y")==0 || strcmp(response, "yes")==0 || strlen(response)==0)
	{
	  printf ("%s\n", line);
	  if (levelindex < 0)
	    {
	      // playhints
	      if (playindex == nplayhints)
		{
		  playhints = realloc (2 * nplayhints * sizeof *playhints);
		  nplayhints *= 2;
		}
	      playhints[playindex] = malloc((strlen(str) + 1) * sizeof *str);
	      strcpy(playhints[playindex], str);
	      playindex++;
	    }
	}
      else if (strcmp(response, "n")==0 || strcmp(response, "no")==0 )
	{
	  if (levelindex < 0)
	    {
	      // skiphints
	      if (skipindex == nskiphints)
		{
		  skiphints = realloc (2 * nskiphints * sizeof *skiphints);
		  nskiphints *= 2;
		}
	      skiphints[skipindex] = malloc((strlen(str) + 1) * sizeof *str);
	      strcpy(skiphints[skipindex], str);
	      skipindex++;
	    }
	}
    }
}


void
usage ()
{
  printf ("Usage: %s [OPTION]... [FILE]...\n", me);
  fputs ("Processes a list interactively.\n", stdout);
  fputs ("Options:\n", stdout);
  fputs ("  --prefix=, -p  : set prefix to remove from each line\n", stdout);
  fputs ("  --levelsep=, -l: set level separator\n", stdout);
  fputs ("  --action=, -a  : set descriptive verb\n", stdout);
  fputs ("  --help, -h     : show help\n", stdout);
  fputs ("  --version, -v  : show version information", stdout);
}


void
version()
{
  fputs ("\
interactive 1.0\n\
\n\
Copyright (C) 2006 Stephen Jungels\n\
  This is free software; see the source for copying conditions.  There is NO\n\
  warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n",
  stdout);
}

