// Templates: Zeichen- und Wortstatistik eines Textes
// Mit Ableitung vom STL-Templates "map"
//
// Aufruf: textcnt-stl filename
//
// Klaus Kusche, 2011

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

using namespace std;

// Hinweis:
// Dieses Beispiel funktionierte früher mit rustikalen C++-Compilern,
// wenn man end(), find(...), insert(...), cbegin() und cend()
// direkt aufgerufen hat (ohne this->...).
// 
// Mit aktuellen C++-Sprachstandards und Compilern funktioniert es nicht mehr:
// Man benötigt entweder this->end(), this->find(...) usw.
// oder ValCntArray<T>::end() usw. oder map<T, int>::end() usw.
// Zumindest für insert(...) schreibt der gcc sogar
// "note: declarations in dependent base 'std::map<...>' are not found by unqualified lookup"
// "note: use 'this->insert' instead"
//
// Bei nicht-Template-Klassen funktioniert es ohne this->,
// aber beim Kompilieren von Templates
// findet er einen Template-Instanz-abhängigen Namen nur dann,
// wenn er schon vorab erkennen kann, dass dieser Namen vom Template abhängt.
// Wenn der Name (so wie end()) nicht erkennbar vom Template abhängt,
// dann werden diejenigen end() nicht gefunden bzw. nicht berücksichtigt,
// die erst durch die Instanzierung des Templates definiert werden,
// z.B. unser von der eigenen map-Instanz geerbtes end().


// unser Zähler-Array für Werte vom Typ T
// es ist eine map, aber das soll "von außen" niemand sehen / wissen,
// deshalb private abgeleitet
template <typename T>
class ValCntArray: private map<T, int>
{
  public:
    // kein Konstruktor ==> automatischer Standard-Konstruktor
    // dieser ruft den Vaterklassen-Standard-Konstruktor auf
    // der setzt die Map auf "keine Elemente"
    void count(const T &val);
    void print() const;
};

#if 0
// Basis-Variante:
// Explizite "pair"-Operationen
template <typename T>
void ValCntArray<T>::count(const T &val)
{
  // das ist ein Aufruf unserer eigenen
  // (d.h. der von map geerbten!) Methode find
  // für unser eigenes Objekt, das ja eine Map ist
  // analog weiter unten für Aufrufe von end(), insert(), cbegin(), ...
  // *this im folgenden Code ist das eigene Objekt, also die Map
  auto i = this->find(val);

  if (i == this->end()) {
    // nicht gefunden ==> neues Paar erzeugen und in uns selbst einfügen
    this->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 (this->find(val) == this->end()) {
    // nicht gefunden
    (*this)[val] = 1;
  } else {
    // gefunden
    ++((*this)[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)
{
  ++((*this)[val]);
}
#endif

#if 0
// Basis-Variante:
// Iterator-Schleife
template <typename T>
void ValCntArray<T>::print() const
{
  for (auto i = this->cbegin(); i != this->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 : *this) {
    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);
}
