#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;
};

inline CF_Line::CF_Line(uint16_t lineNr, std::string const &line)
:
    d_nr(lineNr),
    d_line(line)
{}
inline std::string const &CF_Line::line() const
{
    return d_line;
}
inline uint16_t CF_Line::lineNr() const
{
    return d_nr;
}
inline size_t CF_Line::next(size_t pos) const
{
    return d_line.find_first_of(" \t\r", pos);  // first ws sep from 'pos'
}
inline std::ostream &operator<<(std::ostream &out, CF_Line const &cfl)
{
    return out << cfl.line();
}

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);
};

inline bool CF_Pimpl::idChar(int ch)
{
    return isalnum(ch) or ch == '_';
}
//static
inline bool CF_Pimpl::caseSensitive(std::string const &haystack,
                                           std::string const &needle)
{
    return haystack.find(needle) != std::string::npos;
}
//static
inline bool CF_Pimpl::caseInsensitive(std::string const &lhs,
                                             std::string const &rhs)
{
    return caseSensitive(String::lc(lhs), rhs);
}

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
};

inline CF_Pimpl::const_iterator CF_Pimpl::begin() const
{
    return d_line.begin();
}

inline Config::const_iterator Config::begin() const
{
    return d_ptr->begin();
}
inline CF_Types::CIVectIteratorPair CF_Pimpl::beginEndID(std::string const &re)
{
    return beginEnd(re, &CF_Pimpl::findID);
}

inline Config::CIVectIteratorPair Config::beginEndID(
                                                std::string const &id) const
{
    return d_ptr->beginEndID(id);
}
inline CF_Types::CIVectIteratorPair CF_Pimpl::beginEndRE(std::string const &re)
{
    return beginEnd(re, &CF_Pimpl::findRE);
}

inline Config::CIVectIteratorPair Config::beginEndRE(
                                                std::string const &re) const
{
    return d_ptr->beginEndRE(re);
}
inline Config::Config(Casing sType, Comment cType)
:
    d_ptr(new CF_Pimpl(sType, cType))
{}
inline Config::Config(std::string const &fname, Casing sType, Comment cType)
:
    Config(FBB::Exception::factory<std::ifstream>(fname),
            sType, cType)
{}
inline Config::Config(std::istream  &stream, Casing sType, Comment cType)
:
    d_ptr(new CF_Pimpl(stream, 1, sType, cType))
{}
inline Config::Config(std::istream  &stream, uint16_t lineNr,
                      Casing sType, Comment cType)
:
    d_ptr(new CF_Pimpl(stream, lineNr, sType, cType))
{}
inline Config::Config(std::istream  &&stream, Casing sType, Comment cType)
:
    Config(stream, 1, sType, cType)
{}
inline Config::Config(std::istream  &&stream, uint16_t lineNr,
                      Casing sType, Comment cType)
:
    Config(stream, lineNr, sType, cType)
{}
inline Config::Config(Config &&tmp)
:
    d_ptr( new CF_Pimpl(std::move(*tmp.d_ptr) ))
{}
inline Config::Config(Config const &rhs)
:
    d_ptr(new CF_Pimpl(*rhs.d_ptr))
{}
inline void Config::clear()
{
    d_ptr->clear();
}
inline CF_Pimpl::const_iterator CF_Pimpl::end() const
{
    return d_line.end();
}

inline Config::const_iterator Config::end() const
{
    return d_ptr->end();
}
inline Config::const_iterator Config::find(std::string const &needle,
                                            const_iterator const &from) const
{
    return d_ptr->find(needle, from);
}

inline Config::const_iterator Config::find(std::string const &needle) const
{
    return find(needle, begin());
}
inline Config::const_iterator Config::findID(
                        std::string const &id, const_iterator from) const
{
    return d_ptr->findID(id, from);
}

inline Config::const_iterator Config::findID(
                                             std::string const &id) const
{
    return findID(id, begin());
}
inline Config::const_iterator Config::findKey(
                        std::string const &id,
                        const_iterator const &from) const
{
    return d_ptr->findKey(id, from);
}

inline Config::const_iterator Config::findKey(
                                             std::string const &id) const
{
    return findKey(id, begin());
}
inline Config::const_iterator Config::findRE(
                    std::string const &id, const_iterator const &from) const
{
    return d_ptr->findRE(id, from);
}

inline Config::const_iterator Config::findRE(
                                             std::string const &id) const
{
    return findRE(id, begin());
}
inline void Config::load(std::string const &fname)
{
    load(FBB::Exception::factory<std::ifstream>(fname), 1);
}
inline void Config::load(std::istream &stream, uint16_t firstNr)
{
    d_ptr->load(stream, firstNr);
}
inline void Config::load(std::istream &&stream, uint16_t firstNr)
{
    load(stream, firstNr);
}
inline CF_Line const &CF_Pimpl::operator[](size_t idx) const
{
    return d_line[idx];
}

inline CF_Line const &Config::operator[](size_t idx) const
{
    return (*d_ptr)[idx];
}
inline void CF_Pimpl::setCasing(Casing type)
{
    d_caseSensitive = type == UseCase;
}

inline void Config::setCasing(Casing type)
{
    d_ptr->setCasing(type);
}
inline void CF_Pimpl::setComment(Comment type)
{
    d_rmComment = type == NoComment;
}

inline void Config::setComment(Comment type)
{
    d_ptr->setComment(type);
}
inline size_t CF_Pimpl::size() const
{
    return d_line.size();
}

inline size_t Config::size() const
{
    return d_ptr->size();
}

} // FBB
#endif
