/********************************************************************
* CONFIDENTIAL - All source code is the propriety and confidential  *
* information of David Sorber.                                      *
*                                                                   *
* Copyright 2024 David Sorber                                       *
* Unpublished -- all rights reserved under the copyright laws       *
* of the United States.                                             *
********************************************************************/

#ifndef __LOGGER__
#define __LOGGER__

#include <cstring>
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <mutex>
#include <sys/time.h>

#include "CopyManagerOptions.h"


// Defined outside of namespace just for convenience.
// NOTE: when using >= C++14 and >= -O1 the following function will be executed
// at compile time. This avoids the need for the previous CMake path length trick.
inline constexpr size_t PATH_OFFSET(const char* input)
{
    // OTHER NOTE: this assumes truncating the file path after the "src/"
    // portion which is currently valid for copytool source tree organization.
    const char* output = std::strstr(input, "src/");
    if (output == nullptr)
    {
        return 0;
    }
    return (output - input) + 4;
}


namespace DBS
{

// Enumeration representing the log severity levels
enum LOG_LEVEL {off=-1, error=0, warn=1, info=2, debug=3, trace=4};

// Convenience LOG macro definition
#define LOG(sev) DBS::Logger::getInstance(sev).internalLog() \
    << &(__FILE__[PATH_OFFSET(__FILE__)]) << ":" << __LINE__ << "|"

class Logger
{
public:

    /**
     * Logger Destructor
     */
    ~Logger();

    /**
     * Get the singleton instance
     */
    static Logger& getInstance(
        enum LOG_LEVEL sev,
        CopyManagerOptions* optionsObj=nullptr)
    {
        static Logger instance(optionsObj);
        if (sev >= 0)
        {
            // Lock our mutex and set the severity level
            instance.lockMutex();
            instance.setSeverity(sev);
        }
        return instance;
    };

    /**
     * Nested class used to get around the "endl" tag, it automatically
     * handles the "cleanup" at the end of the log message stream in the
     * destructor
     */
    class InternalLogger
    {
    public:

        InternalLogger(enum LOG_LEVEL severity, Logger &outer)
            : m_internalSev(severity),
              r_outerLogger(outer)
        {
        }

        InternalLogger(const InternalLogger &other)
            : m_internalSev(other.m_internalSev),
              r_outerLogger(other.r_outerLogger)
        {
        }

        ~InternalLogger()
        {
            std::string str = m_internalMsgStream.str();
            if (! str.empty())
            {
                r_outerLogger.log_msg(m_internalSev, str);

                // Clean the output stream for future logging
                m_internalMsgStream.str("");
            }

            // Make sure we unlock!
            r_outerLogger.unlockMutex();
        }

        /**
         * Overriding the insertion operator to build log message
         */
        template<typename T>
        InternalLogger& operator<< (const T& msg)
        {
            m_internalMsgStream << msg;
            return *this;
        }

    private:

        enum LOG_LEVEL m_internalSev;
        Logger& r_outerLogger;
        std::stringstream m_internalMsgStream;
    };


    InternalLogger internalLog()
    {
        return InternalLogger(m_severity, *this);
    }

private:

    /**
     * Private constructor (singleton)
     */
    Logger(CopyManagerOptions* configObj);

    /**
     * Set the log severity level
     */
    void setSeverity(enum LOG_LEVEL sev)
    {
        m_severity = sev;
    }

    /**
     * Lock the internal mutex
     */
    void lockMutex()
    {
        m_rloggerMutex.lock();
    }

    /**
     * Unlock the internal mutex
     */
    void unlockMutex()
    {
        m_rloggerMutex.unlock();
    }

    /**
     * Select the output stream based on the specified options
     * @return
     */
    inline std::ostream& getStream()
    {
        return (r_options.getLogToStderr() ? std::cerr : m_logFile);
    }

    /**
     * Write a log message to the log file.
     *
     * @param level - the severity of the message being logged
     * @param message - the message to send
     */
    void log_msg(enum LOG_LEVEL level, const std::string& msg);

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

    /**
     * The log message to write
     */
    std::ostringstream m_outputStream;

    /**
     * Severity level of the log message
     */
    enum LOG_LEVEL m_severity;

    /**
     * Mutex for making this singleton thread-safe
     */
    std::mutex m_rloggerMutex;

    std::ofstream m_logFile;

    CopyManagerOptions& r_options;
};

} // DBS


#endif // __LOGGER__