#pragma once

#include <initializer_list>

#include "Types.h"
#include "Array.h"
#include "UTF.h"
#include "Str.h"

namespace ehs
{
	class Log;

	typedef void (*LogRaisedCb)(const Log &);
	typedef void (*LogOutputCb)(const Array<Log> &);

	enum class LogType : UInt_8
	{
		SUCCESS,
		ERR,
		WARN,
		INFO
	};

	/// A helper class for holding error information and handling them.
	/// @tparam T The character data type to use.
	/// @tparam N The number data type to use.
	class EHS_LIB_IO Log
	{
	private:
		static void DefaultRaisedCb(const Log &log);

		static void DefaultOutputCb(const Array<Log> &logs);

        static LogRaisedCb raisedCb;
		static LogOutputCb outputCb;
		static Array<Log> logs;
		static Log lastLog;
		static bool immediate;
		LogType type;
		Array<Str_8> tags;
		UInt_64 code;
		Str_8 msg;

	public:
        static void SetRaisedCallback(LogRaisedCb newCb);

		static void SetOutputCallback(LogOutputCb newCb);

		static void OnExit();

        static void Raise(Log log);

		/// Retrieves the last log raised.
		static Log GetLastLog();

		static void EnableImmediateMode(bool enable);

        /// Default members initialization.
		Log();

        /// Initializes members with the given information.
        /// @param [in] tags The tags to associate this log with.
		/// @param [in] code The unique code to use.
        /// @param [in] msg Detailed information about what happened.
		Log(LogType type, const std::initializer_list<Str_8> &tags, UInt_64 code, Str_8 msg);

        /// Initializes members with the given information.
        /// @param [in] tags The tags to associate this log with.
		/// @param [in] code The unique code to use.
        /// @param [in] msg Detailed information about what happened.
        Log(LogType type, Array<Str_8> tags, UInt_64 code, Str_8 msg);

		Log(Log &&log) noexcept;

        /// Copies all members from the given log.
        /// @param [in] log The log to copy from.
		Log(const Log &log);

		Log &operator=(Log &&log) noexcept;

        /// Copies all members from the given log.
        /// @param [in] log The log to copy from.
        /// @returns The log that has been assigned to.
        Log &operator=(const Log &log);

		/*
        /// Compares with another given log.
        /// @param [in] log The log to compare with.
        /// @returns Whether or not they are equal.
		bool operator==(const Log log);

        /// Compares with another given log.
        /// @param [in] log The log to compare with.
        /// @returns Whether or not they are equal.
		bool operator!=(const Log log);
		*/

		LogType GetType() const;

        /// Checks whether or not this log has the given tags.
        /// @param [in] tags The tags to look for.
        /// @returns True if all tags were found, otherwise false.
		bool HasTags(const std::initializer_list<Str_8> &tags) const;

        /// Checks whether or not this log has the given tags.
        /// @param [in] tags The tags to look for.
        /// @returns True if all tags were found, otherwise false.
		bool HasTags(const Array<Str_8> &tags) const;

		/// Checks whether or not this log has the given tag.
		/// @param [in] tag The tag to look for.
		/// @returns True if tag was found, otherwise false.
		bool HasTag(const Str_8 &tag) const;

		/// Retrieves all the tags.
		/// @returns The result.
        const Array<Str_8> &GetTags() const;

		UInt_64 GetCode() const;

		/// Retrieves the detailed error message string.
		/// @returns The error message.
		Str_8 GetMsg() const;

		Str_8 ToStr() const;

		/// Retrieves whether or not this is a valid object.
		/// @returns The result.
		/// @note To be a valid object it must have one or more tags and a message size greater than zero.
		bool IsValid() const;
	};
}

#ifndef EHS_LOG_INT
	#ifdef EHS_DEBUG
		#define EHS_LOG_INT(type, code, msg) ehs::Log::Raise({type, {ehs::GetAcronym_8(), EHS_FILE, EHS_FUNC, ehs::Str_8::FromNum((ehs::UInt_32)EHS_LINE)}, code, msg})
	#else
		#define EHS_LOG_INT(type, code, msg) ehs::Log::Raise({type, {ehs::GetAcronym_8(), EHS_FUNC}, code, msg})
	#endif
#endif

#ifndef EHS_LOG
	#ifdef EHS_DEBUG
		#define EHS_LOG(type, code, msg) ehs::Log::Raise({type, {ehs::GetAppName_8(), EHS_FILE, EHS_FUNC, ehs::Str_8::FromNum((ehs::UInt_32)EHS_LINE)}, code, msg})
	#else
		#define EHS_LOG(type, code, msg) ehs::Log::Raise({type, {ehs::GetAppName_8(), EHS_FUNC}, code, msg})
	#endif
#endif

#ifndef EHS_LOG_SUCCESS
#define EHS_LOG_SUCCESS() ehs::Log::Raise({})
#endif