//
// g++ -Wall -O2 -o fnt fnt.cpp -lboost_regex-mt

#include <string>
#include <iostream>
#include <iterator>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <iomanip>
#include <ctime>
#include "boost/bind.hpp"
#include "boost/shared_ptr.hpp"
#include "boost/operators.hpp"
#include "boost/regex.hpp"
#include "boost/lexical_cast.hpp"

using namespace std;

/// Konstante für die maximale Leselänge von einem Stream
namespace buffer { enum bufsiz { size = 512  }; }

/// Die Klasse "file_tokenizer" liefert einen Iterator auf einen Input Stream.
/// Der Input Stream wird dabei in Stücke zerteilt, die auf ein bestimmtes
/// Zeichen enden oder die maximale Leselänge der Konstanten buffer::size erreichen.
///
/// Ich mache mir dabei die boost Bibliothek "boost::operator" zu Nutze, um nur
/// die minimale Schnittstelle implementieren zu müssen.
class file_tokenizer : private boost::input_iterator_helper<file_tokenizer, string> {
   /// Zugrundeliegender "input"
   istream * file;
   /// Interne Variable zum Zwischenspeichern des Stream Zustands
   bool ok;
   /// Das Trennzeichen der "tokens"
   unsigned char delim;
   /// Interner Buffer fürs Lesen vom "input"
   char buf[buffer::size];
   /// Zur einfachen Weiterverarbeitung wird der gelesene Buffer
   /// als dieser string gespeichert und vom Iterator zurückgegeben
   string worker;
public:
   /// Standard Konstruktor
   /// @param i Der zugrendliegende input stream
   /// @param d Das Trennzeichen der Iterator Stücke
   explicit file_tokenizer(istream * i = 0, unsigned char d = '\n');
   /// copy constructor, dessen Implementierung boost::operator erzwingt
   file_tokenizer(const file_tokenizer& x);
   /// Vergleichsoperator. Wird hier vorallem für die das Iterator Ende
   /// benötigt. Implementierung boost::operator
   bool operator==(const file_tokenizer & x) const;
   /// Increase Operator, erzwungen von boost::operator
   file_tokenizer & operator++( void );
   /// Derefernzierung zum Zugriff auf das Zugrundeliegende Element
   const string & operator*( void ) const;
};

/// Die struktur "footnote" dient zur Speicherung aller
/// Fußnoten mit der Häufigkeit der Verwendung, der Position in
/// der Textdatei und der Rangfolge ihrer ersten Verwendung.
struct footnote {
   /// Standard Konstruktor
   explicit footnote( unsigned int c = 0,
                      unsigned int o = 0,
                      unsigned int r = 0) :
       counter( c ), offset( o ), order( r) { }
   /// Häufigkeit der Verwendung
   unsigned int counter;
   /// Position in der textdatei
   unsigned int offset;
   /// Rangfolge der ersten Verwendung
   unsigned int order;
};
/// Jede Fußnote wird in dieser Map abgelegt, indiziert durch
/// ihre orginal Nummer.
typedef map<unsigned int, footnote> footnote_map;

/// Der Textdatei wird beim ersten Scannen die Orginal
/// Fußnotennummer und die Anzahl der Zeichen, die zwischen
/// der letzten Fußnote lagen, entrissen. Diese beiden
/// Zahlen werden in einer Struktur names "footnote_offset"
/// gespeichert und beim zweiten Lesen der Datei verwendet.
struct footnote_offset {
   /// Standard Konstruktor
   explicit footnote_offset(
       unsigned int id, unsigned int offset ) :
       id( id), offset( offset ) {
   }
   /// Orginal Nummer der Fußnote. Die Fußnote 0 hat eine
   /// Sonderbedeutung. Sie ist für den Anfangstext bis zur
   /// ersten Fußnote reserviert und soll nicht weiter-
   /// verarbeitet werden.
   unsigned int id;
   /// Anzahl der gelesen Zeichen seit der letzten Fußnote
   unsigned int offset;
};
/// Das Ergebnis des ersten Textdatei Scans werden innerhalb
/// einer Liste von "footnote_offset"s gespeichert. Ich
/// nenne ihn "text_chunks".
typedef list< footnote_offset > text_chunks;

/// Nachdem wir alle Daten gesammelt haben, wird die Textdatei
/// ein zweites Mal durchlaufen. Dabei werden je nach Modus die
/// Fußnoten nach der Reihenfolge ihres Auftretens oder ihrer
/// Häufigkeit numeriert und im Text angegeben. Um die Ausgabe
/// zu erleichtern, schaffe ich mir dieses erweiterte
/// Funktionsobject "print_footnote"
class print_footnote {
   /// Die Sammelung aller gescannten Fußnoten
   footnote_map & fidx;
   /// Zugrundeliegender "input"
   istream * file;
   /// Interner Buffer fürs Lesen vom "input"
   char buf[buffer::size];
public:
   /// Standard Konstruktor
   /// @param fidx Die gesammelten Fußnoten
   /// @param file Der zugrundeliegnede "input stream"
   explicit print_footnote( footnote_map & fidx,
                            istream * file );
   /// Hilfsfunktion die den geöffneten stream zum Anfang zurücksetzt
   void rewind( void );
   /// Dadurch wird dies ein Funktionsobject, das den Text ausdrucken
   /// kann. Intern wird der mitgegebene Textabschnitt mit der
   /// neuen Fußnote versehen und an "stdout" geschickt. Bei Fußnote
   /// "0" wird der der Verweis übersprungen.
   void operator()( const footnote_offset & ft );
};

/// In diesem Funktionsobjekt wird jeder String nach dem
/// Fußnotenmuster gescanned und den beiden Containern "text_chunks"
/// und "footnote_map" die entsprechenden Einträge hinzugefügt.
class build_index {
   boost::regex pattern;
   string buffer;
   const string switcher;
   bool switched;
   text_chunks * text;
   footnote_map * footnotes;
public:
   /// Konstruktor
   explicit build_index( const string & sw ,
                         text_chunks * text,
                         footnote_map * footnotes );
   /// Durchkämmt den den mitgegebenen string und erweitert
   /// damit die interenen Container um ...
   void operator()( const string & ft );
};

/// Nun ist alles vorbereitet...
int main( int argc, char *argv[] )
{
   // Die zu bearbeitende Datei wird als erster Parameter erwartet
   ifstream source( argv[1] );
   text_chunks text;
   footnote_map footnotes;
   build_index bldidx( "@footnotes:", &text, &footnotes );
   print_footnote printit( footnotes, &source );

   // Auf der Datei wird nun einen Iterator definiert,
   // der diese in ']' terminierte Stückchen zerteilt.
   file_tokenizer bos( &source,'['), eos;
   // Gehe mittels dieses Iteratotrs durch die Datei und baue
   // dabei den Index auf.
   for_each( bos, eos, bldidx );
   // Textdatei zurücksetzten und damit bereit für den
   // zweiten Durchlauf sein
   printit.rewind();
   // Jetzt kann der Textteil der Datei ausgedruckt werden
   for_each( text.begin(), text.end(), printit );
   // Aufräumen
   source.close();
}

//////////////////////////////////////////////////////////////////////
// Implementierungsteil //////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////

file_tokenizer::file_tokenizer(istream * i, unsigned char d )
   : file(i), ok(false), delim(d) { }
file_tokenizer::file_tokenizer(const file_tokenizer& x)
   : file(x.file), ok(x.ok), delim( x.delim) {
   ++(*this);
}
bool file_tokenizer::operator==(const file_tokenizer & x) const {
   return ( ok == x.ok ) && ( ! ok || file == x.file );
}
file_tokenizer & file_tokenizer::operator++( void ){
   ok = ( file && *file && ! file->eof() ) ? true : false;
   if ( ok ) {
       worker.clear();
       if ( file->peek() == delim ){
           file->get();
           worker.assign("[");
       }
       if ( file->peek() != delim ){
           memset( buf, 0, buffer::size );
           file->get( buf, buffer::size , delim );
           worker.append( buf );
       }
   }
   return *this;
}
const string & file_tokenizer::operator*( void ) const {
   return worker;
}
print_footnote::print_footnote( footnote_map & fidx,
                               istream * file ) :
   fidx( fidx ), file( file ) {
}
void print_footnote::rewind( void ){
   file->clear();
   file->seekg( 0, ios::beg );
}
void print_footnote::operator()( const footnote_offset & ft ){
   // Nur echte Fußnoten werden auch gedruckt
   if ( ft.id != 0 ){
       cout << '[' << fidx[ft.id].order << ']';
       file->get( buf, 32, ']' );
       file->get();
   }
   memset( buf, 0, buffer::size );
   file->read( buf, ft.offset );
   cout << buf;
}
build_index::build_index( const string & sw ,
                         text_chunks * text,
                         footnote_map * footnotes
   ) :
   pattern( "\\[([0-9]+)\\].*" ), buffer (""),
   switcher(sw) , switched(false),
   text(text), footnotes(footnotes) {
}
void build_index::operator()( const string & ft ){
   static unsigned int footnote_counter = 0;
   buffer.append( ft );
   size_t switch_pos = buffer.find( switcher, 0 );
   if ( switch_pos != string::npos ){
       switched = true;
   }

   boost::smatch footnote_id;
   unsigned int id = 0;
   unsigned int os = buffer.length();
   if( 0 != boost::regex_match(buffer, footnote_id, pattern ) ){
       id = boost::lexical_cast<unsigned int>(footnote_id[1].str());
       os = os - footnote_id[1].str().length() - 2;
   }

   buffer.clear();
   text->push_back( footnote_offset( id, os ) );
   if ( footnotes->find( id ) == footnotes->end() )
       (*footnotes)[ id ] = footnote();
   (*footnotes)[ id ].counter++;
   if ( switched )
       (*footnotes)[ id ].order = footnote_counter++;


}