// 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"

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:

        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 reinterpret_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 *reinterpret_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_

