/* Very Intelligent filter */
/* © 1990 Pagani Massimiliano */

/* Per compilare col Lattice C:		*/
/*	lc -L -cfist -d0 vif.c		*/

/* Questo filtro dovrebbe fare le seguenti cose:			*/
/* - Rimuovere i caratteri vuoti (spazi, tab) alla fine delle linee	*/
/* - Sostituire spazi consecutivi con caratteri tab rispettando le di-	*/
/*   mensioni								*/
/* - Rimuovere gli spazi coperti da un tab.				*/
/* - Rimuovere i newline alla fine del file.				*/
/* - i ^Z, ^M								*/
/* opzionalmente:							*/
/* - rimuovere le sequenze di escape.					*/


#include <stdio.h>
#include <stdlib.h>
#include <error.h>
#include <ctype.h>
#include <dos.h>
#include <fcntl.h>

/* Stato dell'automa */
typedef enum {RESET, SPAZIO, TAB, NEWLINE} STATO;

#ifdef LATTICE
/* Gli errori vengono trovati controllando la errno */
/* prototipi */
  void ShowHelp(void);
  BOOL copyfile( unsigned char *, unsigned char * );
  BOOL OutChars( int, unsigned char, FILE * );
  void ExaustEscape( unsigned char, FILE *, FILE * );
  STATO DoSpazio( STATO, int *, int *, int * );
  void DoTab( STATO, int *, int *, int * );
  void DoNewLines( STATO, int *, int *, int *, int * );
  void DoFilter( FILE *, FILE *, BOOL, BOOL, BOOL );
  int main( int, char ** );
#else
#  define FALSE	0L
#  define TRUE	(!FALSE)
typedef int BOOL;
#endif

#undef DEBUG


#define TABSIZE	8
#define ISTAB(col)	(!(((col)-1)-TABSIZE*(((col)-1)/TABSIZE)))
		/* è una macro che ritorna TRUE quando la colonna attuale è */
		/* una tabulazione, FALSE altrimenti */
#define NEXTTAB(col)	((col)+TABSIZE-(((col)-1)%TABSIZE))
		/* ritorna la prossima possizione di tabulazione */

/* Variabili importate */
extern int errno;


void
ShowHelp()
{
  puts("converts spaces into tabs,");
  puts("removes useless spaces, tabs, control chars and");
  puts("	escape sequences");
  puts("Usage:");
  puts("  vif [s] [<file> | (<filein> <fileout>)]");
  puts("[s] any combination of switches precedeed by '-':");
  puts("  e	do not strip escape sequences.");
  puts("  q	no statistic at the end.");
  puts("  p	progressing display.");
  puts("If no files specified stdin and stdout will be used.");
  puts("If only one file, the filtering will be done on a");
  puts("temporary file (in T:) then the result will be copied");
  puts("onto the original file (WARNING).");
  puts("If two files specified filtering will be applied on");
  puts("<filein> and result in <fileout>.");
}

BOOL
copyfile( name_in, name_out )
unsigned char * name_in;
unsigned char * name_out;
{
int fd_in, fd_out;
char buffer[ BUFSIZ ];
int count = -1L;

  fd_in = open( name_in, O_RDONLY );
  fd_out = open( name_out, O_CREAT | O_WRONLY | O_TRUNC, 0 );
  if( fd_in && fd_out ){
    while( (count = read( fd_in, buffer, BUFSIZ )) > 0){
      if( write( fd_out, buffer, count ) != count ){
	printf("problema in scrittura\n");
	break;
      }
    }
    close( fd_in );
    close( fd_out );
  }
# ifdef DEBUG
    printf("errno = %d\n", errno );
# endif
  return( (BOOL) errno );
}

BOOL
OutChars( n, c, out )
int n;
unsigned char c;
FILE *out;
{

#ifdef DEBUG
  printf("scrivo %d ", n );
  switch( c ){
    case ' ':
      printf("spazi\n");
      break;
    case '\n':
      printf("nl\n");
      break;
    case '\t':
      printf("tab\n");
      break;
  }
#endif

  for( ; n > 0; n-- ){
    putc( c, out );
  }
  return( (BOOL) !errno );	/* (errno == 0) -> ok */
}

/* Esaurisce la sequenza di escape */

void
ExaustEscape( c, in, out )
unsigned char c;
FILE *in;
FILE *out;
{
enum { ESC1B, ESC9B, NUMERO, FINE } stato;

  if( c == 0x1B ) stato = ESC1B;
  else stato = ESC9B;
  while( stato != FINE ){
    c = getc( in );
    if( out ) putc( c, out );
    switch( stato ){
      case ESC1B:
	switch( c ){
	  case '[':
	    stato = ESC9B;
	    break;
	  default:
	    stato = FINE;
	}
	break;
      case ESC9B:
	if( isdigit( c ) || (c==';') ){
	  stato = NUMERO;
	}
	else stato = FINE;
	break;
      case NUMERO:
	if( !isdigit( c ) && (c != ';') ){
	  stato = FINE;
	}
	break;
    }
  }
}

/* risolve il problema quando il carattere letto è uno spazio */

STATO
DoSpazio( stato, col, numSpazi, numTab )
STATO stato;
int *col, *numSpazi, *numTab;
{

  (*col)++;
  switch( stato ){
    case RESET:
    case SPAZIO:
      *numSpazi = (stato == RESET) ? 1 : *numSpazi+1;
      if( ISTAB( *col ) ){
	*numSpazi = 0;
	(*numTab)++;
	stato = TAB;
      }
      else stato = SPAZIO;
      break;
    case TAB:
	/* ci sono dei tab, quindi uno spazio */
      (*numSpazi)++;
      stato = SPAZIO;
      break;
    case NEWLINE:
      *numSpazi = 1;
      stato = SPAZIO;
      break;
  }
  return( stato );
}

/* risolve il problema quando il carattere letto è un tab */

void
DoTab( stato, col, numSpazi, numTab )
STATO stato;
int *col;
int *numSpazi, *numTab;
{

  *col = NEXTTAB( *col );
  switch( stato ){
    case NEWLINE:
    case RESET:
      *numTab = 1;
      break;
    case SPAZIO:
	/* un certo numero di spazi, quindi un tab */
	/* al massimo una tabulazione, quindi il tab copre gli spazi */
      (*numTab)++;
      *numSpazi = 0;
      break;
    case TAB:
      (*numTab)++;
      break;
  }
}

void
DoNewLines( stato, col, numSpazi, numTab, numNewLine )
STATO stato;
int *col;
int *numSpazi, *numTab, *numNewLine;
{

  *col = 1;
  switch( stato ){
    case RESET:
      *numNewLine = 1;
      break;
    case SPAZIO:
    case TAB:
      /* abbiamo dei vuoti seguiti da un newLine */
      *numTab = *numSpazi = 0;
    case NEWLINE:
      (*numNewLine)++;
      break;
  }
}

void
DoFilter( in, out, noStat, stripESC, progress )
FILE *in, *out;
BOOL noStat, stripESC;	/* flags */
BOOL progress;
{
STATO stato;		/* stato dell'automa */
int c;			/* ultimo carattere letto */
int char_in = 0L;	/* # di caratteri letto */
int char_out = 0L;	/* # caratteri scritti */
BOOL lineaVuota;	/* la linea che stiamo leggendo è vuota ? */
int numSpazi, numTab, numNewLine;
			/* # spazi, tab e nl consecutivi */
int col;		/* colonna attuale */
register int linea;	/* # di linea attuale */
char count = 0;		/* contatore simbolini */
register int tlinea=0;

#ifdef DEBUG
  printf("DoFilter: inizio\n");
#endif

  col = linea = 1L;
  numSpazi = numTab = numNewLine = 0L;
  lineaVuota = TRUE;
  stato = RESET;
  do {
    c = getc( in );
    char_in++;

#ifdef DEBUG
    printf("%d %d -> letto %2x", col, linea, c );
    if( c > 32 ) printf("\t%c", c );
    putchar( '\n' );
#endif
    if( progress && ((linea>>4) != tlinea) ){
      tlinea = linea>>4;
      putchar( '#' );
      if( ++count > 40 ){
	count = 0;
	putchar( '\n' );
      }
      fflush( stdout );
    }
    switch( c ){
      case ' ':
	stato = DoSpazio( stato, &col, &numSpazi, &numTab );
	break;
      case '\t':
	DoTab( stato, &col, &numSpazi, &numTab );
	stato = TAB;
	break;
      case '\n':
	DoNewLines( stato, &col, &numSpazi, &numTab, &numNewLine );
	stato = NEWLINE;
	linea++;
	lineaVuota = TRUE;
	break;
      case 0xD:
      case 0x1A:
	/* ignoriamo i CR e gli EOF dei baracchi */
	break;
      case 0xC: 	/* Form Feed */
	putc( c, out );
	char_out++;
	col = 1;
	lineaVuota = TRUE;
	break;
      case 0x1B:	/* Escape sequence */
      case 0x9B:
	ExaustEscape( (unsigned char) c, in, (stripESC ? NULL : out) );
	break;
      case EOF:
	break;
      default:
	lineaVuota = FALSE;
	col++;
	char_out++;
	if( numNewLine > 0 ){
	  OutChars( numNewLine, '\n', out );
	  char_out += numNewLine;
	  numNewLine = 0L;
	}
	if( numTab > 0 ){
	  OutChars( numTab, '\t', out );
	  char_out += numTab;
	  numTab = 0L;
	}
	if( numSpazi > 0 ){
	  OutChars( numSpazi, ' ', out );
	  char_out += numSpazi;
	  numSpazi = 0L;
	}
	putc( c, out );
	stato = RESET;
	break;
    }
  } while( (c != EOF) && (errno==0) );
  if( errno ){
    perror("vif");
  }
  else {
    if( progress ) putchar( '\n' );
    if( !noStat ){
      printf("processate %ld linee\n", linea );
      printf("letti   %ld caratteri\n", --char_in );
      printf("scritti %ld caratteri\n", char_out );
      if( char_out > 0 ){
	printf("Compressed %ld%%\n", 100*(char_in - char_out)/char_out );
      }
    }
  }
}

int
main( argc, argv )
int argc;
char **argv;
{
int ind;
char *copt;	/* puntatore opzione corrente */
FILE *in;
FILE *out;
BOOL oneFile = FALSE;	/* lavora su un file temporaneo */
BOOL noStat = FALSE;	/* nessuna statistica alla fine */
BOOL stripESC = TRUE;	/* toglie gli escape */
unsigned char tmpname[ FMSIZE ];	/* nome file temporaneo */
int count;		/* # del file in area temporanea */
BOOL done = FALSE;	/* Finito */
BOOL progress = FALSE;	/* indica il progredire */

  printf("VIF: very intelligent filter\n");
  printf(" © 1990 Pagani Massimiliano\n");
  printf("  ShareWare - see doc file\n");
  printf("----------------------------\n");
  if( (argc == 2) && (*argv[1] == '?') ){
    ShowHelp();
    exit( 0L );
  }
  if( (argc > 1) && (*argv[1] == '-')){
	/* sono state specificate delle opzioni */
    for( copt=argv[1]+1; *copt; copt++ ){
      switch( *copt ){
	case 'e':	/* conserviamo le seq. di escape */
	  stripESC = FALSE;
	  break;
	case 'q':	/* quiet */
	  noStat = TRUE;
	  break;
	case 'p':	/* indica il progredire */
	  progress = TRUE;
	  break;
	default:
	  printf("Ignoro -%c in quanto non valido. \n", *copt );
      }
    }
    ind = 2;
  }
  else {
    ind = 1;
  }
  if( ind == argc ){
    progress = FALSE;	/* altrimenti rompiamo le scatole */
    noStat = TRUE;	/* idem con patate */
    in = stdin;
    out = stdout;
#   ifdef DEBUG
      printf("Applico il filtro sullo stdin, output su stdout\n");
#   endif
  }
  else if( (ind+1) == argc ){
#   ifdef DEBUG
      printf("Applico il filtro su %s out in temp\n", argv[ind]);
#   endif
    /* c'è un solo file */
    oneFile = TRUE;
    in = fopen( argv[ind], "r" );
    for( count=0; !done; count++ ){
      sprintf( tmpname, "T:viftemp%d", count );
      if( access( tmpname, 0 ) == -1 ){
	/* Un errore o semplicemente non c'è */
	if( errno != ENOENT ){
	  out = NULL;
	  perror( tmpname );
	}
	else {
	  out = fopen( tmpname, "w" );
	  errno = 0L;
	}
	done = TRUE;
      }
    }
  }
  else {
    in = fopen( argv[ind], "r" );
    out = fopen( argv[ind+1], "w" );
#ifdef DEBUG
    printf("Applico il filtro sul file \"%s\" output su \"%s\"\n", argv[1], argv[ind+1] );
#endif
  }
  if( !in ){
    fprintf( stderr, "non riesco ad aprire il file %s in lettura\n", argv[ind] );
    exit( 10L );
  }
  if( !out ){
    fprintf( stderr, "non riesco ad aprire il file %s in scrittura\n", argv[ind+1] );
    exit( 11L );
  }
  DoFilter( in, out, noStat, stripESC, progress );
  if( oneFile && (errno==0) ){
    fclose( in );
    fclose( out );
    if( !copyfile( tmpname, argv[ ind ]) ){
      remove( tmpname );
    }
    else {
      puts("problemi nel copiare il file temporaneo");
      puts("recupera il contenuto del file in:");
      puts( tmpname );
    }
  }

  return( errno );
}
