#ifndef PCAPPP_LOGGER
#define PCAPPP_LOGGER

#include <stdio.h>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <stdint.h>

#ifndef LOG_MODULE
#define LOG_MODULE UndefinedLogModule
#endif

/// @file

/**
 * \namespace pcpp
 * \brief The main namespace for the PcapPlusPlus lib
 */
namespace pcpp
{

	/**
	 * An enum representing all PcapPlusPlus modules
	 */
	enum LogModule
	{
		UndefinedLogModule,
		CommonLogModuleIpUtils, ///< IP Utils module (Common++)
		CommonLogModuleTablePrinter, ///< Table printer module (Common++)
		CommonLogModuleGenericUtils, ///< Generic Utils (Common++)
		PacketLogModuleRawPacket, ///< RawPacket module (Packet++)
		PacketLogModulePacket, ///< Packet module (Packet++)
		PacketLogModuleLayer, ///< Layer module (Packet++)
		PacketLogModuleArpLayer, ///< ArpLayer module (Packet++)
		PacketLogModuleEthLayer, ///< EthLayer module (Packet++)
		PacketLogModuleIPv4Layer, ///< IPv4Layer module (Packet++)
		PacketLogModuleIPv6Layer, ///< IPv6Layer module (Packet++)
		PacketLogModulePayloadLayer, ///< PayloadLayer module (Packet++)
		PacketLogModuleTcpLayer, ///< TcpLayer module (Packet++)
		PacketLogModuleUdpLayer, ///< UdpLayer module (Packet++)
		PacketLogModuleVlanLayer, ///< VlanLayer module (Packet++)
		PacketLogModuleHttpLayer, ///< HttpLayer module (Packet++)
		PacketLogModulePPPoELayer, ///< PPPoELayer module (Packet++)
		PacketLogModuleDnsLayer, ///< DnsLayer module (Packet++)
		PacketLogModuleMplsLayer, ///< MplsLayer module (Packet++)
		PacketLogModuleIcmpLayer, ///< IcmpLayer module (Packet++)
		PacketLogModuleGreLayer, ///< GreLayer module (Packet++)
		PacketLogModuleSSLLayer, ///< SSLLayer module (Packet++)
		PacketLogModuleSllLayer, ///< SllLayer module (Packet++)
		PacketLogModuleDhcpLayer, ///< DhcpLayer module (Packet++)
		PacketLogModuleDhcpV6Layer, ///< DhcpV6Layer module (Packet++)
		PacketLogModuleIgmpLayer, ///< IgmpLayer module (Packet++)
		PacketLogModuleSipLayer, ///< SipLayer module (Packet++)
		PacketLogModuleSdpLayer, ///< SdpLayer module (Packet++)
		PacketLogModuleRadiusLayer, ///< RadiusLayer module (Packet++)
		PacketLogModuleGtpLayer, ///< GtpLayer module (Packet++)
		PacketLogModuleBgpLayer, ///< GtpLayer module (Packet++)
		PacketLogModuleSSHLayer, ///< SSHLayer module (Packet++)
		PacketLogModuleTcpReassembly, ///< TcpReassembly module (Packet++)
		PacketLogModuleIPReassembly, ///< IPReassembly module (Packet++)
		PacketLogModuleIPSecLayer, ///< IPSecLayers module (Packet++)
		PacketLogModuleNtpLayer, ///< NtpLayer module (Packet++)
		PcapLogModuleWinPcapLiveDevice, ///< WinPcapLiveDevice module (Pcap++)
		PcapLogModuleRemoteDevice, ///< WinPcapRemoteDevice module (Pcap++)
		PcapLogModuleLiveDevice, ///< PcapLiveDevice module (Pcap++)
		PcapLogModuleFileDevice, ///< FileDevice module (Pcap++)
		PcapLogModulePfRingDevice, ///< PfRingDevice module (Pcap++)
		PcapLogModuleMBufRawPacket, ///< MBufRawPacket module (Pcap++)
		PcapLogModuleDpdkDevice, ///< DpdkDevice module (Pcap++)
		PcapLogModuleKniDevice, ///< KniDevice module (Pcap++)
		NetworkUtils, ///< NetworkUtils module (Pcap++)
		NumOfLogModules
	};

	/**
	 * @class Logger
	 * PcapPlusPlus logger manager.
	 * PcapPlusPlus uses this logger to output both error and debug logs.
	 * There are currently 3 log levels: Logger#Error, Logger#Info and Logger#Debug.
	 *
	 * PcapPlusPlus is divided into modules (described in #LogModule enum). The user can set the log level got each module or to all modules at once.
	 * The default is Logger#Info which outputs only error messages. Changing log level for modules can be done dynamically while the application is running.
	 *
	 * The logger also exposes a method to retrieve the last error log message.
	 *
	 * Logs are printed to console by default in a certain format. The user can set a different print function to change the format or to print to
	 * other media (such as files, etc.).
	 *
	 * PcapPlusPlus logger is a singleton which can be reached from anywhere in the code.
	 *
	 * Note: Logger#Info level logs are currently only used in DPDK devices to set DPDK log level to RTE_LOG_NOTICE.
	 */
	class Logger
	{
	public:
		/**
		 * An enum representing the log level. Currently 3 log levels are supported: Error, Info and Debug. Info is the default log level
		 */
		enum LogLevel
		{
			Error, ///< Error log level
			Info, ///< Info log level
			Debug ///< Debug log level
		};

		/**
		 * @typedef LogPrinter
		 * Log printer callback. Used for printing the logs in a custom way.
		 * @param[in] logLevel The log level for this log message
		 * @param[in] logMessage The log message
		 * @param[in] file The source file in PcapPlusPlus code the log message is coming from
		 * @param[in] method The method in PcapPlusPlus code the log message is coming from
		 * @param[in] line The line in PcapPlusPlus code the log message is coming from
		 */
		typedef void (*LogPrinter)(LogLevel logLevel, const std::string& logMessage, const std::string& file, const std::string& method, const int line);

		/**
		 * A static method for converting the log level enum to a string.
		 * @param[in] logLevel A log level enum
		 * @return The log level as a string
		 */
		static std::string logLevelAsString(LogLevel logLevel);

		/**
		 * Get the log level for a certain module
		 * @param[in] module PcapPlusPlus module
		 * @return The log level set for this module
		 */
		LogLevel getLogLevel(LogModule module) { return m_LogModulesArray[module]; }

		/**
		 * Set the log level for a certain PcapPlusPlus module
		 * @param[in] module PcapPlusPlus module
		 * @param[in] level The log level to set the module to
		 */
		void setLogLevel(LogModule module, LogLevel level) { m_LogModulesArray[module] = level; }

		/**
		 * Check whether a certain module is set to debug log level
		 * @param[in] module PcapPlusPlus module
		 * @return True if this module log level is "debug". False otherwise
		 */
		bool isDebugEnabled(LogModule module) const { return m_LogModulesArray[module] == Debug; }

		/**
		 * Set all PcapPlusPlus modules to a certain log level
		 * @param[in] level The log level to set all modules to
		 */
		void setAllModlesToLogLevel(LogLevel level) { for (int i=1; i<NumOfLogModules; i++) m_LogModulesArray[i] = level; }

		/**
		 * Set a custom log printer.
		 * @param[in] printer A log printer function that will be called for every log message
		 */
		void setLogPrinter(LogPrinter printer) { m_LogPrinter = printer; }

		/**
		 * Set the log printer back to the default printer
		 */
		void resetLogPrinter() { m_LogPrinter = &defaultLogPrinter; }

		/**
		 * @return Get the last error message
		 */
		std::string getLastError() { return m_LastError; }

		/**
		 * Suppress logs in all PcapPlusPlus modules
		 */
		void suppressLogs() { m_LogsEnabled = false; }

		/**
		 * Enable logs in all PcapPlusPlus modules
		 */
		void enableLogs() { m_LogsEnabled = true; }

		/**
		 * Get an indication if logs are currently enabled.
		 * @return True if logs are currently enabled, false otherwise
		 */
		bool logsEnabled() const { return m_LogsEnabled; }

		template<class T>
		Logger& operator<<(const T& msg)
		{
			(*m_LogStream) << msg;
			return *this;
		}

		std::ostringstream * internalCreateLogStream();

		/**
		 * An internal method to print log messages. Shouldn't be used externally.
		 */
		void internalPrintLogMessage(std::ostringstream* logStream, Logger::LogLevel logLevel, const char* file, const char* method, int line);

		/**
		 * Get access to Logger singleton
		 * @todo: make this singleton thread-safe/
		 * @return a pointer to the Logger singleton
		**/
		static Logger& getInstance()
		{
			static Logger instance;
			return instance;
		}
	private:
		bool m_LogsEnabled;
		Logger::LogLevel m_LogModulesArray[NumOfLogModules];
		LogPrinter m_LogPrinter;
		std::string m_LastError;
		std::ostringstream* m_LogStream;

		// private c'tor - this class is a singleton
		Logger();

		static void defaultLogPrinter(LogLevel logLevel, const std::string& logMessage, const std::string& file, const std::string& method, const int line);
	};

#define PCPP_LOG_DEBUG(message) do \
	{ \
		if (pcpp::Logger::getInstance().logsEnabled() && pcpp::Logger::getInstance().isDebugEnabled(LOG_MODULE)) \
		{ \
			std::ostringstream* sstream = pcpp::Logger::getInstance().internalCreateLogStream(); \
			(*sstream) << message; \
			pcpp::Logger::getInstance().internalPrintLogMessage(sstream, pcpp::Logger::Debug, __FILE__, __FUNCTION__, __LINE__); \
		} \
	} while(0)

#define PCPP_LOG_ERROR(message) do \
	{ \
        std::ostringstream* sstream = pcpp::Logger::getInstance().internalCreateLogStream(); \
  		(*sstream) << message; \
		pcpp::Logger::getInstance().internalPrintLogMessage(sstream, pcpp::Logger::Error, __FILE__, __FUNCTION__, __LINE__); \
	} while (0)

} // namespace pcpp

#endif /* PCAPPP_LOGGER */
