#ifndef INCLUDED_BOBCAT_CONFIG_
#define INCLUDED_BOBCAT_CONFIG_

//    Lines are stored with initial WS removed.
//    If a line ends in \, then the next line (initial WS removed)
//    is appended to the current line.
//    Information at and beyond the first # on individual lines is removed
//    if the rmComment flag is set to true
//    Then, lines containing only blanks and tabs are not stored

#include <fstream>
#include <vector>
#include <string>
#include <cstring>

#include <bobcat/exception>
#include <bobcat/string>
#include <bobcat/pattern>

namespace FBB
{

class CF_Line
{
    uint16_t d_nr = 0;
    std::string d_line;

    public:
        CF_Line() = default;
        CF_Line(uint16_t lineNr, std::string const &line);

        std::string const &line() const;
        std::string key() const;
        std::string value() const;
        std::string tail() const;
        uint16_t lineNr() const;

    private:
        size_t next(size_t pos) const;                  // .f
        size_t tailPos() const;
};

#include "cfline1.f"
#include "line.f"
#include "linenr.f"
#include "next.f"
#include "cflineopinsert.f"

struct CF_Types
{
    enum Comment
    {
        KeepComment,
        NoComment,
    };
    enum Casing
    {
        UseCase,
        NoCase,
    };

    using LineVect =  std::vector<CF_Line>;
    using const_iterator =  std::vector<CF_Line>::const_iterator;

    using CIVect =  std::vector<const_iterator>;
    using CIVectIteratorPair =  
                std::pair<CIVect::const_iterator, CIVect::const_iterator>;
};

class CF_Pimpl: public CF_Types
{
    uint16_t d_rawNumber;
    LineVect d_line;

    bool d_rmComment;
    bool d_caseSensitive;

    CIVect d_CIvect;

    mutable Pattern d_pattern;

    public:
        explicit CF_Pimpl(Casing sType, Comment cType);                 // 1

        explicit CF_Pimpl(std::istream &stream,                         // 2
                          uint16_t lineNr, Casing sType, Comment cType);

        void setCasing(Casing type);                                    // .f
        void setComment(Comment type);                                  // .f

        void load(std::istream &stream, uint16_t lineNr);               // 4

        void clear();                                                   // 2

        const_iterator begin() const;                                   // .f
        const_iterator end() const;                                     // .f

        const_iterator find(std::string const &target,                  // 2
                            const_iterator const &from) const;

        const_iterator findID(std::string const &id,                    // 2
                              const_iterator const &from) const;

        const_iterator findKey(std::string const &key,                  // 2
                              const_iterator const &from) const;

        const_iterator findRE(std::string const &re,                    // 2
                              const_iterator const &from) const;

        CIVectIteratorPair beginEndRE(std::string const &re);           // .f
        CIVectIteratorPair beginEndID(std::string const &id);           // .f

        CF_Line const &operator[](size_t idx) const;                    // .f

        size_t size() const;                                            // .f

    private:
        bool nextLine(std::istream &inStream, std::string &dest);
        bool rmCommentAndEscapes(std::string &line);

        const_iterator findRE(const_iterator const &from) const;        // 3

        CIVectIteratorPair beginEnd(
                            std::string const &re,
                            const_iterator (CF_Pimpl::*find)(
                                                std::string const &,
                                                const_iterator const &) const
                        );

        static void trimLeft(std::string &line);
        static void trimRight(std::string &line, bool appendNext);
        static bool idChar(int ch);                                     // .f
        static bool caseSensitive(std::string const &haystack,          // .f
                                  std::string const &needle);

                            // needle is guaranteed lowercase
        static bool caseInsensitive(std::string const &haystack,        // .f
                                  std::string const &needle);
};

#include "idchar.f"
#include "casesensitive.f"
#include "caseinsensitive.f"

class Config: public CF_Types
{
    CF_Pimpl *d_ptr;

    public:
        explicit Config(Casing sType = UseCase,                         // 1.f
                          Comment cType = NoComment);

        explicit Config(std::string const &fname,                       // 2.f
                    Casing sType = UseCase,
                    Comment cType = NoComment);

        explicit Config(std::istream &stream,                           // 3.f
                    Casing sType = UseCase, Comment cType = NoComment);

        explicit Config(std::istream &stream, uint16_t lineNr,          // 4.f
                    Casing sType = UseCase, Comment cType = NoComment);

        explicit Config(std::istream &&stream,                          // 5.f
                    Casing sType = UseCase, Comment cType = NoComment);

        explicit Config(std::istream &&stream, uint16_t lineNr,         // 6.f
                    Casing sType = UseCase, Comment cType = NoComment);

        Config(Config &&tmp);                                           // 7.f
        Config(Config const &rhs);                                      // 8.f

        ~Config();

        Config &operator=(Config &&tmp);                                // 1
        Config &operator=(Config const &rhs);                           // 2

        CF_Line const &operator[](size_t idx) const;                    // 1.f

         void setCasing(Casing type);                                   // .f
         void setComment(Comment type);                                 // .f

        void load(std::string const &fname);                            // 1.f
        void load(std::istream &stream, uint16_t firstNr = 1);          // 2.f
        void load(std::istream &&stream, uint16_t firstNr = 1);         // 3.f

        void clear();                                                   // 1.f

        const_iterator begin() const;                                   // .f
        const_iterator end() const;                                     // .f

        const_iterator find(std::string const &needle) const;           // 1.f
        const_iterator find(std::string const &needle,                  // 1.f
                            const_iterator const &from) const;

        const_iterator findID(std::string const &id) const;             // 1.f
        const_iterator findID(std::string const &id,                    // 1.f
                              const_iterator from) const;

        const_iterator findKey(std::string const &key) const;           // 1.f
        const_iterator findKey(std::string const &key,                  // 1.f
                               const_iterator const &from) const;

        const_iterator findRE(std::string const &re) const;             // 1.f
        const_iterator findRE(std::string const &re,                    // 1.f
                                const_iterator const &from) const;

        CIVectIteratorPair beginEndRE(std::string const &re) const;     // .f
        CIVectIteratorPair beginEndID(std::string const &id) const;     // .f

        size_t size() const;                                            // .f
};

#include "begin.f"
#include "beginendid.f"
#include "beginendre.f"
#include "config1.f"
#include "config2.f"
#include "config3.f"
#include "config4.f"
#include "config5.f"
#include "config6.f"
#include "config7.f"
#include "config8.f"
#include "clear1.f"
#include "end.f"
#include "find1.f"
#include "findid1.f"
#include "findkey1.f"
#include "findre1.f"
#include "load1.f"
#include "load2.f"
#include "load3.f"
#include "opindex.f"
#include "setcasing.f"
#include "setcomment.f"
#include "size.f"

} // FBB
#endif
