// Text ersetzen, durch stückweise Ausgabe der Zeile
// 
// Aufruf: subst suchwort ersatzwort [infile [outfile]]
// Ist kein Filename angegeben, wird stdin bzw. stdout verwendet
// 
// Klaus Kusche, 2010

#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);

// 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);
}

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

  progName = argv[0];
  if ((argc < 3) || (argc > 5)) {
    fprintf(stderr, "Aufruf: %s suchwort ersatzwort [infile [outfile]]\n",
            progName);
    exit(EXIT_FAILURE);
  }

  // zu suchender String, Ersatzstring
  const char *fromStr = argv[1];
  const char *toStr = argv[2];
  int fromLen = strlen(fromStr); // Länge von fromStr
  // bei leerem fromStr würde unser Programm seltsame Dinge machen...
  if (fromLen == 0) {
    fprintf(stderr, "%s: Empty search string?\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(line, sizeof(line), inFile)) {
    if (strchr(line, '\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);
    }
    // pro Vorkommen von fromStr in line (von links nach rechts) ein Durchlauf:
    // curPos zeigt auf den Beginn jenes Teils von line,
    // der noch durchsucht / verarbeitet werden muss.
    char *curPos;
    char *fromPos;  // Position des ersten Vorkommens von fromStr ab curPos
    for (// for-Initialisierung:
         // curPos beginnt ganz vorne in der eingelesenen Zeile
         curPos = line;
         // for-Bedingung, zugleich Suche von fromStr:
         // Lass fromPos auf die erste Fundstelle von fromStr ab curPos zeigen
         // Schleifenende wenn fromStr ab curPos nicht mehr vorkommt
         (fromPos = strstr(curPos, fromStr)) != NULL;
         // for-Weiterzählen:
         // Nächster Durchlauf mit dem Rest von line:
         // Setze curPos auf das erste Zeichen *hinter* dem gefundenen fromStr
         curPos = fromPos + fromLen) {
      // Endemarkierung: Schneide line beim 1. Zeichen von fromStr auseinander
      *fromPos = '\0';
      // gib das Stück von curPos bis vor fromStr (bis zum neuen \0) aus
      if (fputs(curPos, outFile) == EOF) {
        errMsg("writing", outFileName);
      }
      // gib statt fromStr unseren toStr aus
      if (fputs(toStr, outFile) == EOF) {
        errMsg("writing", outFileName);
      }
    }
    // gib den Rest der Zeile (von curPos bis zum Zeilenende, incl. \n) aus
    if (fputs(curPos, outFile) == EOF) {
      errMsg("writing", outFileName);
    }
  }

  // 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);
}
