/*---------------------------------------------------------------------------------------------------------------------
- File      : flow_basic_operators.h                                                      Project ELSE, EPFL - DI/LIA -
-                                                                       Evaluation in Language and Speech Engineering -
- Author    : Seydoux Florian   Creation date : 29 June 1999                                                          -
- Eulogist  : -                 Approval date : -                  Version: 0.1                                       -
-                                                                                                                     -
- Descript. : Define some basic operator on DataFlow (char flow)                                                      -
-                                                                                                                     -
- Requested : -                                                                                                       -
-                                                                                                                     -
- Gaps      : o) Meme hierarchie d'operateurs avec et sans ancrage.                                                   -
-             o) Strategie efficace pour vider les pipes en fin de flot                                               -
-             o) (lié) taille (delais) de chaque operateurs                                                           -
-                                                                                                                     -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Rev. date | Reviser               | Revise's description                                                            -
- - - - - - + - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- ../../....| ........              | ...                                                                             -
---------------------------------------------------------------------------------------------------------------------*/

#ifndef FLOW_BASIC_OPERATOR_H
#define FLOW_BASIC_OPERATOR_H

#include <deque>
#include <vector>
#include <string>
#include <cassert>
#include <cstring>
#include <stdexcept>

#include "globaldef.h"
#include "html_tag.h"
#include "dataflow.h"
#include "constrained_circular_buffer.h"

#ifdef _USE_NAMESPACES
    namespace Else { 
#endif // _USE_NAMESPACES

/* Alternative: on transcode le contenu pour le rendre HTML complient, et on le place dans une balise. 
 * Probleme: il faut transferer le contenu du string dans un 'filters_pool'... et les filters_pool utilisent
 * des RawStrTa... (ref. croisee dans les inclusions).
 * Solution (temporaire): une fonction de codage/decodage HTML - rawtext sur un string... mais cette fonction
 * est a 'factoriser' avec les outils 'txt2html' et 'untagger'
*/

// 
// Transformations:
// 
// '> ' -> '&lt '     '< ' -> '&gt '
// '>'  -> '&lt;'     '<'  -> '&gt;'
// '\ ' -> '&quot '   '& ' -> '&amp '
// '\'  -> '&quot;'   '&'  -> '&amp;'
//  
// conform_string rawToHtml(const string& rawtext);

// 
// Transformations:
// 
// '> ' <- '&lt '     '< ' <- '&gt '
// '>'  <- '&lt;'     '<'  <- '&gt;'
// '\ ' <- '&quot '   '& ' <- '&amp '
// '\'  <- '&quot;'   '&'  <- '&amp;'
// '  ' <- '&nbsp' 
// conform_string HtmlToRaw(const string& htmlText);

class RawStrTag: public conform_string, public HtmlField {
    class SizeAttribute : public TagAttribute {
    friend class RawStrTag;
    private:
        SizeAttribute(const RawStrTag& outer);
        void writeSingle(ostream& os) const;
        const string& name() const;
    private:
        static const string ATTRIBUT_NAME; // = "SIZE"
        const RawStrTag& outer;
    };

    class ValueAttribute : public TagAttribute {
    friend class RawStrTag;
    private:
        ValueAttribute(const RawStrTag& outer);
        void writeSingle(ostream& os) const;
        const string& name() const;
    private:
        static const string ATTRIBUT_NAME; // = "VALUE"
        const RawStrTag& outer;
    };

public:
    RawStrTag(const string& name);
    RawStrTag(const string& name, const string& str);
    RawStrTag(const string& name, const char*const str);
    const string& tagName() const;
    void write(ostream& os) const;
    
public:
	const string name;
    SizeAttribute   sizeAttribute;
    ValueAttribute  valueAttribute;
};


typedef LongNatural  AnchorType;

class Anchor: public TagAttribute { 
public:
    Anchor(const AnchorType startValue = 0);
    operator AnchorType() const;
    const string& name() const;
protected:
    void writeSingle(ostream& os) const;
    static const string ATTRIBUT_NAME; // = "ANCHOR"
    AnchorType value;
};



class AnchorCounter: public CharFlowListener, public Anchor { 
// il faudrait en faire une template, admettant un predicat (bool operateur) comme argument, fct de comptage.
public:
    AnchorCounter(const AnchorType startValue = 0);
    void reset(const AnchorType resetValue = 0);
    void add(const AnchorType increment);
    void computeFlowItem(const ichar& data);
};


class TextFlowToStream : public CharFlowListener, public StringFlowListener {
public:
    TextFlowToStream(ostream& os);
    void setOutputStream(ostream& os);
    void computeFlowItem(const ichar& data);
    void computeFlowItem(const string& data);
private:
    ostream* osPtr;
};


class StringToCharFlow : public StringFlowListener {
public:
    StringToCharFlow(CharFlowListener& out);
    void computeFlowItem(const string& data);
private:
    CharFlowListener& out;
};


class CharFlowString : public conform_string, public CharFlowListener {
//
// Il faut changer la hierarchie de classes, en attribuant un autre nom
// a celle-ci, et en faisant de CharFlowBuffer une specialisation (donc merge avec RawStrTag)
//  
public:
    CharFlowString();
    void computeFlowItem(const ichar& data);
    void loadString(const string& str);
};




class CharFlowBuffer: public RawStrTag, public CharFlowListener {
public:
    CharFlowBuffer();
    void computeFlowItem(const ichar& data);
};




class PatternTracker: public CharFlowAgent { // complexFlowAgent<bool,char> {
public:

    // Signal chaque occurence de la sous-chaine, dans le flux donne caractere par caractere
    // Attention: 
    // si SubChr = {EE}, sequence = {uEEEoEEEEp}
    //                   result   = {0010001010}
    //                   et pas   = [0011001110]  
    //
    // Si la chaine est vide, le résultat sera toujours faux.

    // Constructeur: passage par const reference -> memorisation interne par copie
    PatternTracker(const string& pattern, CharFlowListener& nextAgent = nullCharFlowListener);

    // Constructeur: passage par pointeur -> memorisation de la reference (objet externe)
    // 
    // [Rem: il me semble qu'il serait preferable de n'avoir qu'une reference (pointeur)
    //       et un booleen, ce dernier indiquant si le pointeur doit ou non etre desallouer
    //       en fin de vie de l'objet (car constructeur avec passage par reference -> allocation
    //       pour la copie locale). Peut-etre que les 'vrais' auto-pointeurs peuvent aider.
    //       
    PatternTracker(const string*const pattern, CharFlowListener& nextAgent = nullCharFlowListener);
    void clear();
    void flush();
    bool full();
    bool empty();
    ShortNatural size() const;
    ShortNatural capacity() const;
    void setNextAgent(CharFlowListener& nextAgent);
    void loadString(const string& str);
    string::size_type loadAndCheckString(const string& str);
    bool patternFound() const;
    const string& getPattern() const;
    void computeFlowItem(const ichar& data);
private:
    bool                   found;
    CharPipe               pipe;
    const string           localStr;
    const string&          pattern;
    string::const_iterator nextWaitedChar;
};
typedef PatternTracker* PatternTrackerPtr;




class Filter;
typedef Filter* FilterPtr;
class Filter: public CharFlowAgent {
public:
    virtual ~Filter();
    virtual void setNextAgent(CharFlowListener& nextAgent);
protected:
    Filter(CharFlowListener& nextAgent);
    virtual void applyFilter() const;
protected:
    CharFlowListenerPtr nextAgent;      // Successeur dans le flux de caracteres
};




class AnchoredFilter;
typedef AnchoredFilter* AnchoredFilterPtr;
class AnchoredFilter: public Filter, public HtmlField {
    //
    // Le but est de filtrer un flux de caractères de manière réversible
    // (Donc, l'action de chaque filtre doit être signalée qqpart, cela est fait dans le
    // flux (stream) d'ancrage (l'action et la position relative sont memorisee).
    // [Rem: prevoir un charFlow pour donnees ancrees plutot qu'un stream, mais
    //       le probleme est que les tags sont pour l'instant séparé des flow...
    //       la generalisation de l'emploi des 'flow' risque d'etre fortement redondante avec
    //       les streams (qui sont assurement mieux 'designé').]
    //
    // Pour garantir la reversibilite, le pool de filtres doit etre memorise (car les
    // donnees doivent recirculer dans les filtres (inverseurs), en partant du dernier.
    // A cette fin, le filtre est egalement un tag Sgml, qui represente la 'definition'
    // du filtre, a ajouter en preambule du fichier d'ancrage (persistance du pool de filtres)
    // 


    class FilterId: public ShortNaturalAttribute {
    public:
        FilterId(const ShortNatural id);
        static const string ATTRIBUT_NAME; // = "op_id"
    };

    class DefinitionTag: public HtmlField {
    friend class AnchoredFilter;
    public:
    	DefinitionTag():HtmlField()   { }
    	const string& tagName() const { return NAME; }
    	void write(ostream& os) const { startTag.write(os); }
    	static const string NAME;
    };
    

public:
    void setAnchoredStream(ostream& anchoredStream);
    void writeDefinition(ostream& os);
    const string& tagName() const;
    void write(ostream& os) const;
    
protected:
    AnchoredFilter(CharFlowListener& nextAgent, const string& tagName, ostream& anchoredStream = cerr);
//  virtual void applyFilter() const = 0;
    virtual void writeDefinitionField(ostream& os) const = 0;

protected:
    DefinitionTag       definitionTag;  // Definition du filtre (persistance)
    StringAttribute     operatorType;   // Indique le type de filtre (lors de son application)
    AnchorCounter       anchor;         // Ancre = positionnement local dans le flux
    ostream*            anchoredStreamPtr;
private:
    static const string DEFINED_TYPE;
    static ShortNatural counter;        // (class) Compteur d'instances
    const string        name;
    FilterId            id;             // Identificateur d'instance
};


class BasicQuietFilter: public Filter {
//
// Filtre basic: transmet ou bloque ce qui est entre 2 marques.
// L'action débute lorsque la première marque est rencontrée, et se poursuit jusqu'à ce que
// la seconde le soit.
//
// Si la première marque = "", l'action débute immédiatement, et n'aura lieu qu'une fois.
// Si la seconde marque = "", l'action se poursuivra jusqu'en fin de flux (et n'aura donc lieu qu'une fois)
//
// (  keep,   exceptMarks) -> seul le texte entre les marques (non comprises) est transmis (DEFAULT)
// (  keep, ! exceptMarks) -> seul les marques et le texte entre est transmis
// (! keep,   exceptMarks) -> seul le texte entre les marques (non comprises) est supprimé
// (! keep, ! exceptMarks) -> seul le texte entre (et y compris) les marques est supprimé
// 
public:
    enum State { OutMarks, InMarks };

    BasicQuietFilter(const string& startMark, const string& stopMark,
                     const bool keep = true, const bool exceptMarks = true,
                     CharFlowListener& nextAgent = nullCharFlowListener);
    void setNextAgent(CharFlowListener& nextAgent);
    void flush();
    void computeFlowItem(const ichar& data);
    const State& getState();
public:
    PatternTracker startMark;         // Identifie le debut du bloc a extraire
    PatternTracker stopMark;          // Identifie la fin du bloc
protected:
    const bool     keep;              // Definit le mode de fonctionnement (AND/NAND)
    const bool     exceptMarks;       // Exclut les marques de l'action entreprise
    State          state;             // Mémorise l'état courant de l'AFD.
};




class ExtractAnchoredFilter : public AnchoredFilter {
//
// (Watchdog == 0) = no Watchdog !
//
    enum State { OutMarks, InMarks };
public:
    ExtractAnchoredFilter(const string& start, const string& stop,
                          const string& subst = string(), const AnchorType watchdog = 0,
                          CharFlowListener& nextAgent = nullCharFlowListener, ostream& anchoredStream = cerr);
    ~ExtractAnchoredFilter();
    void setNextAgent(CharFlowListener& nextAgent);
    void applyFilter() const;
    void writeDefinitionField(ostream& os) const;
    void flush();
    void computeFlowItem(const ichar& data);
public:
    static const string TAG_NAME;          // = "EXTRACTOR"
    static const string SUBTAG_START_MARK; // = "START_MARK"
    static const string SUBTAG_STOP_MARK;  // = "STOP_MARK"
    static const string SUBTAG_SUBST_MARK; // = "SUBST_MARK"
    const RawStrTag  startStrTag;       // SgmlTag (persistance)
    const RawStrTag  stopStrTag;        // SgmlTag (persistance)
    const RawStrTag  substStrTag;       // Chaine a substituer a [start]*[stop]
    const AnchorType distanceMax;       // Distance maximale entre les ancres de debut et de fin du bloc 
private:
    State            state;             // Etat courant de l'automate
    PatternTracker*  startMark;         // Identifie le debut du bloc a extraire
    PatternTracker*  stopMark;          // Identifie la fin du bloc
    AnchorCounter    extractedCounter;  // Comptabilise (en terme d'ancres) la taille des données extraites.
    CharFlowBuffer   buffer;            // Memorise les donnees a extraire, avant identification du stopTag.
};




class TranslateAnchoredFilter: public AnchoredFilter {
    enum State { WaitMark, MergeMarks };


    class MergeCounter: public ShortNaturalAttribute {
    public:
        MergeCounter(const ShortNatural value);
        bool isDefault() const;
        void reset();
        void add(const ShortNatural value = 1);
    public:
        static const string ATTRIBUT_NAME; // = "merged"
    };


public:
    TranslateAnchoredFilter(const string original, const string translated, const string mergeWith = string(),
                            CharFlowListener& nextAgent = nullCharFlowListener, ostream& anchoredStream = cerr);
    ~TranslateAnchoredFilter();
    void setNextAgent(CharFlowListener& nextAgent);
    void applyFilter() const;
    void writeDefinitionField(ostream& os) const;
    void flush();
    void computeFlowItem(const ichar& data);
public:
    static const string TAG_NAME;          // = "TRANSCODER"
    static const string SUBTAG_ORIG_MARK;  // = "ORIG_MARK"
    static const string SUBTAG_MERGE_MARK; // = "MERGE_MARK"
    static const string SUBTAG_SUBST_MARK; // = "SUBST_MARK"
    const RawStrTag  originalStrTag;
    const RawStrTag  mergedStrTag;
    const RawStrTag  translatedStrTag;
    const bool       mergeContiguous;     // fusion des sequence contigues ?
protected:
    PatternTracker*  originalSequence;    // sequence a transcoder
    PatternTracker*  mergedSequence;      // sequence contigue a fusionner lors du transcodage 
private:
    State            state;               // Etat courant de l'AFD (merging sequence)
    MergeCounter     mergeCounter;        // Nombre de fusion
    AnchorCounter    swappedCounter;      // Compteur intermédiaire.
};



class QuietTranslateFilter: public Filter {
    enum State { WaitMark, MergeMarks };
    typedef ShortNatural MergeCounter;
public:
    QuietTranslateFilter(const string original, const string translated, const string mergeWith = string(),
                         CharFlowListener& nextAgent = nullCharFlowListener);
    void setNextAgent(CharFlowListener& nextAgent);
    void flush();
    void computeFlowItem(const ichar& data);
protected:
    const bool       mergeContiguous;     // fusion des sequence contigues ?
    const string     translated;          // transcodage de la sequence
    PatternTracker   originalSequence;    // sequence a transcoder
    PatternTracker   mergedSequence;      // sequence contigue a fusionner lors du transcodage 
private:
    State            state;               // Etat courant de l'AFD (merging sequence)
};


class QuietSpecificExtractFilter : public Filter { 
// Manque: Liste d'exceptions, avec localisation (Start,Stop,anywhere...)
// Actuellement, l'exception ne porte que sur la marque de debut (ce qui suit immediatement).
    enum State { OutMarks, Perhaps, InMarks };
public:
    QuietSpecificExtractFilter(const string& start, const string& stop, const string& subst = string(),
                               const string& except = string(), CharFlowListener& nextAgent = nullCharFlowListener);
    void setNextAgent(CharFlowListener& nextAgent);
    void flush();
    void computeFlowItem(const ichar& data);
private:
    State            state;             // Etat courant de l'automate
    const string     subst;             // Chaine de substitution   
    PatternTracker   startMark;         // Identifie le debut du bloc a extraire
    PatternTracker   stopMark;          // Identifie la fin du bloc
    PatternTracker   exceptMark;        // Sequence d'invalidation
    CharFlowBuffer   buffer;            // Memorise les donnees a extraire, avant identification du stopTag.
                                        // Actuellement non utilise.
};


#ifdef _USE_NAMESPACES
}
#endif // _USE_NAMESPACES

#endif // FLOW_BASIC_OPERATORS

