// Text ersetzen, mit strrepl-Funktion zeichenweise ohne String-Funktionen
// 
// Aufruf: subst suchwort ersatzwort [infile [outfile]]
// Ist kein Filename angegeben, wird stdin bzw. stdout verwendet
// 
// Klaus Kusche, 2020

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

// Max. Zeilenlänge der Eingabe
#define LINELEN 4096

const char *progName;  // Der Programmname (argv[0]), für Fehlermeldungen

void errMsg(const char *operation, const char *fileName);
char *strrepl(char dest[], const char src[], int destLen,
              const char oldStr[], const char newStr[]);

// Gib eine schöne Fehlermeldung aus und beende das Programm
// operation ... wobei ist der Fehler passiert?
// fileName  ... welcher File war betroffen?
void errMsg(const char *operation, const char *fileName)
{
  fprintf(stderr, "%s: error %s %s: %s\n",
          progName, operation, fileName, strerror(errno));
  exit(EXIT_FAILURE);
}

// Die String-Ersetz-Funktion:
// Kopiere src nach dest
// und ersetze dabei alle Vorkommen von oldStr durch newStr
// destLen ist die Größe von dest (zur Längenprüfung)
char *strrepl(char dest[], const char src[], int destLen,
              const char oldStr[], const char newStr[])
{
  const char *srcPtr = src;    // Aktuelle Position in src
  char *destPtr = dest;        // Aktuelle Position in dest
  char *lastPtr = dest + (destLen - 1);  // Zeiger auf das letzte Byte von dest
                                         // für Längen-Prüfungen

  while (*srcPtr != '\0') {
    if (*srcPtr == oldStr[0]) {
      // aktuelles Zeichen == erstes Zeichen von oldStr
      // (dieses if vermeidet auch Treffer bei leerem oldStr)
      // ==> das könnte ein Vorkommen von oldStr sein
      // ==> vergleiche src ab srcPtr mit oldStr
      const char *oldPtr;  // Aktuelle Position in oldStr
      const char *tmpPtr;  // Hilfs-Pointer in src: Wo vergleiche ich gerade?
      for (tmpPtr = srcPtr, oldPtr = oldStr;
           (*oldPtr != '\0') && (*oldPtr == *tmpPtr);
           ++tmpPtr, ++oldPtr) {
      }
      if (*oldPtr == '\0') {
        // oldStr wurde komplett gefunden
        // tmpPtr zeigt hinter die Fundstelle in src
        // mach im nächsten Schleifendurchlauf
        // mit dem src-Zeichen hinter der Fundstelle weiter
        srcPtr = tmpPtr;
        // newStr nach dest kopieren, newPtr = Aktuelle Position in new
        for (const char *newPtr = newStr; *newPtr != '\0'; ++newPtr) {
          if (destPtr >= lastPtr)  {
            dest[0] = '\0';  // dest soll bei Fehler auf leer gesetzt werden
            return NULL;
          }
          *(destPtr++) = *newPtr;
        }
        // Normales Kopieren eines Zeichens entfällt:
        // Gleich nächster Umlauf des while: Nächstes src-Zeichen anschauen
        continue;
      }
      // else: oldStr nicht komplett gefunden ==> Normalfall: Kopiere 1 Zeichen
    }
    // "Normalfall": Kopiere ein Zeichen von src nach dest
    if (destPtr >= lastPtr) {  // dest ist schon voll!
      dest[0] = '\0';  // dest soll bei Fehler auf leer gesetzt werden
      return NULL;
    }
    *(destPtr++) = *(srcPtr++);
  }

  *destPtr = '\0';  // neuen String in dest beenden
  return dest;
}

int main(int argc, const char *argv[])
{
  FILE *inFile, *outFile;               // Eingabe-File und Ausgabe-File
  const char *inFileName, *outFileName; // Filenamen der beiden Files
  char input[LINELEN + 2];              // String zum Einlesen einer Zeile
                                        // +1 für \n, +1 für \0
  char output[LINELEN + 2];             // String für Ergebnis von strrepl

  progName = argv[0];
  if ((argc < 3) || (argc > 5)) {
    fprintf(stderr, "Aufruf: %s suchwort ersatzwort [infile [outfile]]\n",
            progName);
    exit(EXIT_FAILURE);
  }
  
  // mach den File auf, wenn auf der Befehlszeile ein Filenamen angegeben wurde
  // verwende stdin bzw. stdout, wenn kein Filenamen angegeben wurde
  if (argc >= 4) {
    inFileName = argv[3];
    if ((inFile = fopen(inFileName, "r")) == NULL) {
      errMsg("opening (for reading)", inFileName);
    }
  } else {
    inFileName = "stdin";
    inFile = stdin;
  }

  if (argc >= 5) {
    outFileName = argv[4];
    if ((outFile = fopen(outFileName, "w")) == NULL) {
      errMsg("opening (for writing)", outFileName);
    }
  } else {
    outFileName = "stdout";
    outFile = stdout;
  }

  // Input zeilenweise verarbeiten bis der File zu Ende ist
  while (fgets(input, sizeof(input), inFile)) {
    if (strchr(input, '\n') == NULL) {
      // Eingelesene Zeile enthält kein \n
      // ==> passiert nur, wenn kein Platz mehr für das \n war
      // ==> Zeile ist länger als LINELEN Zeichen
      // Problem unter Windows:
      // Letzte Zeile von Textfiles hat oft kein \n
      // ==> falsche Fehlermeldung!
      // (in Linux hat auch die letzte Zeile *immer* ein \n)
      fprintf(stderr, "%s: error reading %s: Incomplete line\n",
              progName, inFileName);
      exit(EXIT_FAILURE);
    }
    if (strrepl(output, input, sizeof(output), argv[1], argv[2])) {
      // strrepl hat funktioniert
      fputs(output, outFile);
    } else {
      fprintf(stderr, "%s: error replacing in %s: result too long\n",
              progName, input);
      exit(EXIT_FAILURE);      
    }
  }

  // Hat das fgets NULL geliefert,
  // weil das File-Ende erreicht wurde,
  // oder weil ein Fehler aufgetreten ist?
  if (ferror(inFile)) {
    errMsg("reading", inFileName);
  }

  // Files schließen
  // es schadet auch nichts, die std-Files zu schließen
  if (fclose(inFile) == EOF) {
    errMsg("closing", inFileName);
  }
  if (fclose(outFile) == EOF) {
    errMsg("closing", outFileName);
  }
  
  exit(EXIT_SUCCESS);
}
