// Copyright (C) 2006  Davis E. King (davisking@users.sourceforge.net)
// License: Boost Software License   See LICENSE.txt for the full license.
#ifndef DLIB_LOGGER_KERNEl_1_
#define DLIB_LOGGER_KERNEl_1_

#include "../threads.h"
#include "../misc_api.h"
#include "../set.h"
#include "logger_kernel_abstract.h"
#include <limits>
#include <cstring>
#include "../algs.h"
#include "../assert.h"
#include "../uintn.h"
#include "../map.h"
#include "../smart_pointers.h"

namespace dlib
{

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

    class log_level
    {
    public:
        log_level(
            int priority_, 
            const char* name_
        ) : 
            priority(priority_)
        {
            strncpy(name,name_,19);
            name[19] = '\0';
        }

        int priority;
        char name[20];
    };

    const log_level LALL  (std::numeric_limits<int>::min(),"ALL");
    const log_level LNONE (std::numeric_limits<int>::max(),"NONE");
    const log_level LTRACE(-100,"TRACE");
    const log_level LDEBUG(0  ,"DEBUG");
    const log_level LINFO (100,"INFO ");
    const log_level LWARN (200,"WARN ");
    const log_level LERROR(300,"ERROR");
    const log_level LFATAL(400,"FATAL");

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

    void set_all_logging_output_streams (
        std::ostream& out
    );

    void set_all_logging_levels (
        const log_level& new_level
    );

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

    void print_default_logger_header (
        std::ostream& out,
        const std::string& logger_name,
        const log_level& l,
        const uint64 thread_id
    );

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

    class logger 
    {
        /*!
            INITIAL VALUE
                - print_header == print_default_logger_header
                - out.rdbuf() == std::cout.rdbuf()
                - cur_level == LERROR
                - auto_flush_enabled == true 

            CONVENTION
                - print_header == logger_header()
                - out.rdbuf() == output_streambuf()
                - cur_level == level()
                - logger_name == name()
                - auto_flush_enabled == auto_flush()

                - logger::gd::loggers == a set containing all currently existing loggers.
                - logger::gd::m == the mutex used to lock everything in the logger
                - logger::gd::thread_names == a map of thread ids to thread names.  
                - logger::gd::next_thread_name == the next thread name that will be given out
                  to a thread when we find that it isn't already in thread_names.
        !*/

        class logger_stream
        {
            /*!
                INITIAL VALUE
                    - been_used == false

                CONVENTION
                    - enabled == is_enabled()
                    - if (been_used) then
                        - logger::gd::m is locked
                        - someone has used the << operator to write something to the
                          output stream.
            !*/
        public:
            logger_stream (
                const log_level& l_,
                logger& log_
            ) :
                l(l_),
                log(log_),
                been_used(false),
                enabled (l.priority >= log.cur_level.priority)
            {}

            inline ~logger_stream(
            )
            {
                if (!been_used)
                {
                    return;
                }
                else
                {
                    print_end_of_line();
                }
            }

            bool is_enabled (
            ) const { return enabled; }

            template <typename T>
            inline logger_stream& operator << (
                const T& item
            )
            {
                if (!enabled)
                {
                    return *this;
                }
                else
                {
                    print_header_and_stuff();
                    log.out << item;
                    return *this;
                }
            }

        private:

            void print_header_and_stuff (
            );
            /*!
                ensures
                    - if (!been_used) then
                        - prints the logger header 
                        - locks log.gd.m
                        - #been_used == true
            !*/

            void print_end_of_line (
            );
            /*!
                ensures
                    - prints a newline to log.out
                    - unlocks log.gd.m
            !*/

            const log_level& l;
            logger& log;
            bool been_used;
            const bool enabled;
        };

        friend class logger_stream;
    public:

        logger (  
            const char* name_
        );

        virtual ~logger (
        ); 

        const std::string& name (
        ) const { return logger_name; }

        logger_stream operator << (
            const log_level& l
        ) const { return logger_stream(l,const_cast<logger&>(*this)); }

        bool is_child_of (
            const logger& log
        ) const
        {
            return (name().find(log.name() + ".") == 0) || (log.name() == name());
        }

        const log_level level (
        ) const 
        { 
            auto_mutex M(gd.m);
            return log_level(cur_level); 
        };

        void set_level (
            const log_level& new_level
        )
        {
            auto_mutex M(gd.m);
            gd.loggers.reset();
            while (gd.loggers.move_next())
            {
                if (gd.loggers.element()->is_child_of(*this))
                    gd.loggers.element()->cur_level = new_level;
            }

            gd.set_level(logger_name, new_level);
        }

        bool auto_flush (
        ) const 
        { 
            auto_mutex M(gd.m);
            return auto_flush_enabled;
        };

        void set_auto_flush (
            bool enabled
        )
        {
            auto_mutex M(gd.m);
            gd.loggers.reset();
            while (gd.loggers.move_next())
            {
                if (gd.loggers.element()->is_child_of(*this))
                    gd.loggers.element()->auto_flush_enabled = enabled;
            }

            gd.set_auto_flush(logger_name, enabled);
        }

        std::streambuf* output_streambuf (
        )
        {
            auto_mutex M(gd.m);
            return out.rdbuf();
        }

        void set_output_stream (
            std::ostream& out_
        ) 
        {
            auto_mutex M(gd.m);
            gd.loggers.reset();
            while (gd.loggers.move_next())
            {
                if (gd.loggers.element()->is_child_of(*this))
                    gd.loggers.element()->out.rdbuf(out_.rdbuf());
            }

            gd.set_output_stream(logger_name, out_);
        }

        typedef void (*print_header_type)(
            std::ostream& out, 
            const std::string& logger_name, 
            const log_level& l,
            const uint64 thread_id
        );

        print_header_type logger_header (
        ) const { return print_header; }

        void set_logger_header (
            print_header_type ph
        )
        {
            auto_mutex M(gd.m);
            gd.loggers.reset();
            while (gd.loggers.move_next())
            {
                if (gd.loggers.element()->is_child_of(*this))
                    gd.loggers.element()->print_header = ph;
            }

            gd.set_logger_header(logger_name, ph);
        }

    private:


        struct global_data
        {
            rmutex m;
            set<logger*>::kernel_1b loggers;
            map<thread_id_type,uint64>::kernel_1b thread_names;
            uint64 next_thread_name;

            global_data (
            );

            ~global_data(
            );

            uint64 get_thread_name (
            );
            /*!
                requires
                    - m is locked
                ensures
                    - returns a unique id for the calling thread.  also makes the number
                      small and nice unlike what you get from get_thread_id()
            !*/

            void thread_end_handler (
            );
            /*!
                ensures
                    - removes the terminated thread from thread_names
            !*/

            struct level_container
            {
                level_container ();

                log_level val;
                map<std::string,scoped_ptr<level_container> >::kernel_1b_c table;
            } level_table;

            const log_level level (
                const std::string& name
            ) const; 
            /*!
                ensures
                    - returns the level loggers with the given name are supposed 
                      to have
            !*/

            void set_level (
                const std::string& name,
                const log_level& new_level
            );
            /*!
                ensures
                    - for all children C of name:
                        - #level(C) == new_level
                    - if name == "" then
                        - for all loggers L:
                            - #level(L) == new_level
            !*/

            struct auto_flush_container
            {
                bool val;
                map<std::string,scoped_ptr<auto_flush_container> >::kernel_1b_c table;
            } auto_flush_table;

            bool auto_flush (
                const std::string& name
            ) const;
            /*!
                ensures
                    - returns the auto_flush value loggers with the given name are supposed 
                      to have
            !*/

            void set_auto_flush (
                const std::string& name,
                bool enabled
            );
            /*!
                ensures
                    - for all children C of name:
                        - #auto_flush_enabled(C) == enabled 
                    - if name == "" then
                        - for all loggers L:
                            - #auto_flush_enabled(L) == enabled 
            !*/

            struct output_streambuf_container
            {
                std::streambuf* val;
                map<std::string,scoped_ptr<output_streambuf_container> >::kernel_1b_c table;
            } streambuf_table;

            std::streambuf* output_streambuf (
                const std::string& name
            );
            /*!
                ensures
                    - returns the streambuf loggers with the given name are supposed 
                      to have
            !*/

            void set_output_stream (
                const std::string& name,
                std::ostream& out_
            );
            /*!
                ensures
                    - for all children C of name:
                        - #output_streambuf(C) == out_.rdbuf() 
                    - if name == "" then
                        - for all loggers L:
                            - #output_streambuf(L) == out_.rdbuf() 
            !*/

            struct logger_header_container
            {
                print_header_type val;
                map<std::string,scoped_ptr<logger_header_container> >::kernel_1b_c table;
            } header_table;

            print_header_type logger_header (
                const std::string& name
            );
            /*!
                ensures
                    - returns the header function loggers with the given name are supposed 
                      to have
            !*/

            void set_logger_header (
                const std::string& name,
                print_header_type ph
            );
            /*!
                ensures
                    - for all children C of name:
                        - #logger_header(C) == ph 
                    - if name == "" then
                        - for all loggers L:
                            - #logger_header(L) == ph 
            !*/

        }; // end of struct global_data

        static global_data& get_global_data();

        friend void set_all_logging_levels (
            const log_level& new_level
        );

        friend void set_all_logging_output_streams (
            std::ostream& out
        );


        global_data& gd;

        const std::string logger_name;

        print_header_type print_header;
        bool auto_flush_enabled;
        std::ostream out;
        log_level cur_level;


        // restricted functions
        logger(const logger&);        // copy constructor
        logger& operator=(const logger&);    // assignment operator

    };    

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

}

#ifdef NO_MAKEFILE
#include "logger_kernel_1.cpp"
#endif

#endif // DLIB_LOGGER_KERNEl_1_

