/*------------------------------------------------*
 | File: grep.c - First release MLO 920611        |
 | Last revised: v1.1 MLO 920612                  |
 | Unix-like grep utility, for rev. 37 or greater |
 | (OS 2.04) of the Amiga DOS. See Syntax() for a |
 | short description of this program.             |
 *------------------------------------------------*/

/**
 | #include files
**/

#include <stdio.h>                      /* Standard library */
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <exec/types.h>                 /* AmigaDOS includes */
#include <exec/memory.h>
#include <dos/dos.h>
#include <dos/dosasl.h>
#include <dos/dosextens.h>
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>

/**
 | #define's and local stuff
**/

#define REVISION        "1.1"           /* Program version */
#define LAST_CHANGED    "920612"

typedef short int       Boolean;        /* Boolean variables */

#define True            1
#define False           0

#define MATCH_STATUS    0               /* Exit status */
#define NOMATCH_STATUS  5
#define ERROR_STATUS    10

#define OS2_REVISION    37              /* AmigaDOS min. release (2.04) */

#define FILENAME_MAX    128             /* Max. length of file names */
#define LINE_LENGTH     256             /* Max. length of an input line */

/**
 | Version Tag string
**/

char VersionTag[] = "\0$VER: grep v" REVISION " - MLO " LAST_CHANGED;

/**
 | Global variables: flags for various program options, exit status
**/

Boolean CountOnly       = False;
Boolean IgnoreCase      = False;
Boolean PrintAll        = False;
Boolean PrintName       = False;
Boolean LineNumber      = False;
Boolean Reverse         = False;
Boolean StarEnabled     = False;
Boolean BreakFlag       = False;
int Status              = MATCH_STATUS;

/**
 | Global varables: program buffers, pointers to AmigaDOS structures
**/

extern struct DosLibrary *DOSBase;      /* Opened from c.o */

char *Pattern               = NULL;
char *Line                  = NULL;
struct AnchorPath *pAP      = NULL;
size_t APlength             = sizeof(struct AnchorPath) + FILENAME_MAX;

/**
 | ANSI procedure prototypes
**/

Boolean CheckBreak(void);
void    Cleanup(void);
int     CXBRK(void);
void    DoTheStuff(FILE *fp, char *name);
void    Error(void);
void    Init(void);
char   *NextFile(char *pattern);
void    NoMem(void);
void    Syntax(void);

main(
  int argc,
  char **argv
){

/**
 | Main program: general initialisation, then loop on program options.
**/

  Init();

  while (--argc) {
    if ( ((*++argv)[0] == '-') ) {
      int i=1;
      char c;

      while (c = (*argv)[i++]) {
        switch (c) {
          case 'c':
          case 'C':
            CountOnly = True;
            break;
          case 'f':
          case 'F':
            PrintAll = True;
            break;
          case 'i':
          case 'I':
            IgnoreCase = True;
            break;
          case 'l':
          case 'L':
            PrintName = True;
            break;
          case 'n':
          case 'N':
            LineNumber = True;
            break;
          case 'v':
          case 'V':
            Reverse = True;
            break;
          default:
            Syntax();
            Error();
        }
      }
    } else if ((*argv)[0] == '?') {
      Syntax();
      Cleanup();
    } else {
      break;
    }
  }

/**
 | First (required) argument: pattern to search for.
 | As the pattern can be everywhere in the input line,
 | put a '*' before and after before converting to the
 | internal system format.
**/

  if (argc--) {
    size_t i = strlen(*argv);
    size_t j = i + 3;
    char *temp;
    char star[] = "*";

    if ((temp = malloc(j)) == NULL)   NoMem();
    else {
      if ((Pattern = malloc(j *= 3)) == NULL) {
        free(temp);
        NoMem();
      }
    }

    *temp = *star;
    strcpy(temp+1, *argv);
    strcpy(temp+1+i, star);

    if (IgnoreCase)   (void) ParsePatternNoCase(temp, Pattern, j);
    else              (void) ParsePattern(temp, Pattern, j);

    free(temp);
    Status = NOMATCH_STATUS;

  } else {
    Syntax();
    Error();
  }

/**
 | Following (optional) arguments: file patterns.
 | They will be decoded, and every file in turn scanned for the
 | given pattern; if no argument is given, scan stdin.
 | If a break (CTRL-C) is detected from DoTheStuff() (and then
 | BreakFlag is non-zero) we must re-enter NextFile to release
 | the pattern matching internal stuff (calling MatchEnd()).
**/

  if (argc) {
    while (!BreakFlag   &&   argc--) {
      char *fil_nam;

      ++argv;
      while ((fil_nam = NextFile(*argv)) != NULL) {
        FILE *fp;

        if ((fp = fopen(fil_nam, "r")) == NULL) {
          printf("Couldn't open file \"%s\" ...\n", fil_nam);
          Status = ERROR_STATUS;
        } else {
          DoTheStuff(fp, fil_nam);
          fclose(fp);
        }
      }
    }
  } else {
    DoTheStuff(stdin, NULL);
  }

  Cleanup();  
}

Boolean CheckBreak(void)
{

/**
 | Internal CTRL-C detection routine (the signal is cleared
 | from the call). An internal attention flag i set up, and
 | non-zero is returned as function value.
**/

  if (!BreakFlag   &&
      ((SetSignal(0, SIGBREAKF_CTRL_C)) & SIGBREAKF_CTRL_C))
          BreakFlag = True;

  return BreakFlag;
}

void Cleanup(void)
{

/**
 | Releases all system resources, then terminates the program
 | returning 'Status' to the operating system.
**/

  if (pAP     != NULL)    FreeMem(pAP, APlength);
  if (Line    != NULL)    free(Line);
  if (Pattern != NULL)    free(Pattern);
  if (StarEnabled)        DOSBase->dl_Root->rn_Flags &= ~RNF_WILDSTAR;

  exit(Status);
}

int CXBRK(void)
{

/**
 | Supersedes SAS-C internal CTRL-C handling routine,
 | that detects CTRL-C on I/O etc.
 | That one sets the internal attention flag, then waits
 | (until CheckBreak() will be called).
**/

  BreakFlag = True;
  return 0;
}

void DoTheStuff(
  FILE *fp,
  char *name
){

/**
 | Central control routine.
 | Scans the file looking for the given pattern, and producing the
 | required output.
**/

  long lineCount  = 0;                /* Number of read lines */
  long how_many   = 0;                /* Number of matching lines */
  Boolean match;                      /* 'File name printed' flag */
  Boolean file_out = False;

  if (PrintAll) {
    file_out = True;
    printf("Scanning file \"%s\" ...\n", name);
  }

  while (!CheckBreak()   &&   fgets(Line, LINE_LENGTH, fp) != NULL) {
    lineCount++;
    match = IgnoreCase ? MatchPatternNoCase(Pattern, Line) :
                         MatchPattern(Pattern, Line);

    if (match) {
      if (Status == NOMATCH_STATUS)   Status = MATCH_STATUS;
      if (!Reverse) {

Gotcha:
        if (CountOnly) {
          how_many++;
        } else {
          if (PrintName   &&   !file_out) {
            file_out = True;
            if (name != NULL)   printf("In file \"%s\" ...\n", name);
          }
          if (LineNumber)       printf("%ld: ", lineCount);
          fputs(Line, stdout);
        }
      }
    } else {
      if (Reverse) goto Gotcha;
    }
  }

  if (!BreakFlag   &&   CountOnly   &&   how_many) {
    if (PrintName   &&   name != NULL)  printf("In file \"%s\": ", name);
    printf("%ld matches.\n", how_many);
  }

  if (how_many = ferror(fp)) {
    printf("DOS error %ld while reading file \"%s\" ...\n",
            how_many, name);
    Status = ERROR_STATUS;
  }
}

void Error(void)
{
  Status = ERROR_STATUS;
  Cleanup();
}

void Init(void)
{

/**
 | Initialisation routine.
 | Checks dos.library version number; enables (if not already enabled)
 | the use of the '*' as wildcard character; and obtains memory for
 | the buffers needed to work.
**/

  if (DOSBase->dl_lib.lib_Version < OS2_REVISION) {
    printf("Unable to open \"dos.library\" rev. %d ...\n", OS2_REVISION);
    Error();
  }

  if (StarEnabled = !(DOSBase->dl_Root->rn_Flags & RNF_WILDSTAR)) {
    DOSBase->dl_Root->rn_Flags |= RNF_WILDSTAR;
  }

  if ((pAP = AllocMem(APlength, MEMF_CLEAR)) == NULL   ||
      (Line = malloc(LINE_LENGTH)) == NULL) {
    NoMem();
  }
  pAP->ap_Strlen    = FILENAME_MAX;
  pAP->ap_BreakBits = SIGBREAKF_CTRL_C;
}

char *NextFile(
  char *pattern
){

/**
 | Calls MatchFirst()/MatchNext() for the next file in the pattern;
 | if a break has been detected NextFile() exits immediately (then
 | this program will be terminated); otherwise the routine checks
 | for errors, then exits.
**/

  static Boolean first_entry = True;
  long error;

  if (BreakFlag)  goto Clear;

  error = first_entry ? MatchFirst(pattern, pAP) : MatchNext(pAP);

  if (pAP->ap_FoundBreak) {
    BreakFlag = True;
    goto Clear;
  }

  if (error) {
    if (error == ERROR_NO_MORE_ENTRIES) {
      if (first_entry) {
        printf("\"%s\": no such file(s) ...\n", pattern);
      }
    } else {
      printf("DOS error %ld returned from MatchFirst/MatchNext ...\n",
              error);
    }
    first_entry = True;

Clear:
    MatchEnd(pAP);
    return NULL;
  }

  first_entry = False;
  return pAP->ap_Buf;
}

void NoMem(void)
{
  puts("Couldn't obtain heap memory ...\n");
  Error();
}

void Syntax(void)
{
  printf("\n\t\t%s\n\n", VersionTag + 7);
  puts("\tSyntax: grep [options] pattern [ File [ File [ ... ]]\n");
  puts("Searches files (a pattern can be given in the file name(s)), for");
  puts("lines containing a given pattern (with the AmigaDOS conventions).");
  puts("If no file names are given, grep assumes standard input.");
  puts("Exit status is 0 if any matches are found, 5 (warning) if none,");
  puts("10 (error) for unreadable files or other errors.\n");
  puts("Options: -c Print only the number of the matching lines;");
  puts("         -f Print the name of all the scanned files;");
  puts("         -i Ignore upper/lower case distinctions in the comparison;");
  puts("         -l Print the name of the files with matching lines (once);");
  puts("         -n Precede each line by its line number in the file;");
  puts("         -v Print all lines but those that contain the pattern.\n");
}
