/*
===========================================================================
Daemon BSD Source Code
Copyright (c) 2013-2014, Daemon Developers
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the <organization> nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
===========================================================================
*/

#include <string>

#include "Cvar.h"
#include "String.h"

#ifndef COMMON_LOG_H_
#define COMMON_LOG_H_

namespace Log {

    /*
     * There are 4 log levels that code can use:
     *   - ERROR when something bad happens TODO: make it crash like Com_Error?
     *   - WARNING when something is not going as expected, it is very visible.
     *   - NOTICE when we want to say something interesting, not very visible.
     *   - DEBUG shouldn't be visible by default.
     */

    enum Level {
        DEBUG,
        NOTICE,
        WARNING,
        ERROR
    };

    // The default filtering level
    const Level DEFAULT_FILTER_LEVEL = WARNING;

    /*
     * Loggers are used to group logs by subsystems and allow logs
     * to be filtered by log level by subsystem. They are used like so
     * in a submodule "Foo" in a module "bar"
     *
     *   static Logger fooLog("bar.foo"); //filters with the default filtering level
     *
     *   fooLog.Warn("%s %i", string, int); //"appends" the newline automatically
     *   fooLog.Debug(<expensive formatting>); //if the log is filtered, no formatting occurs
     *
     * In addition the user/developer can control the filtering level with
     *   /set logs.logLevel.foo.bar {error, warning, info, debug}
     */

    class Logger {
        public:
            Logger(Str::StringRef name, Level defaultLevel = DEFAULT_FILTER_LEVEL);

            template<typename ... Args>
            void Error(Str::StringRef format, Args&& ... args);

            template<typename ... Args>
            void Warn(Str::StringRef format, Args&& ... args);

            template<typename ... Args>
            void Notice(Str::StringRef format, Args&& ... args);

            template<typename ... Args>
            void Debug(Str::StringRef format, Args&& ... args);

        private:
            // the cvar logs.logLevel.<name>
            Cvar::Cvar<Level> filterLevel;
    };

    /*
     * When debugging a function or before a logger is introduced for
     * a module the following functions can be used for less typing.
     * However it shouldn't stay in production code because it will
     * cannot be filtered and will spoil the console.
     */

    template<typename ... Args>
    void Error(Str::StringRef format, Args&& ... args);

    template<typename ... Args>
    void Warn(Str::StringRef format, Args&& ... args);

    template<typename ... Args>
    void Notice(Str::StringRef format, Args&& ... args);

    template<typename ... Args>
    void Debug(Str::StringRef format, Args&& ... args);

    /*
     * A log Event, sent to the log system along a list of targets to output
     * it to. Event are not all generated by the loggers (e.g. kill messages)
     */

    struct Event {
        Event(int timestamp, std::string text)
            : timestamp(timestamp), text(std::move(text)) {}
        int timestamp;
        std::string text;
    };

    /*
     * The list of potential targets for a log event.
     * TODO: avoid people having to do (1 << TARGET1) | (1 << TARGET2) ...
     */

    enum TargetId {
        GRAPHICAL_CONSOLE,
        TTY_CONSOLE,
        CRASHLOG,
        LOGFILE,
        GAMELOG,
        HUD,
        MAX_TARGET_ID
    };

    //Internals

    // Functions used for Cvar<Log::Level>
    bool ParseCvarValue(std::string value, Log::Level& result);
    std::string SerializeCvarValue(Log::Level value);

    // Common entry points for all the formatted logs of the same level
    // (decide to which log targets the event goes)
    void CodeSourceError(std::string message);
    void CodeSourceWarn(std::string message);
    void CodeSourceNotice(std::string message);
    void CodeSourceDebug(std::string message);

    // Implementation of templates

    // Logger

    template<typename ... Args>
    void Logger::Error(Str::StringRef format, Args&& ... args) {
        CodeSourceError(Str::Format(format, std::forward<Args>(args) ...));
    }

    template<typename ... Args>
    void Logger::Warn(Str::StringRef format, Args&& ... args) {
        if (filterLevel.Get() <= WARNING) {
            CodeSourceWarn(Str::Format(format, std::forward<Args>(args) ...));
        }
    }

    template<typename ... Args>
    void Logger::Notice(Str::StringRef format, Args&& ... args) {
        if (filterLevel.Get() <= NOTICE) {
            CodeSourceNotice(Str::Format(format, std::forward<Args>(args) ...));
        }
    }

    template<typename ... Args>
    void Logger::Debug(Str::StringRef format, Args&& ... args) {
        if (filterLevel.Get() <= DEBUG) {
            CodeSourceDebug(Str::Format(format, std::forward<Args>(args) ...));
        }
    }

    // Quick Logs

    template<typename ... Args>
    void Error(Str::StringRef format, Args&& ... args) {
        CodeSourceError(Str::Format(format, std::forward<Args>(args) ...));
    }

    template<typename ... Args>
    void Warn(Str::StringRef format, Args&& ... args) {
        CodeSourceWarn(Str::Format(format, std::forward<Args>(args) ...));
    }

    template<typename ... Args>
    void Notice(Str::StringRef format, Args&& ... args) {
        CodeSourceNotice(Str::Format(format, std::forward<Args>(args) ...));
    }

    template<typename ... Args>
    void Debug(Str::StringRef format, Args&& ... args) {
        CodeSourceDebug(Str::Format(format, std::forward<Args>(args) ...));
    }
}

namespace Cvar {
    template<>
    std::string GetCvarTypeName<Log::Level>();
}

#endif //COMMON_LOG_H_
