// Templates: Zeichen- und Wortstatistik eines Textes
// Mit Verwendung des STL-Templates "map" als Member
//
// Aufruf: textcnt-stl filename
//
// Klaus Kusche, 2011

#include <iostream>
#include <fstream>
#include <cstdlib>
#include <cstring>
#include <string>
#include <cctype>
#include <map>

using namespace std;

// unser Zähler-Array für Werte vom Typ T
template <typename T>
class ValCntArray
{
  public:
    // kein Konstruktor ==> automatischer Standard-Konstruktor
    // dieser ruft für die Map "counters" den Standard-Konstruktor auf
    // der setzt die Map auf "keine Elemente"
    void count(const T &val);
    void print() const;

  private:
    // eine Map speichert Paare ("pair") von Werten,
    // wobei der erste Wert im Paar derjenige ist, nach dem gesucht wird
    map<T, int> counters;
};

#if 0
// Basis-Variante:
// Explizite "pair"-Operationen
template <typename T>
void ValCntArray<T>::count(const T &val)
{
//  typename map<T,int>::iterator i;
//  i = counters.find(val);
// Besser ab C++ 11:
// Typ von i automatisch nach Initialisierungswert festlegen lassen
  auto i = counters.find(val);

  if (i == counters.end()) {
    // nicht gefunden ==> neues Paar erzeugen und einfügen
    counters.insert(pair<T,int>(val, 1));
  } else {
    // gefunden (i zeigt darauf) ==> Zähler erhöhen
    ++(i->second);
  }
}
#endif

#if 0
// Verbesserte Variante 1:
// Mit operator[]
// Wir nutzen, dass ein noch nicht existierender Index in den []
// automatisch ein neues Element anlegt und in die map einfügt!
template <typename T>
void ValCntArray<T>::count(const T &val)
{
  if (counters.find(val) == counters.end()) {
    // nicht gefunden 
    counters[val] = 1;
  } else {
    // gefunden
    ++(counters[val]);
  }
}
#endif

#if 1
// Verbesserte Variante 2:
// Wenn der operator[] ein neues Element anlegt und einfügt,
// und der Nutzdaten-Teil ist ein int,
// dann wird dieser automatisch auf 0 gesetzt
// ==> Egal, ob das Element alt oder neu ist,
//     wir können in beiden Fällen einfach 1 dazuzählen!
template <typename T>
void ValCntArray<T>::count(const T &val)
{
  ++(counters[val]);
}
#endif

#if 0
// Basis-Variante:
// Iterator-Schleife
template <typename T>
void ValCntArray<T>::print() const
{
// wie oben: Durch "auto" ersetzt!
//  typename map<T,int>::iterator i;

// unsere Datenstruktur (das "this") ist const,
// also brauchen wir auch einen const-Iterator,
// deshalb cbegin und cend statt begin und end  
  for (auto i = counters.cbegin(); i != counters.cend(); ++i) {
    cout << i->first << ": " << i->second << endl;
  }
}
#endif

#if 1
// Verbesserte Variante:
// "for :"-Schleife
template <typename T>
void ValCntArray<T>::print() const
{
  // erst ab C++ 11:
  // for ( variable : container )
  // "Belege die Variable der Reihe nach mit allen Elementen des Containers"
  // Die Variable ist *kein* Iterator oder Pointer, sondern der Wert selbst
  // Genauer: Eine Referenz auf den Wert
  for (auto &i : counters) {
    cout << i.first << ": " << i.second << endl;
  }
}
#endif

const int LINE_WIDTH = 4095;   // max. Zeilenlänge des Eingabe-Files

int main(int argc, const char *argv[])
{
  if (argc != 2) {
    cerr << "Aufruf: " << argv[0] << " infilename" << endl;
    exit(EXIT_FAILURE);
  }

  ifstream inf(argv[1]);  // Eingabe-File
  if (!inf) {
    cerr << argv[0] << ": Cannot open " << argv[1] << " for reading" << endl;
    exit(EXIT_FAILURE);
  }

  ValCntArray<char> charCnt;     // zählt die einzelnen Buchstaben
  ValCntArray<string> wordCnt;   // zählt die ganzen Wörter
  ValCntArray<int> lenCnt;       // zählt die Wortlängen

  char line[LINE_WIDTH + 1]; // +1 für \0
  
  while (inf.getline(line, sizeof(line))) {
    int len = strlen(line);
    // an welchem Index in line beginnt das aktuelle Wort?
    // wenn wortStart -1 ist, sind wir nicht innerhalb eines Wortes
    int wordStart = -1;
    // die Schleife geht absichtlich bis <= len statt < len:
    // wir machen noch einen extra Umlauf für das '\0' selbst,
    // damit auch das letzte Wort der Zeile sicher gezählt wird.
    for (int i = 0; i <= len; ++i) {
      if (isalpha(line[i])) {
        // ein Buchstabe...
        line[i] = tolower(line[i]);  // in Kleinbuchstaben verwandeln
        charCnt.count(line[i]);      // und zählen
        if (wordStart == -1) {       // wenn wir nicht in einem Wort waren:
          wordStart = i;             // hier beginnt ein neues Wort!
        }
      } else {
        // kein Buchstabe
        if (wordStart != -1) {
          // wir waren in einem Wort: Teilstring ausschneiden und zählen
          string s(&(line[wordStart]), i - wordStart);
          wordCnt.count(s);
          lenCnt.count(s.length());
          wordStart = -1;  // wir sind nicht mehr in einem Wort
        }
      }
    }
  }

  charCnt.print();
  cout << "===" << endl;
  wordCnt.print();
  cout << "===" << endl;
  lenCnt.print();

  // wenn fail() true ist aber eof() nicht, ist ein Fehler aufgetreten
  if (!inf.eof()) {
    cerr << argv[0] << ": Cannot read " << argv[1] <<
            " (line too long?)" << endl;
    exit(EXIT_FAILURE);
  }
  
  exit(EXIT_SUCCESS);
}
