/*
 * Copyright 2012 Telenav Inc. All rights reserved.
 *
 * TeleNav logging system. See documentation at 
 * http://spaces.telenav.com:8080/pages/viewpage.action?pageId=29622335 and
 * http://spaces.telenav.com:8080/display/auto/Using+new+logging+system
 */
#ifndef TN_FOUNDATION_SIMPLE_LOG_HPP
#define TN_FOUNDATION_SIMPLE_LOG_HPP

#include <iostream>
#include <sstream>
#include <map>
#include <list>
#include <string>

#include <foundation/private/init.hpp>

namespace tn {
namespace simple_log {

enum LogLevel {
    /**
     * Use DISABLED to temporarily disable a logging statement (i.e. before 
     * checking in something that you'd need later, but that would generate too
     * much output even for VERBOSE level) 
     */
    LogLevel_Disabled = 0,
    
    /**    
     * Use ERROR level when something that end-user requested could not be 
     * completed successfully. Note that low level components often don't have 
     * enough context to decide whether end-user is affected, so they usually 
     * just return an error and log at VERBOSE or INFO (if at all)
     */
    LogLevel_Error,
    
    /**    
     * Use WARNING level when something unexpected or unusual happened but 
     * your code was still able to complete the request successfully.
     */
    LogLevel_Warning,
    
    /**
     * Use INFO level to provide forensic evidence for 
     * investigation of errors and establish enough context useful for 
     * understanding and reproducing errors.
     */
    LogLevel_Info,
    
    /**
     * Use VERBOSE level for logging information that is too verbose or low 
     * level to be logged as INFO
     */
    LogLevel_Verbose
};

} // namespace simple_log

} // namespace tn


std::ostream& operator<<(std::ostream& out, tn::simple_log::LogLevel level);


namespace tn {

namespace simple_log {

/** The Topic object is how logging for specific topics can be enabled
 *  and disabled individually and dynamically. They are a required
 *  argument to the logging stream operator.  Instances of the Topic
 *  object should be declared as static or global objects to avoid the
 *  dictionary lookup of the name upon construction. That's why
 *  they're a separate class from the Log class.
 */

struct Topic {

    Topic();
    Topic(char const * name);

    /**  Set the logging level for a given topic. If a message has
     *  equal or higher priority, it will be output to the
     *  platform-specific logging facility.
     */
    void  SetLevel(LogLevel level) {iter->second = level;}
    LogLevel GetLevel() const {return iter->second;}


    /**  
     * Set level for ALL topics 
     */
    static void SetAllLevels(LogLevel level);

    /** 
     * Get default log level
     */
    static LogLevel GetDefaultLevel();
    
    /**
     * Gets client a list of active topics so they can set the levels
     * interactively.
     */
    static std::list<Topic> GetTopics();
    
    bool  IsEnabled(LogLevel level) const;
    bool  IsDisabled() const;
    
    std::string const & GetName() const {return iter->first;}

    static Topic const & GetDefaultTopic() {return *defaultTopic;}

    // To be called by the Init object only.
    static void Init(bool);

    typedef std::map<std::string, LogLevel> StatusMap;
    typedef std::pair<std::string, LogLevel> StatusMapEntry;

private:

    static StatusMap::iterator GetIter(char const *);
    static StatusMap* statusMapPtr; //use global anonymous guard to protect the map
    static Topic* defaultTopic;
    static LogLevel defaultLogLevel;

    //Copy of iterator into statusMapPtr (so we can set the log level)
    StatusMap::iterator iter;

    Topic(StatusMap::iterator const &);
};


struct Log {

    Log();

    ~Log();

    template <typename T>
    Log& operator<<(T const & item) {
        out << item;
        return *this;
    }

    /*  For our custom stream manipulators.
     */
    typedef Log& (*Manipulator)(Log&);
    Log& operator<<(Manipulator manip);


    /*  For standard manipulators.
     */
    typedef std::basic_ostream<char, std::char_traits<char> > CoutType;
    typedef CoutType& (*StandardEndLine)(CoutType&);
    Log& operator<<(StandardEndLine manip);
    

    // workaround for broken hex implementation on some platforms
    void hex();

    /*  SetStream must be used with care. It stores a pointer to the
     *  stream. Don't delete the stream object prematurely. */

    static void SetStream(std::ostream &);
    static void ResetStream();

private:

    // Platform-specific logging.
    static void log(std::string const &);

    std::ostringstream out;

    static std::ostream * stream;
};


extern Log nullstream;

namespace {

::tn::foundation::Init topicInit(&Topic::Init);

} // namespace


/*  In theory the hex stream function shouldn't be necessary since the
 *  code is designed to work with the standard versions, but the
 *  WinCE-zephyr implementation seems to be non-standard.
 *
 */

Log& hex(Log& log);


inline LogLevel SetLogLevel(LogLevel level) 
{
    LogLevel oldLevel = Topic::GetDefaultLevel();
    Topic::SetAllLevels(level);
    return oldLevel;
}

inline bool IsEnabled(LogLevel level)
{
    return Topic::GetDefaultTopic().IsEnabled(level);
}

inline bool IsEnabled(LogLevel level, Topic const & topic)
{
    return topic.IsEnabled(level);
}

inline void SetLogStream(std::ostream & out) {
    Log::SetStream(out);
}

inline void ResetLogStream() {
    Log::ResetStream();
}

} // namespace simple_log

} // namespace tn

std::ostream& operator<<(std::ostream& out, tn::simple_log::LogLevel level);

tn::simple_log::LogLevel const TnDisabled = tn::simple_log::LogLevel_Disabled;
tn::simple_log::LogLevel const TnError = tn::simple_log::LogLevel_Error;
tn::simple_log::LogLevel const TnWarning = tn::simple_log::LogLevel_Warning;
tn::simple_log::LogLevel const TnInfo = tn::simple_log::LogLevel_Info;
tn::simple_log::LogLevel const TnVerbose = tn::simple_log::LogLevel_Verbose;

#define TN_LOG_DISABLED   tn::simple_log::LogLevel_Disabled
#define TN_LOG_ERROR      tn::simple_log::LogLevel_Error
#define TN_LOG_WARNING    tn::simple_log::LogLevel_Warning
#define TN_LOG_INFO       tn::simple_log::LogLevel_Info
#define TN_LOG_VERBOSE    tn::simple_log::LogLevel_Verbose


typedef tn::simple_log::Topic TnLogTopic;
typedef tn::simple_log::LogLevel TnLogLevel;

#define TN_LOG(LEVEL)                                            \
    !tn::simple_log::IsEnabled(LEVEL)                                  \
    ?                                                           \
    tn::simple_log::nullstream                                         \
    :                                                           \
    tn::simple_log::Log()                                              \
    << LEVEL << "(" << __FILE__ << ":" << __LINE__ << "): "

#define TN_LOGT(LEVEL, TOPIC)                                    \
    !tn::simple_log::IsEnabled(LEVEL, TOPIC)                           \
    ?                                                           \
    tn::simple_log::nullstream                                         \
    :                                                           \
    tn::simple_log::Log()                                              \
    << LEVEL << "(" << TOPIC.GetName() << "): "

#define TN_LOG_IF(LEVEL, CONDITION)                              \
    !(tn::simple_log::IsEnabled(LEVEL) && (CONDITION))                 \
    ?                                                           \
    tn::simple_log::nullstream                                         \
    :                                                           \
    tn::simple_log::Log()                                              \
    << LEVEL << "(" << __FILE__ << ":" << __LINE__ << "): "

#define TN_LOGT_IF(LEVEL, TOPIC, CONDITION)                      \
    !(tn::simple_log::IsEnabled(LEVEL, TOPIC) && (CONDITION))          \
    ?                                                           \
    tn::simple_log::nullstream                                         \
    :                                                           \
    tn::simple_log::Log()                                              \
    << LEVEL << "(" << TOPIC.GetName() << "): "


#endif /* TN_FOUNDATION_SIMPLE_LOG_HPP */

