// Autor: Martin Heinrich
// MatNr: 0125222
// Klasse zu BspNr: 3061
// Dient dazu einen Text zu überprüfen und zu bearbeiten.
// Die Klasse Strings enthält die in den Spezifikationen festgelegten Regeln.
// Die Dokumentation befindet sich in der Datei Texte.txt

// Die Bezeichnungen //[Ziffer][Buchstabe] beziehen sich auf die Dokumentation (Texte.txt)

public class Strings
{
  private static final String lLetters = "enaiourbcdfghjklmpqstvwzxy"; // Ich habe die Häufigkeiten nicht nachgeschlagen
  private static final String uLetters = "ENAIOURBCDFGHJKLMPQSTVWZXY"; // ...aber vielleicht bringts ein paar ns :-)
  private static final String Letters = "enaiourbcdfghjklmpqstvwzxyAEIOUNRBCDFGHJKLMPQSTVWZXY";
  private static final char Space = ' ';
  private static final char LineBreak = '-';
  private static final String NewLine = "\n";
  private static final String MetaChars = ",;.\"() "; // Außer Buchstaben erlaubte Zeichen
  private static final String Simplify = ",;.\""; // Zeichen, die nur einmal ausgegeben werden sollen
  private static final String CommaSemiDot = ".,;";
  private static final String[] BreakBefore = {"keit", "heit", "los", "sam", "schaft"};
  private static final String[] BreakBehind = {"keit", "heit", "los", "sam"};

  // Die MindestAnzahl an Buchstaben (=2) bringt so viele Vorteile, dass sie an mehreren Stellen einfließt :-(
  private static final int maxLetterCount = 12;

  private static String text; // enthält den gesamten eingegebenen Text
  // wordCount wird erst durch checkSpecifications (eigentl. checkWords) ermittelt:
  private static int wordCount = -1; // enthält die Anzahl der Wörter

//3a
  // text, word- und breakCount können nur gelesen werden:
  public static String getString() {return new String(text);} // erstellt ein NEUES Objekt
  public static StringBuffer getStringBuffer() {return new StringBuffer(text);}
  // "wordCount" wird erst durch checkSpecifications (eigentl. checkWords) ermittelt!
  public static int getWordCount() {return wordCount;}
  // "breakCount" wird immer neu berechnet: gibt die Anzahl der Abteilungszeichen zurück (-):
  public static int getBreakCount()
  { int count = 0, cIndex = -1;
    while (-1 != (cIndex = text.indexOf(LineBreak, cIndex+1))) {count++;};
    return count;
  } // getBreakCount


//3b
  // Constructors:
  Strings() {text = new String();}
  Strings(String initText) {text = (new String(initText)).trim();} // erzeugt auf alle Fälle ein NEUES Objekt
  Strings(StringBuffer initText) {text = (new String(initText)).trim();} 


//****************************************************************
  // append: fügt einen String oder StringBuffer an
  // insSpace (optional [default = (text != "")]): fügt ein Leerzeichen zwischen den Stings ein wenn true
  public static void append(String appText) {append(appText, text.length() != 0);}
  public static void append(StringBuffer appText) {append(appText, text.length() != 0);}
  public static void append(String appText, boolean insSpace)
  {
    if (insSpace) {text += Space;};
    text += appText.trim();
  } // append(String appText, boolean insSpace)
  public static void append(StringBuffer appText, boolean insSpace)
  {
    if (insSpace) {text += Space;};
    text += appText.toString().trim();
  } // append(StringBuffer appText, boolean insSpace)
//****************************************************************


//4a
//****************************************************************
  // checkSpecifications: überprüft alle Korrektheitsregeln und korrigiert Interpunktionsfehler
  public static boolean checkSpecifications()
  { return ( // Die Methode ruft nur die entsprechenden privaten Methoden auf:
    checkChars() &&
    findErrors() &&
    checkWords()
    ); // return
  } // checkSpecifications
//****************************************************************


//****************************************************************
  // forceLines: führt alle Zeilenumbrüche durch
  // width: gibt die Zeilenbreite an
  // ACHTUNG: Wenn die Methode abbricht (d.h. return false), ist text zerstört!!!
  public static boolean forceLines(int width)
  { 
//5a
    String tText = text; // von hier wird der alte String gelesen; tText wird "abgebaut"
    int Index; // Zeigt die aktuelle Möglichkeit den String abzuteilen (jeweils vor Index)
//5b
    // Die folgenden Variablen werden nur beim Suchen nach Abteilungsmöglichkeiten NACH Silben eingesetzt
    String lText; // Übernimmt den String in Kleinbuchstaben, damit die Silbensuche ohne Unterscheidung verläuft
    int tIndex; // Wird zum Vergleichen mit Index verwendet

    text = new String(); // Der umgebrochene Text wird neu aufgebaut...
//5c
    while (tText.length() > width)
    {
//5d 
      Index = tText.lastIndexOf(',', width-1)+1; // NACH einem Komma darf abgeteilt werden
      Index = Math.max(Index, tText.lastIndexOf(Space, width)); // VOR einem Leerzeichen darf abgeteilt werden

      // Wenn ich VOR einer Silbe abteilen will, besteht diese sowieso nur aus Kleinbuchstaben!
      for (int i = 0; i < BreakBefore.length; i++)
      {
        Index = Math.max(Index, tText.lastIndexOf(BreakBefore[i], width-1)); // VOR einer solchen Silbe
      }; // for (i)                                                    // '-' nicht vergessen

//5e
      // Wenn ich NACH einer Silbe abteilen will, müssen noch zwei Buchstaben folgen
      lText = tText.substring(0, width-1).toLowerCase(); // so muss nicht alles (mehrfach) umgewandelt werden
      for (int i = 0; i < BreakBehind.length; i++)
      {
        tIndex = lText.lastIndexOf(BreakBehind[i]);
        if (tIndex != -1)
        { tIndex += BreakBehind[i].length();
          try
          { // Wenn im Folgenden die Indexgrenze überschritten wird, gibt es definitiv keine 2 Buchstaben nach der Silbe
            if ((tIndex > Index) && (MetaChars.indexOf(tText.charAt(tIndex)) == -1) &&
                                    (MetaChars.indexOf(tText.charAt(tIndex+1)) == -1))
            { Index = tIndex;};
          } catch (Exception e) {};
        }; // if (tIndex)
      }; // for (i)
     
//5f
      if (Index <= 0) {return false;}; // konnte nicht abteilen

      text += tText.substring(0, Index);
      // Wenn ich innerhalb eines Wortes abteile muss ein '-' eingefügt werden
      // Wenn hier ein Indexüberlauf auftritt, dann war ein Beistrich der letzte Buchstabe! (bereits ausgeschlossen!)
      if ((MetaChars.indexOf(tText.charAt(Index)) == -1) && (MetaChars.indexOf(tText.charAt(Index-1)) == -1))
      { text += LineBreak;};
      text += NewLine;
//5g
      tText = tText.substring(Index).trim(); // Leerzeichen am Beginn muss weg
    }; // while (length)

    text += tText;
    return true;
  } // forceLines
//****************************************************************


//****************************************************************
  // checkChars überprüft alle Regeln, die mit auftretenden Zeichen zusammenhängen:
  // * Nur Buchstaben und ' ,;.()"' dürfen auftreten
  // * Kein Buchstabe darf öfter als 3 mal hintereinander vorkommen
  // * ,;". werden vereinfacht, d.h. nur einmal ausgegeben
  // Die Methode unterscheidet keine Groß-/Kleinschreibung (steht ja bei 3061 nicht in den Spezifikationen)
  private static boolean checkChars()
  {
    if (text.length() == 0) {return true;}; // :-)
//6a
    int count = 1; // enthält die Anzahl der aufeinanderfolgenden BUCHSTABEN
    int prevLetter, prevSimplify; // Index des vorherigen Zeichens (Buchstabe bzw. zu vereinfachend)
    int curLetter, curSimplify; // Index des aktuellen Zeichens (Buchstabe bzw. zu vereinfachend)

//6b    
    // i = 0; Die folgenden Zeilen entsprechen den for-Operationen auf dem ersten Zeichen (natürlich ohne Vergleiche)
    char curChar = text.charAt(0); // so muss charAt() nur 1 mal aufgerufen werden
    prevLetter = lLetters.indexOf(curChar); // Hier könnte die G/K-Schreibung beachtet werden...
    if (prevLetter == -1) {prevLetter = uLetters.indexOf(curChar);};
    if (prevLetter == MetaChars.indexOf(curChar)) {return false;}; // nämlich == -1
    prevSimplify = Simplify.indexOf(curChar);

    for (int i = 1; i < text.length(); i++)
    {
//6c
      curChar = text.charAt(i);
      curLetter = lLetters.indexOf(curChar); // Hier könnte die G/K-Schreibung beachtet werden...
      if (curLetter == -1) {curLetter = uLetters.indexOf(curChar);};
      if (curLetter == MetaChars.indexOf(curChar)) {return false;}; // nämlich == -1
      curSimplify = Simplify.indexOf(curChar);
//6d
      if ((curLetter != -1) && (curLetter == prevLetter)) // gleicher Buchstabe
      {
        if (++count > 3) {return false;};
      } else {count = 1;};
//6e
      if ((curSimplify != -1) && (curSimplify == prevSimplify)) // gleiche ,;."
      {
        text = text.substring(0, i) + text.substring(i+1);
        i--; // weil das nächste Zeichen jetzt wieder an der selben Stelle ist
      } else // if (Simplify)
      {
        prevLetter = curLetter;
        prevSimplify = curSimplify;
      }; // if (Simplify)
    }; // for (i)
    return true;
  } // checkChars
//****************************************************************
  

//****************************************************************
  // findErrors überprüft alle Fehler, die durch das Suchen von substrings gefunden werden:
  // * ,; und ;, wird , andere Kombinationen sind nicht erlaubt (,;, oder ;,;)
  // * " und ( müssen wieder geschlossen werden
  private static boolean findErrors()
  { 
    int cIndex = 0; // Zwischenspeicher für einen indexOf()-Wert
//7a    
    while (-1 != (cIndex = text.indexOf(",;", cIndex)))
    {
      // Indexüberläufe sind im Folgenden unwichtig:
      try {if (text.charAt(cIndex-1) == ';') {return false;};} catch (Exception e) {}; // ;,;
      try {if (text.charAt(cIndex+2) == ',') {return false;};} catch (Exception e) {}; // ,;,

      text = text.substring(0, ++cIndex) + text.substring(cIndex+1);
    }; // while

    cIndex = 0;
    while (-1 != (cIndex = text.indexOf(";,", cIndex)))
    {
      text = text.substring(0, cIndex) + text.substring(++cIndex);
    }; // while
//7b
    int temp = 0; // Anzahl der "

    // BEACHTE: cIndex hat hier den Wert -1
    while (-1 != (cIndex = text.indexOf("\"", cIndex+1))) {temp++;};
    if (1 == (temp %= 2)) {return false;};
//7c
    // int temp = 0; // GIBTS SCHON!: Index der zuletzt "verwendeten" schließenden Klammer
    // BEACHTE: cIndex hat hier den Wert -1
    while (-1 != (cIndex = text.indexOf("(", cIndex+1)))
    {
      if (-1 == (temp = text.indexOf(")", ((cIndex > temp)?cIndex:temp+1)))) {return false;};
    }; // if (cIndex!=-1)

    return true;
  } // findErrors
//****************************************************************


//****************************************************************
  // checkWords überprüft alle Regeln, die mit Wörtern und Strings zusammenhängen:
  // * Ein String darf nicht nur aus Interpunktionszeichen bestehen
  // * " darf Anfang oder am Ende des Strings stehen
  // * .;) dürfen ausschließlich am Ende eines Strings stehen
  // * Am Ende darf vor und/oder nach ) bzw " noch eines aus .,; stehen
  // * ( darf nur am Anfang eines Strings stehen
  // * , darf nicht am Anfang stehen (nur zwischen Wörtern oder am Ende -> s.o.)
  // * Großbuchstaben nur am Anfang eines Wortes
  // * Allererster Buchstabe muss groß sein
  // * Ein Wort hat zwischen 2 und 12 Buchstaben (jeweils inklusive)
  // * Hinter dem letzten Wort sind , und ; zu entfernen
  // * Wenn hinter dem letzten Wort kein Punkt steht, ist einer anzuhängen
  private static boolean checkWords()
  { 
//8a
    switch (text.length()) 
    { case 0: wordCount = 0; return true; // :-)
      case 1: return false; // Ein Wort muss zwei Buchstaben haben!
// Wegen der gleichzeitigen Führung dreier Zeichen ist der Trivialfall gleich hier:
      case 2:
      { if ((uLetters.indexOf(text.charAt(0)) >= 0) && (lLetters.indexOf(text.charAt(1)) >= 0))
        { wordCount = 1;
          text += '.';
          return true;
        } else
        { return false;
        }; // else
      }; // case 2
    }; // switch
//8b
    boolean letterFound = false; // Buchstabe im String gefunden (nur Interpunktionszeichen verboten!)
    int letterCount = 0; // Anzahl der Buchstaben im Wort
    wordCount = 0;
// Das erste und letzte Zeichen wird außerhalb der Schleife behandelt!
    char prevChar = text.charAt(0); // vorhergehendes (also (i-2)tes) Zeichen

    switch (prevChar)
    { case '(': case '"': break; // sicher erlaubt
      case ',': case ';': case '.': case ')': return false; // sicher verboten
      default: // Buchstaben:
      { if (uLetters.indexOf(prevChar) >= 0)
        { letterFound = true;
          letterCount = 1;
        } else
        { return false; // Kleinbuchstabe als 1. Zeichen
        }; // if
      }; // default
    }; // switch
    char curChar = prevChar; // aktuelles (also (i-1)tes) Zeichen
    char nextChar = text.charAt(1); // nächstes (also i-tes) Zeichen
                    // kein Fehler, weil length()==1 bereits ausgeschlossen!
//8c
    for (int i = 2; i < text.length(); i++) // i wird um 1 zu hoch gezählt um (i < text.length()-1) zu vermeiden!
    {
      prevChar = curChar;
      curChar = nextChar;
      nextChar = text.charAt(i);
//8d
      if (MetaChars.indexOf(curChar) == -1) // Buchstaben (aber MetaChars ist kürzer als Letters)
      { if (letterCount == 0)
        { if ((wordCount == 0) && (uLetters.indexOf(curChar) == -1)) {return false;}; // 1. Buchstabe muss groß sein
          letterFound = true;
          letterCount = 1;
        } else // if (LetterCount)
        { if (uLetters.indexOf(curChar) >= 0) {return false;}; // Großbuchstabe nur am Wortanfang
          if (letterCount++ == maxLetterCount) {return false;};
        }; // if (LetterCount)
      } else // if (MetaChars)
      { switch (curChar)
        { 
//8e
          case '"': // Nur am Anfang oder Ende eines STRINGS
            if ((prevChar != Space) && (nextChar != Space) && (CommaSemiDot.indexOf(nextChar) == -1))
            { return false;
            }; // if (Anfang || Ende)
            if (letterCount == 1) {return false;}; // minLetterCount = 2
            if (letterCount > 1)
            { letterCount = 0;
              wordCount++;
            }; // if (LetterCount)
            break;
//8f
          case '(': // Nur am Anfang eines STRINGS
            if (prevChar != Space) {return false;};
            break;
//8g
          case ')': // Nur am Ende eines STRINGS
            if ((nextChar != Space) && (CommaSemiDot.indexOf(nextChar) == -1)) {return false;};
            if (letterCount == 1) {return false;}; // minLetterCount = 2
            if (letterCount > 1)
            { letterCount = 0;
              wordCount++;
            }; // if (LetterCount)
            break;
//8h
          case ';': // Dürfen nicht VOR einem Wort stehen, sonst wie ','
          case '.':
            if (MetaChars.indexOf(nextChar) == -1) {return false;};
          case ',': // Muss HINTER einem Wort oder " oder ) stehen
            switch (letterCount)
            { case 0: // Muss zwischen " oder ) und LEERZEICHEN stehen
                if (((prevChar != '"') && (prevChar != ')')) || (nextChar != Space)) {return false;};
                break;
              case 1: return false; // minLetterCount = 2
              default:
              { letterCount = 0;
                wordCount++;
              }; // default (letterCount)
            }; // switch (letterCount)
            break;
//8i
          case ' ': // trennt die STRINGS
            if (!letterFound) {return false;}; // String darf nicht nur aus Interpunktionszeichen bestehen
            letterFound = false;
            if (letterCount == 1) {return false;}; // minLetterCount = 2
            if (letterCount > 1)
            { letterCount = 0;
              wordCount++;
            }; // if (LetterCount)
            break;
          default: // dürfte nie eintreten (sicher nicht, wenn checkChars() vorher aufgerufen wurde!)
          { return false;};
        }; // switch (curChar)
      }; // if (Buchstaben)
    }; // for (i)
//8j
    // Letztes Zeichen wird kontrolliert und ,; nach letztem Wort entfernt und ggf. ein Punkt angehängt
    if (lLetters.indexOf(nextChar) >= 0) // Letztes Zeichen darf kein Großbuchstabe sein (weil minLetterCount = 2)
    { if ((letterCount < 1) || (letterCount >= maxLetterCount)) {return false;};
      wordCount++;
      text += '.';
    } else // if (lLetters)
//8k
    { if ((!letterFound) || (letterCount == 1)) {return false;}; // nur Interpunktionszeichen im STRING sind verboten
                                                                 // und minLetterCount = 2
      switch (nextChar)
      { case '(': return false;
//8l
        case ',':
        case ';':
        case '.':
          text = text.substring(0, text.length()-1); // .,; hinter dem letzten Wort werden entfernt
          if (letterCount > 1)
          { wordCount++;
            text += '.';
          } else // if (letterCount)
          { switch (curChar) // davor nur ) oder "
            { case '"':
              case ')': // davor müssten , oder ; noch entfernt werden (weil nach letztem Wort)
                switch (prevChar)
//8m
                { case ',':
                  case ';':
                    text = text.substring(0, text.length()-2) + curChar + '.';
                    break;
//8n
                  case '.': // falls oben ein '.' entfernt wurde muss der jetzt wieder hin
                    if (nextChar == '.') {text += '.';}; // sonst nicht (weil hinter dem letzten Wort eh einer ist)
                    break;
//8o
                  default: // vor [")|.,;] steht ein Buchstabe (eigentlich nur mehr vor [")])
                  { text += '.';};
                }; // switch (prevChar)
                break;
              default:
              { return false;};
            }; // switch (curChar)
          }; // if (LetterCount)
          break;
//8p
        case '"':
        case ')': // davor müssten , oder ; noch entfernt werden (weil nach letztem Wort)
          switch (curChar)
          { case ',':
            case ';':
              text = text.substring(0, text.length()-2) + nextChar + '.';
              break;
            case '.': break; // hinter dem letzten Wort steht eh ein Punkt
            default: // vor [")] steht ein Buchstabe
            { wordCount++;
              text += '.';
            }; // default
          }; // switch (curChar)
          break;
        default: // Großbuchstabe am Schluss
        { return false;};
      }; // switch (nextChar)
    }; // if (lLetters)

    return true;
  } // checkWords
//****************************************************************

} // Strings