#ifndef INCLUDED_PARSER_
#define INCLUDED_PARSER_

#include <iostream>

#include <unordered_map>
#include <string>
#include <vector>
#include <sstream>

#include "../extractable/extractable.h"
//#include "../unsignedcastable/unsignedcastable.h"

#include "../typedefs/typedefs.h"
#include "../err/err.h"
#include "../globals/globals.h"
#include "../vsd/vsd.h"

class ConfigLines;

struct Parser
{
    struct OptionInfo
    {
        std::string name;
        std::string value;
        uint16_t    lineNr;
    };

    using OptionsVect = std::vector<OptionInfo>;

    private:
        using Map = std::unordered_map<std::string, Parser * >;

                                            // each element holds the specs
        LineInfoVect d_lines;               // of one series of keywords
        Map d_map;

        static Map s_map;

    public:
        class Lines
        {
            friend class Parser;

            unsigned d_size;                        // initial size

            LineInfoVect::const_iterator d_iter;    // first and last elements
            LineInfoVect::const_iterator d_end;     // of a selected vector

            public:
                                         // by repeated calls get all lines,
                                         // from the first to the last until
                                         // 0 is returned
                LineInfo const *get();                          // linesget.cc
    
                unsigned size() const;
                operator bool() const;          // size() != 0

            private:
                                                    // no lines found
                Lines(LineInfoVect const &lines);   // lineslines1.cc

                                                    // >= 1 line found
                                                    // lineslines2.cc
                Lines(LineInfoVect::const_iterator const &begin,
                      LineInfoVect::const_iterator const &end);

                Lines(Lines const &other) = default;

                LineInfoVect::const_iterator begin() const;             // .ih
                LineInfoVect::const_iterator end() const;               // .ih
        };

        using MapValue = Map::value_type;

        Parser();                                                       // 1.
        Parser(Map &&tmp);                                              // 2.

        ~Parser();

            // any number (>= 1) lines is OK
        static Lines any(StringVect const &keywords);

                    // extract 'dest' variables from lines.get()->tail
                    // returns 'good' if ok, else if no lines then
                    // eof() else fail() + error msg
        template <Extractable ...Types>                                 // .f
        static std::istringstream extract(Lines &&ines,
                                          Types &...dest);

                    // same as 1.f, but LineInfo is already available
                    // extracts from line.tail
        template <Extractable ...Types>                                 // .f
        static std::istringstream extract(LineInfo const &line,
                                          Types &...dest);

                    // extract 'size' 'dest' elements from lines.get()->tail
                    // return values as with extract1.f
        template <Extractable Type>                                     // .f
        static std::istringstream extract(Lines &&lines, Type *dest,
                                                        size_t size);

            // extract 'dest' values from one configuration line at 'keywords'
        template <Extractable ...Types>                                 // .f
        static std::istringstream extract(StringVect const &keywords,
                                          Types &...dest);

        static bool hasSection(StringVect const &keywords);

                                                    // analysis file
        void load(std::istream &in, uint16_t startLineNr,               // 1.
                  StringVect &labels,
                  OptionsVect &options);

        void load(std::string const &fname);        // configuration file  2.

            // requires specs like Survival:
            //  type:  a        .00004475   .000004392  Normal
            //  type:  b       1.85867      .0420       Normal
        static VSDvect VSDparameters(                               //  1.cc
                            VaryType varyType, StringVect const &keywords,
                            std::initializer_list<char> const &required);

            // exactly 1 configuration line is required
        static Lines one(StringVect const &keywords);

            // requires one line specs like Mammo:
            //  m:     .136    .136    .136    .136

        static VSDvect VSDparameters(                               //  2.cc
                            VaryType varyType, StringVect const &keywords,
                            size_t nRequired);

        // exactly 1 line is required.                              // .f
        static bool nonNegative(StringVect const &keywords, double &dest);

        template <std::integral Type>                                   // .f
        static bool nonNegative(StringVect const &keywords, Type &dest);

            // extract exactly one value associated with keywords
        template <Extractable Type>                                     // .f
        static bool one(StringVect const &keywords, Type &dest);

        static bool positive(StringVect const &keywords, double &dest); // .f

            // exactly 1 line is required
        static bool proportion(StringVect const &keywords, double &dest);

    private:
                                        // add the next parser line to the
                                        // appropriate map-section
        static void addConfigLine(std::vector<Map *> &mapPtrVect,
                                  ConfigLines &lines, ParamsSrc src);

        static bool atLeast(double minimum,
                            StringVect const &keywords, double &dest);

        static std::string checkRedo(ConfigLines &configLine);

                                        // deepest nesting level in the map
        static bool endPoint(Map::value_type const &mapValue);


        template <Extractable Type, Extractable ...Types>               // .f
        static bool extract(std::istream &in, Type &first, Types &...more);

        static bool extract(std::istream &in);                          // .f

                                                    // define d_lines[0]
                                                    // at s_map's deepest
        static void initLineInfoVects(Map &map);    // nesting levels

        static LineInfoVect const &keywordsLines(StringVect const &keywords);

        static Lines lines(StringVect const &keywords);                 //  1.

        static void locateError(std::vector<Map *> &mapPtrVect,
                                ConfigLines &lines, ParamsSrc src);

        static std::string sectionList(StringVect const &keywords);

        static void setLabels(ConfigLines &parser, StringVect &labels);
        static void setOptions(ConfigLines &parser, OptionsVect &options);

        static void rmLineInfoVectors(Map &map);
};

#include "parser.f"

#endif
