    // Copyright (C) 2003  Davis E. King (davis@dlib.net)
    // License: Boost Software License   See LICENSE.txt for the full license.
    #ifndef DLIB_CONFIG_READER_KERNEl_1_
    #define DLIB_CONFIG_READER_KERNEl_1_

    #include "config_reader_kernel_abstract.h"
    #include <string>
    #include <iostream>
    #include <sstream>
    #include <fstream>
    #include "../algs.h"
    #include "../stl_checked/std_vector_c.h"

    #ifndef DLIB_ISO_CPP_ONLY
    #include "config_reader_thread_safe_1.h"
    #endif

    namespace dlib
    {

        template <
            typename map_string_string,
            typename map_string_void,
            typename tokenizer
            >
        class config_reader_kernel_1
        {

            /*!
                REQUIREMENTS ON map_string_string
                    is an implementation of map/map_kernel_abstract.h that maps std::string to std::string

                REQUIREMENTS ON map_string_void
                    is an implementation of map/map_kernel_abstract.h that maps std::string to void*

                REQUIREMENTS ON tokenizer
                    is an implementation of tokenizer/tokenizer_kernel_abstract.h

                CONVENTION
                    key_table.is_in_domain(x) == is_key_defined(x)
                    block_table.is_in_domain(x) == is_block_defined(x)

                    key_table[x] == operator[](x)
                    block_table[x] == (void*)&block(x)
            !*/

        public:

            // These two typedefs are defined for backwards compatibility with older versions of dlib.
            typedef config_reader_kernel_1 kernel_1a;
    #ifndef DLIB_ISO_CPP_ONLY
            typedef config_reader_thread_safe_1<
                config_reader_kernel_1,
                map_string_void
                > thread_safe_1a;
    #endif // DLIB_ISO_CPP_ONLY

            config_reader_kernel_1();

            class config_reader_error : public dlib::error
            {
                friend class config_reader_kernel_1;
                config_reader_error(
                    unsigned long ln,
                    bool r = false
                ) :
                    dlib::error(ECONFIG_READER),
                    line_number(ln),
                    redefinition(r)
                {
                    std::ostringstream sout;
                    sout << "Error in config_reader while parsing at line number " << line_number << ".";
                    if (redefinition)
                        sout << "\nThe identifier on this line has already been defined in this scope.";
                    const_cast<std::string&>(info) = sout.str();
                }
            public:
                const unsigned long line_number;
                const bool redefinition;
            };

            class file_not_found : public dlib::error
            {
                friend class config_reader_kernel_1;
                file_not_found(
                    const std::string& file_name_
                ) :
                    dlib::error(ECONFIG_READER, "Error in config_reader, unable to open file " + file_name_),
                    file_name(file_name_)
                {}

                ~file_not_found() throw() {}

            public:
                const std::string file_name;
            };

            class config_reader_access_error : public dlib::error
            {
            public:
                config_reader_access_error(
                    const std::string& block_name_,
                    const std::string& key_name_
                ) :
                    dlib::error(ECONFIG_READER),
                    block_name(block_name_),
                    key_name(key_name_)
                {
                    std::ostringstream sout;
                    sout << "Error in config_reader.\n";
                    if (block_name.size() > 0)
                        sout << "   A block with the name '" << block_name << "' was expected but not found.";
                    else if (key_name.size() > 0)
                        sout << "   A key with the name '" << key_name << "' was expected but not found.";

                    const_cast<std::string&>(info) = sout.str();
                }

                ~config_reader_access_error() throw() {}
                const std::string block_name;
                const std::string key_name;
            };

            config_reader_kernel_1(
                const std::string& config_file
            );

            config_reader_kernel_1(
                std::istream& in
            );

            virtual ~config_reader_kernel_1(
            );

            void clear (
            );

            void load_from (
                std::istream& in
            );

            void load_from (
                const std::string& config_file
            );

            bool is_key_defined (
                const std::string& key
            ) const;

            bool is_block_defined (
                const std::string& name
            ) const;

            typedef config_reader_kernel_1 this_type;
            const this_type& block (
                const std::string& name
            ) const;

            const std::string& operator[] (
                const std::string& key
            ) const;

            template <
                typename queue_of_strings
                >
            void get_keys (
                queue_of_strings& keys
            ) const;

            template <
                typename alloc
                >
            void get_keys (
                std::vector<std::string,alloc>& keys
            ) const;

            template <
                typename alloc
                >
            void get_keys (
                std_vector_c<std::string,alloc>& keys
            ) const;

            template <
                typename queue_of_strings
                >
            void get_blocks (
                queue_of_strings& blocks
            ) const;

            template <
                typename alloc
                >
            void get_blocks (
                std::vector<std::string,alloc>& blocks
            ) const;

            template <
                typename alloc
                >
            void get_blocks (
                std_vector_c<std::string,alloc>& blocks
            ) const;

        private:

            static void parse_config_file (
                config_reader_kernel_1& cr,
                tokenizer& tok,
                unsigned long& line_number,
                const bool top_of_recursion = true
            );
            /*!
                requires
                    - line_number == 1
                    - cr == *this
                    - top_of_recursion == true
                ensures
                    - parses the data coming from tok and puts it into cr.
                throws
                    - config_reader_error
            !*/

            map_string_string key_table;
            map_string_void block_table;

            // restricted functions
            config_reader_kernel_1(config_reader_kernel_1&);
            config_reader_kernel_1& operator=(config_reader_kernel_1&);

        };

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
        // member function definitions
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        template <
            typename map_string_string,
            typename map_string_void,
            typename tokenizer
            >
        config_reader_kernel_1<map_string_string,map_string_void,tokenizer>::
        config_reader_kernel_1(
        )
        {
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename map_string_string,
            typename map_string_void,
            typename tokenizer
            >
        void config_reader_kernel_1<map_string_string,map_string_void,tokenizer>::
        clear(
        )
        {
            // free all our blocks
            block_table.reset();
            while (block_table.move_next())
            {
                delete static_cast<config_reader_kernel_1*>(block_table.element().value());
            }
            block_table.clear();
            key_table.clear();
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename map_string_string,
            typename map_string_void,
            typename tokenizer
            >
        void config_reader_kernel_1<map_string_string,map_string_void,tokenizer>::
        load_from(
            std::istream& in
        )
        {
            clear();

            tokenizer tok;
            tok.set_stream(in);
            tok.set_identifier_token(
                tok.lowercase_letters() + tok.uppercase_letters(),
                tok.lowercase_letters() + tok.uppercase_letters() + tok.numbers() + "_-."
            );

            unsigned long line_number = 1;
            try
            {
                parse_config_file(*this,tok,line_number);
            }
            catch (...)
            {
                clear();
                throw;
            }
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename map_string_string,
            typename map_string_void,
            typename tokenizer
            >
        void config_reader_kernel_1<map_string_string,map_string_void,tokenizer>::
        load_from(
            const std::string& config_file
        )
        {
            clear();
            std::ifstream fin(config_file.c_str());
            if (!fin)
                throw file_not_found(config_file);

            load_from(fin);
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename map_string_string,
            typename map_string_void,
            typename tokenizer
            >
        config_reader_kernel_1<map_string_string,map_string_void,tokenizer>::
        config_reader_kernel_1(
            std::istream& in
        )
        {
            load_from(in);
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename map_string_string,
            typename map_string_void,
            typename tokenizer
            >
        config_reader_kernel_1<map_string_string,map_string_void,tokenizer>::
        config_reader_kernel_1(
            const std::string& config_file
        )
        {
            load_from(config_file);
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename map_string_string,
            typename map_string_void,
            typename tokenizer
            >
        void config_reader_kernel_1<map_string_string,map_string_void,tokenizer>::
        parse_config_file(
            config_reader_kernel_1<map_string_string,map_string_void,tokenizer>& cr,
            tokenizer& tok,
            unsigned long& line_number,
            const bool top_of_recursion
        )
        {
            int type;
            std::string token;
            bool in_comment = false;
            bool seen_identifier = false;
            std::string identifier;
            while (true)
            {
                tok.get_token(type,token);
                // ignore white space
                if (type == tokenizer::WHITE_SPACE)
                    continue;

                // basically ignore end of lines
                if (type == tokenizer::END_OF_LINE)
                {
                    ++line_number;
                    in_comment = false;
                    continue;
                }

                // we are in a comment still so ignore this
                if (in_comment)
                    continue;

                // if this is the start of a comment
                if (type == tokenizer::CHAR && token[0] == '#')
                {
                    in_comment = true;
                    continue;
                }

                // if this is the case then we have just finished parsing a block so we should
                // quit this function
                if ( (type == tokenizer::CHAR && token[0] == '}' && !top_of_recursion) ||
                     (type == tokenizer::END_OF_FILE && top_of_recursion) )
                {
                    break;
                }

                if (seen_identifier)
                {
                    seen_identifier = false;
                    // the next character should be either a '=' or a '{'
                    if (type != tokenizer::CHAR || (token[0] != '=' && token[0] != '{'))
                        throw config_reader_error(line_number);

                    if (token[0] == '=')
                    {
                        // we should parse the value out now
                        // first discard any white space
                        if (tok.peek_type() == tokenizer::WHITE_SPACE)
                            tok.get_token(type,token);

                        std::string value;
                        type = tok.peek_type();
                        token = tok.peek_token();
                        while (true)
                        {
                            if (type == tokenizer::END_OF_FILE || type == tokenizer::END_OF_LINE)
                                break;

                            if (type == tokenizer::CHAR && token[0] == '\\')
                            {
                                tok.get_token(type,token);
                                if (tok.peek_type() == tokenizer::CHAR &&
                                    tok.peek_token()[0] == '#')
                                {
                                    tok.get_token(type,token);
                                    value += '#';
                                }
                                else if (tok.peek_type() == tokenizer::CHAR &&
                                    tok.peek_token()[0] == '}')
                                {
                                    tok.get_token(type,token);
                                    value += '}';
                                }
                                else
                                {
                                    value += '\\';
                                }
                            }
                            else if (type == tokenizer::CHAR &&
                                     (token[0] == '#' || token[0] == '}'))
                            {
                                break;
                            }
                            else
                            {
                                value += token;
                                tok.get_token(type,token);
                            }
                            type = tok.peek_type();
                            token = tok.peek_token();
                        } // while(true)

                        // strip of any tailing white space from value
                        std::string::size_type pos = value.find_last_not_of(" \t\r\n");
                        if (pos == std::string::npos)
                            value.clear();
                        else
                            value.erase(pos+1);

                        // make sure this key isn't already in the key_table
                        if (cr.key_table.is_in_domain(identifier))
                            throw config_reader_error(line_number,true);

                        // add this key/value pair to the key_table
                        cr.key_table.add(identifier,value);

                    }
                    else  // when token[0] == '{'
                    {
                        // make sure this identifier isn't already in the block_table
                        if (cr.block_table.is_in_domain(identifier))
                            throw config_reader_error(line_number,true);

                        config_reader_kernel_1* new_cr = new config_reader_kernel_1;
                        void* vtemp = new_cr;
                        try { cr.block_table.add(identifier,vtemp); }
                        catch (...) { delete new_cr; throw; }

                        // now parse this block
                        parse_config_file(*new_cr,tok,line_number,false);
                    }
                }
                else
                {
                    // the next thing should be an identifier but if it isn't this is an error
                    if (type != tokenizer::IDENTIFIER)
                        throw config_reader_error(line_number);

                    seen_identifier = true;
                    identifier = token;
                }
            } // while (true)
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename map_string_string,
            typename map_string_void,
            typename tokenizer
            >
        config_reader_kernel_1<map_string_string,map_string_void,tokenizer>::
        ~config_reader_kernel_1(
        )
        {
            clear();
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename map_string_string,
            typename map_string_void,
            typename tokenizer
            >
        bool config_reader_kernel_1<map_string_string,map_string_void,tokenizer>::
        is_key_defined (
            const std::string& key
        ) const
        {
            return key_table.is_in_domain(key);
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename map_string_string,
            typename map_string_void,
            typename tokenizer
            >
        bool config_reader_kernel_1<map_string_string,map_string_void,tokenizer>::
        is_block_defined (
            const std::string& name
        ) const
        {
            return block_table.is_in_domain(name);
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename mss,
            typename msv,
            typename tokenizer
            >
        const config_reader_kernel_1<mss,msv,tokenizer>& config_reader_kernel_1<mss,msv,tokenizer>::
        block (
            const std::string& name
        ) const
        {
            if (is_block_defined(name) == false)
            {
                throw config_reader_access_error(name,"");
            }

            return *static_cast<config_reader_kernel_1*>(block_table[name]);
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename map_string_string,
            typename map_string_void,
            typename tokenizer
            >
        const std::string& config_reader_kernel_1<map_string_string,map_string_void,tokenizer>::
        operator[] (
            const std::string& key
        ) const
        {
            if (is_key_defined(key) == false)
            {
                throw config_reader_access_error("",key);
            }

            return key_table[key];
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename map_string_string,
            typename map_string_void,
            typename tokenizer
            >
        template <
            typename queue_of_strings
            >
        void config_reader_kernel_1<map_string_string,map_string_void,tokenizer>::
        get_keys (
            queue_of_strings& keys
        ) const
        {
            keys.clear();
            key_table.reset();
            std::string temp;
            while (key_table.move_next())
            {
                temp = key_table.element().key();
                keys.enqueue(temp);
            }
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename map_string_string,
            typename map_string_void,
            typename tokenizer
            >
        template <
            typename alloc
            >
        void config_reader_kernel_1<map_string_string,map_string_void,tokenizer>::
        get_keys (
            std::vector<std::string,alloc>& keys
        ) const
        {
            keys.clear();
            key_table.reset();
            while (key_table.move_next())
            {
                keys.push_back(key_table.element().key());
            }
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename map_string_string,
            typename map_string_void,
            typename tokenizer
            >
        template <
            typename alloc
            >
        void config_reader_kernel_1<map_string_string,map_string_void,tokenizer>::
        get_keys (
            std_vector_c<std::string,alloc>& keys
        ) const
        {
            keys.clear();
            key_table.reset();
            while (key_table.move_next())
            {
                keys.push_back(key_table.element().key());
            }
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename map_string_string,
            typename map_string_void,
            typename tokenizer
            >
        template <
            typename queue_of_strings
            >
        void config_reader_kernel_1<map_string_string,map_string_void,tokenizer>::
        get_blocks (
            queue_of_strings& blocks
        ) const
        {
            blocks.clear();
            block_table.reset();
            std::string temp;
            while (block_table.move_next())
            {
                temp = block_table.element().key();
                blocks.enqueue(temp);
            }
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename map_string_string,
            typename map_string_void,
            typename tokenizer
            >
        template <
            typename alloc
            >
        void config_reader_kernel_1<map_string_string,map_string_void,tokenizer>::
        get_blocks (
            std::vector<std::string,alloc>& blocks
        ) const
        {
            blocks.clear();
            block_table.reset();
            while (block_table.move_next())
            {
                blocks.push_back(block_table.element().key());
            }
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename map_string_string,
            typename map_string_void,
            typename tokenizer
            >
        template <
            typename alloc
            >
        void config_reader_kernel_1<map_string_string,map_string_void,tokenizer>::
        get_blocks (
            std_vector_c<std::string,alloc>& blocks
        ) const
        {
            blocks.clear();
            block_table.reset();
            while (block_table.move_next())
            {
                blocks.push_back(block_table.element().key());
            }
        }

    // ----------------------------------------------------------------------------------------

    }

    #endif // DLIB_CONFIG_READER_KERNEl_1_