/* $Revision Header *** Header built automatically - do not edit! ***********
 *
 *	(C) Copyright 1991 by Metalworx
 *
 *	Name .....: wfile.c
 *	Created ..: Tue 26-Nov-91 18:46
 *	Revision .: 3
 *
 *	Date		Author		Comment
 *	=========	========	====================
 *	12-Aug-92	Mtwx		bug in memory allocation on MSDOS &
 *					OS/2 based systems fixed
 *	20-Apr-92	Mtwx		finished -s option
 *	12-Mar-92	Mtwx		built in new option -c
 *	12-Jan-91	Mtwx		Created this file!
 *
 * $Revision Header ********************************************************/
#define REVISION 3

/***************************************************************************
* WFile.c : Work file - work (do things) on (ASCII-) files                 *
*									   *
*      - add or remove CRs (0x0d)                                          *
*      - expand tabs to multiple spaces or vice versa			   *
*      - change foreign symbols and special characters			   *
*      - concatenate text lines 					   *
*									   *
***************************************************************************/

/* -------------------------- Includes ---------------------------------- */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#ifdef AMIGA
#include <exec/types.h>
#include <stddef.h>
#endif
#if defined(OS2)
#include <os2def.h>
#endif

#if __MSDOS__ || OS2
#ifdef __TURBOC__
#include <dir.h>
#endif
#endif

#ifndef UNIX
#if defined(AMIGA)
#include <ansi.h>		       /* color sequences of ANSI.SYS */
#elif defined (__MSDOS__) || defined(OS2)
#include "ansi.h"
#endif
#endif

#include "wfile.h"

/* -------------------------- Defines ----------------------------------- */

#ifdef UNIX
#define TRUE 1
#define FALSE 0
typedef short BOOL;

#endif

#if defined(__MSDOS__)
#define TRUE 1
#define FALSE 0
typedef short BOOL;

#endif

/* -------------------------- global variables -------------------------- */

unsigned char ch_src_sym[MAXCHANGE];   /* Change table for foreign symbols */
unsigned char ch_trg_sym[MAXCHANGE];
unsigned char final_chr;	       /* takes desired final char if specified */
char	 *templ_argv[MAXOPTIONS];      /* Array of pointers for templated
					  argvs */
int	  change_count = 0;	       /* counter for changes to be done */

#if AMIGA
#define COLOR_ON WB_COLOR2
#define COLOR_OFF WB_COLOR1
#define TITLE_COLOR WB_COLOR1
#define HELP_COLOR WB_COLOR1
#define OPTION_COLOR WB_COLOR2

#elif defined(__MSDOS__) || defined(OS2)
#define COLOR_ON ANSIHICYAN
#define COLOR_OFF ANSINORMAL
#define TITLE_COLOR ANSIHIYELLOW_ON_RED
#define HELP_COLOR ANSIHIRED
#define OPTION_COLOR ANSIHIGREEN

#elif UNIX
#define COLOR_ON
#define COLOR_OFF
#define TITLE_COLOR
#define HELP_COLOR
#define OPTION_COLOR

#else
#error Zielsystem nicht angegeben (AMIGA, __MSDOS__, UNIX, o.a)
#endif

/* ------------------------------- external references ------------------ */

extern int Revision;
extern char Version[];
extern char RDate[];

/* ------------------------------- prototypes --------------------------- */

void	  usage(char *), parse_change(char *, int);

#if ! __SASC && ! __TURBOC__ && !defined(OS2)
char	 *strupr();

#endif

unsigned int workfile(register unsigned char *, register unsigned char *,
				long, int, int, int);
char	 *basename(char *);

extern int parse_profile(char *), unix2msdos(char *), msdos2unix(char *),
	  amiga2msdos(char *), msdos2amiga(char *), amiga2unix(char *),
	  unix2amiga(char *), amiga2os2(char *), os22amiga(char *);
extern void whelp(char *);
extern unsigned int scan(register char *, long, int, int);

/* -------------------------- main control loop ------------------------- */

int	  main(int argc, char *argv[])
{
  int	    i, j, num_options = 0, Options = 0, tabsize, t_argc, concat_margin;
  int	    offs_concat_margin;        /* byte offset of the concat_margin in the -c
					  argument, can be 2 or 3 */
  unsigned int bufsize;
  long	    len, mallocsize;
  unsigned char *rbuf, *wbuf, backup_filename[100], pro_filename[100],
	   *argument[MAXOPTIONS];
#if defined(__MSDOS__) || defined(OS2)
  unsigned char *temp;	/* only used when creating backup name */
#endif
  FILE	   *file;

  printf("\n%s%s %s/%d (%s) - released %s  (C) 1991,1992 Metalworx%s\n",
	 TITLE_COLOR, basename(argv[0]), Version, Revision, SYSTEM,
	  RDate,COLOR_OFF);

/* evaluate command line */
  if (argc < 2)
  {
    usage(basename(argv[0]));
    exit(1);
  }

  if (!strcmp(argv[1], "-?"))
  {
    whelp(argv[0]);
    exit(1);
  }

  if (argc > 2)
  {
    for (i = 2; i < argc; i++, num_options++)
    {
      argument[num_options] = (char *) malloc(strlen(argv[i]) + 1);
      strcpy(argument[num_options], argv[i]);
    }
    for (i = 0; i < num_options; i++)
    {
      if (toupper(argument[i][1]) == 'F')       /* read commands from profile */
      {
	if (strlen(&argument[i][2]))   /* profile name given */
	  strcpy(pro_filename, &argument[i][2]);
	else
	  strcpy(pro_filename, "wfile.pro");    /* default profile name */
	t_argc = parse_profile(pro_filename);
	for (j = 0; j < t_argc; j++)
	  argument[num_options++] = templ_argv[j];
	continue;
      }

      if (!strcmp(strupr(&argument[i][1]), "AI"))
      {
	t_argc = amiga2msdos(argument[i]);
	for (j = 0; j < t_argc; j++)
	  argument[num_options++] = templ_argv[j];
	continue;
      }

      if (!strcmp(strupr(&argument[i][1]), "IA"))
      {
	t_argc = msdos2amiga(argument[i]);
	for (j = 0; j < t_argc; j++)
	  argument[num_options++] = templ_argv[j];
	continue;
      }

      if (!strcmp(strupr(&argument[i][1]), "AO"))
      {
	t_argc = amiga2os2(argument[i]);
	for (j = 0; j < t_argc; j++)
	  argument[num_options++] = templ_argv[j];
	continue;
      }

      if (!strcmp(strupr(&argument[i][1]), "OA"))
      {
	t_argc = os22amiga(argument[i]);
	for (j = 0; j < t_argc; j++)
	  argument[num_options++] = templ_argv[j];
	continue;
      }

      if (!strcmp(strupr(&argument[i][1]), "AU"))
      {
	t_argc = amiga2unix(argument[i]);
	for (j = 0; j < t_argc; j++)
	  argument[num_options++] = templ_argv[j];
	continue;
      }

      if (!strcmp(strupr(&argument[i][1]), "UA"))
      {
	t_argc = unix2amiga(argument[i]);
	for (j = 0; j < t_argc; j++)
	  argument[num_options++] = templ_argv[j];
	continue;
      }

      if (!strcmp(strupr(&argument[i][1]), "UI"))
      {
	t_argc = unix2msdos(argument[i]);
	for (j = 0; j < t_argc; j++)
	  argument[num_options++] = templ_argv[j];
	continue;
      }

      if (!strcmp(strupr(&argument[i][1]), "IU"))
      {
	t_argc = msdos2unix(argument[i]);
	for (j = 0; j < t_argc; j++)
	  argument[num_options++] = templ_argv[j];
	continue;
      }

/* add final character */
      if (argument[i][0] == '+' && isdigit(argument[i][1]))
      {
	Options |= ADD_FINAL_CHR;
	final_chr = atoi(&argument[i][1]);
      }

/* change (foreign) characters ? */
      if (strchr(argument[i], '='))
      {
	parse_change(argument[i], change_count);
	change_count++;
      }

      if (!strcmp(strupr(&argument[i][1]), "CR"))
      {
	switch (argument[i][0])
	{
	  case '+':
	    Options |= ADDCR;
	    break;
	  case '-':
	    Options |= REMCR;
	    break;
	  default:
	    Options |= CRMODE;
	    printf("Unknown \"CR\" mode! %s assumed.\n", CRTEXT);
	    break;
	}
      }

      if (toupper(argument[i][1]) == 'T')
      {
	tabsize = atoi(&argument[i][2]);
	if (tabsize == 0)
	{
	  tabsize = TABSIZE;
	  printf("Tabsize %d assumed.\n", tabsize);
	}
	if (tabsize == 1)
	{
	  puts("Tabsize of 1 doesn't make sense! - Try something from 2 upwards.");
	  exit(1);
	}
	switch (argument[i][0])
	{
	  case '+':
	    Options |= SHRSPACE;
	    break;
	  case '-':
	    Options |= EXPTABS;
	    break;
	  default:
	    Options |= TABMODE;
	    printf("Unknown \"TAB\" mode! %s assumed.\n", TABTEXT);
	    break;
	}
      }

      if (argument[i][1] == '0')
	Options |= REAL_NULL;	       /* really replace something against a NULL
					  char */

      if (toupper(argument[i][1]) == 'B')
	Options |= NO_BACKUP;

      if (toupper(argument[i][1]) == 'S')
      {
	bufsize = atoi(&argument[i][2]);
	if (bufsize <= 0)
	{
	  printf("Size of memory buffer= %d Kbytes\nDoesn't make sense, don't you think so ?\n",
		 bufsize);
	  exit(1);
	}
	else
	{
#ifdef __MSDOS__
	  if (bufsize > 65535)
	  {
	    puts("ERROR: Bufsize > 64 KByte exceeds malloc()-limits!");
	    exit(EXIT_FAILURE);
	  }
#endif
	  Options |= BUFSIZE_SPECIFIED;
	}
      }

      if (toupper(argument[i][1]) == 'C' && toupper(argument[i][2]) != 'R')
      {
	Options |= CONCAT_LINES;
	if (toupper(argument[i][2]) == 'K')
	{
	  Options |= PRESERVE_PARAGRAPHS;
	  offs_concat_margin = 3;
	}
	else
	  offs_concat_margin = 2;
	if (isdigit(argument[i][offs_concat_margin]))
	  concat_margin = atoi(&argument[i][offs_concat_margin]);
	else
	{
	  concat_margin = CONCAT_MARGIN;
	  printf("Concat margin not specified. %d assumed!\n", CONCAT_MARGIN);
	}
      }

      if(toupper(argument[i][1]=='L'))
	Options |= DEL_EMPTY_LINES;
    }
  }
  if (change_count)
    Options |= CHANGE_FOREIGN;

/* now check presence of file (argv[1]) and get length */

  if ((file = fopen(argv[1], "rb")) == NULL)
  {				       /* read in binary mode to supress automatic
					  shrinking of newlines on MSDOS based systems
				       */
    perror(argv[1]);
    exit(1);
  }
  fseek(file, 0, 2);                   /* set to EOF - 0 Bytes */
  len = ftell(file);
#ifdef __MSDOS__
  if (len > 60000)
  {
    char      ask;

    printf("\nFilelength (%ld bytes) exceeds MSDOS' fread()-capability!\n",
	   len);
    puts("You better quit here and a) chop your files into smaller pieces");
    puts("                      or b) wait for the next major WFile release");
    printf("Enter 'l' to leave or any other key to continue... ");
    ask = getchar();
    if (ask == 'l')
      exit(EXIT_FAILURE);
  }
#endif
  if (Options & BUFSIZE_SPECIFIED && len > bufsize)
  {
    printf("ERROR: Filelength (%ld) > specified buffer size (%d)!\n", len,
	   bufsize);
    exit(EXIT_FAILURE);
  }
  printf("%s: %ld bytes to read, ", argv[1], len);
  fflush(stdout);
  if (len <= 0)
  {
    puts("File read error!");
    exit(1);
  }

/* allocate memory buffers */

  rbuf = (char *) malloc((size_t) len);
  if (rbuf == NULL)
  {
    printf("Couldn't get %ld bytes of memory for read buffer!!\n", len);
    exit(1);
  }

/* read file into buffer */

  fseek(file, 0, 0);                   /* set to start of file */
  if (fread(rbuf, len, 1, file) != 1)
  {
    perror(argv[1]);
    puts("read failed!!");
    exit(1);
  }
  fclose(file);

  /* get size of write buffer */
  if (Options & BUFSIZE_SPECIFIED)
  {
    mallocsize = scan(rbuf, len, Options, tabsize);
    if (mallocsize > bufsize)
    {
      printf("\nWARNING: Scanning indicates that you probably need more memory\n");
      printf("than you specified.\n");
      printf("Do you want to continue (y/n) ");
      if (getchar() != 'y')
	exit(1);
    }
    else
      mallocsize = bufsize;
  }
  else
    mallocsize = scan(rbuf, len, Options, tabsize);
  wbuf = (char *) malloc((size_t) mallocsize);
  if (wbuf == NULL)
  {
    printf("Couldn't get %ld bytes of memory for write buffer!!\n",
	   mallocsize);
    exit(1);
  }
  printf("%ld bytes allocated, ", mallocsize + len);
  fflush(stdout);

/* create backup of original file */

  if (!(Options & NO_BACKUP))
  {
    strcpy(backup_filename, argv[1]);
#ifdef AMIGA
    strcat(backup_filename, ".bak");
#endif
#ifdef UNIX
    strcat(backup_filename, ".bak");
#endif
#if defined (__MSDOS__) || defined (OS2)
    temp = strchr(backup_filename, '.');
    if (temp)
      strcpy(temp, ".bak");
    else
      strcat(backup_filename, ".bak");
#endif
    if ((file = fopen(backup_filename, "wb")) == NULL)
    {
      perror(argv[1]);
      puts("opening of backup file failed!");
      exit(1);
    }
    else
    {
      if (fwrite(rbuf, len, 1, file) != 1)
      {
	perror(argv[1]);
	puts("write to backup file failed!");
	exit(1);
      }
      fclose(file);
    }
  }

/* start up user desired routines */

  len = (long) workfile(rbuf, wbuf, len, Options, tabsize, concat_margin);

/* write back file with new contents */

  if ((file = fopen(argv[1], "wb")) == NULL)
  {
    perror(argv[1]);
    puts("open to write failed!");
    exit(1);
  }
  fflush(stdout);
  if (fwrite(wbuf, len, 1, file) != 1)
  {
    perror(argv[1]);
    puts("write failed!");
    exit(1);
  }
  printf("\n%ld bytes written\n", len);
  fclose(file);

/* clean up and exit */

  free(rbuf);
  free(wbuf);
  printf("\n");
  exit(0);
}

/* -------------------------- referenced functions ---------------------- */

void	  parse_change(char *param, int num)
{
  char	   *temp;

  if (param[1] == '\'' && param[3] == '\'')     /* quoted character */
    ch_src_sym[num] = param[2];
  else				       /* decimal value */
    ch_src_sym[num] = atoi(&param[1]);
  temp = strchr(param, '=');
  switch (temp[1])
  {
    case '\'':
      ch_trg_sym[num] = temp[2];
      break;
    default:
      ch_trg_sym[num] = atoi(&temp[1]);
      break;
  }
}

void	  usage(name)
char	 *name;
{
  char explanation[]=
    "\nValues in "COLOR_ON"this color"COLOR_OFF" must be entered as"
    "(decimal) ASCII values.\n"
    "Parameters in <> brackets have to be specified.\n"
    "Parameters in [] brackets are optional.\n"
    "Parameters seperated by | are mutual exclusive.\n"
    "Parameters may be either upper or lower case.\n\n"
    "Parameters " OPTION_COLOR "-L" COLOR_OFF " and " OPTION_COLOR "-C"
    COLOR_OFF " should only be used with files that contain single\n"
    "line-feed characters. Strip CR codes first using -CR,"
    " use -L oder -C\n"
    "and add CR codes using -CR!\n\n"
    "Type " HELP_COLOR "WFILE -?" COLOR_OFF " for detailed help!\n";

  printf("Usage: %s < file | -? >  [<+|->CR] [<+|->T[size]] [-%sddd%s=%sddd%s [...] [-0]]\n",
	 name, COLOR_ON, COLOR_OFF, COLOR_ON, COLOR_OFF);
  printf("%*s [-b] [-f[file]] [-AI] [-IA] [-AU] [-UA] [-AO] [-OA] [-UI] [-IU]\n",
	 strlen(name) + 7, " ");
  printf("%*s [+%sddd%s] [-s<bufsize>] [-c[[k]%sddd%s]] [-L]\n", strlen(name) + 7,
	 " ", COLOR_ON, COLOR_OFF, COLOR_ON, COLOR_OFF);
  printf(explanation);
}

#if ! __SASC && ! __TURBOC__ && !defined(OS2)
char	 *strupr(s)
char	 *s;
{
  register int i;
  char	   *us;

  if (strlen(s))
  {
    us = (char *) malloc(strlen(s) + 1);
    if (us)
      for (i = 0; i <= strlen(s); i++)
	us[i] = toupper(s[i]);
    return us;
  }
  else
    return NULL;
}

#endif


/*##########################################################################
# workfile(): main processing routine, translates input to output file and #
#	      does all desired actions					   #
#									   #
#   inputs: char *buf - start of read buffer				   #
#	    char *ptr - start of write buffer				   #
#	    long len - count of read bytes				   #
#	    int Options - specified options (all ORed (|) into this int)   #
#	    int tabsize - size of \t in bytes				   #
#	    int concat_margin - itself					   #
#   return: unsigned int j - length of processed write buffer, actually    #
#			    the length of the file to be written	   #
##########################################################################*/
unsigned int workfile(register unsigned char *buf,
		      register unsigned char *ptr, long len, int Options,
		      int tabsize, int concat_margin)
{
  register unsigned int i, j, k, l;
  unsigned int nl_pos_i = -1, nl_pos_j = -1;

  /*  holds buffer position of latest newline */
  BOOL	    shr_possible, quoted = FALSE;

  for (i = j = 0; i < len; i++)
  {
    switch (buf[i])
    {
      case 0x0d:
	if (Options & REMCR)
	  continue;
	else
	  ptr[j++] = 0x0d;
	break;
      case 0x0a:
	{			       /* make it a block, to create variables on the stack to make the
					  whole thing a bit more transparent */
	  BOOL	    concat, margin_reached, preserve, next_char_is_newline;

	  concat = Options & CONCAT_LINES ? TRUE : FALSE;
	  margin_reached = i - nl_pos_i <= concat_margin ? TRUE : FALSE;
	  preserve = Options & PRESERVE_PARAGRAPHS ? TRUE : FALSE;
	  next_char_is_newline = buf[i + 1] == '\n' ? TRUE : FALSE;

	  if ((concat &&
	       (margin_reached || (preserve && next_char_is_newline)))
	      || !concat)
	    /* do only until concat_margin has been reached, if paragraphs
	       should be preserved or no concat should be done */
	  {
	    if(Options & DEL_EMPTY_LINES)
	if(ptr[j-1]=='\n')
    continue;

	    if (Options & ADDCR)
	      ptr[j++] = 0x0d;
	    ptr[j++] = 0x0a;
	    nl_pos_j = j - 1;	       /* save pos of newline in write buffer */
	  }
	  else
	    ptr[j++] = ' ';            /* insert space to keep words apart */
	  nl_pos_i = i; 	       /* save pos of newline in read buffer  */
	}
	break;

      case '\t':
	if ((Options & EXPTABS) && (quoted == FALSE))
	{
	  l = j;		       /* use origin position of write buffer */
	  for (k = l - nl_pos_j - 1;
	       k < (((l - nl_pos_j - 1) / tabsize + 1) * tabsize);
	       k++)
	    ptr[j++] = ' ';
	}
	else
	  ptr[j++] = '\t';
	break;
      case ' ':
	if ((Options & SHRSPACE) && (quoted == FALSE))
	{
	  shr_possible = TRUE;
	  for (k = i - nl_pos_i - 1, l = 0;
	       k < (((i - nl_pos_i - 1) / tabsize + 1) * tabsize);
	       k++, l++)
	  {
	    if (buf[i + l] != ' ')
	      shr_possible = FALSE;
	  }
	  if (l == 1)
	    shr_possible = FALSE;      /* doesn't make sense to shrink one
					  SPACE into one TAB */
	  if (shr_possible == TRUE)
	  {
	    ptr[j++] = '\t';
	    i += l - 1;
	  }
	  else
	    ptr[j++] = ' ';
	}
	else
	  ptr[j++] = ' ';
	break;
      case '\"':
	if (buf[i - 1] != '\\')        /* not quoted itself */
	  quoted = 1 - quoted;	       /* negate quotation status */
	ptr[j++] = '\"';
	break;
      default:
	if (Options & CHANGE_FOREIGN)
	{
	  l = FALSE;		       /* set found indicator to NOT FOUND */
	  for (k = 0; k < change_count; k++)
	    if (buf[i] == ch_src_sym[k])
	    {
	      l = TRUE; 	       /* set found indicator to FOUND */
	      if (ch_trg_sym[k] != '\0')        /* anything else than a NULL char ? */
		ptr[j++] = ch_trg_sym[k];
	      else if (Options & REAL_NULL)     /* should the NULL really be
						   inserted ? */
		ptr[j++] = ch_trg_sym[k];
	    }
	  if (l == FALSE)              /* not a symbol that is to be changed */
	    ptr[j++] = buf[i];
	}
	else
	  ptr[j++] = buf[i];
	break;
    }
  }
  if (Options & ADD_FINAL_CHR)
    ptr[j++] = final_chr;
  return j;
}

char	 *basename(char *path)
{
#ifdef __TURBOC__
  char	    drive[MAXDRIVE];
  char	    dir[MAXDIR];
  char	    file[MAXFILE];
  char	    ext[MAXEXT];

#else
  char	    file[80];

#endif
  char	   *rstring;

#ifdef __TURBOC__
  fnsplit(path, drive, dir, file, ext);
#else
  strcpy(file, path);
#endif
  rstring = malloc(strlen(file) + 1);
  if (rstring != NULL)
    strcpy(rstring, file);
  return rstring;
}
