//
// 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++;


}
