#pragma once
/* 1) The .eng file logger uses NMEA-like formatted messages.
 * 2) The CPF NMEA-like message looks like this $ tab messageID tab DateTime (ISO8601 format
 *yyyy-mm-ddTHH:mm:ss.(000)Z tab textComment tab Var1 tab Var2 tab Var 3... with no terminating
 *character 3) Every object that logs messages to the .eng file needs a getEngHeader() method
 *accessible by the logger 4) All getEngHeader() methods return a string describing that object's
 *CPF message.  The string is used by Matlab's readtable function. 5) The first line has a valid
 *Matlab variable name for each column in the message with tab separtions.  The second line says
 *what kind of variable is in each column also with tab separations.  Strings are described with
 *"text" and numerics are described with "0.0"
 *
 *STX\tmsgID\tdateTime\tstate\tcomment\tvarName1\tvarName2\tvarName3...\r\ntext\ttext\ttext\ttext\ttext
 *or 0.0\ttext or 0.0\ttext or 0.0.
 *
 * 6) Every string descriptor has to have the STX, msgID, datetime, state and comment columns. As in
 *NMEA messages, the STX value in subsequent messages is "$" which helps with parsing 7) This makes
 *the .eng file virtually self parsing in Matlab.  Adding variable to any object's message only
 *requires adding a valid variable name and variable type to the object's string descriptor in the
 *right places and changing.  New objects writing to the .eng log have to be added to the
 *logEngFileHeaders() method in Logger
 */

#include <cstdint>
#include <string>
#include <system_time.hpp>

enum LogLevels : uint8_t
{
	OFF = 0,
	DEV_ONLY = 1,
	ALWAYS = 2,
	MISSION_INIT = 3
};

/** CPF System Logging Interface
 *
 * There are three primary function variations that you will use:
 * - logLine, which simply takes an already-formatted string and writes it to the file as-is
 * - logEngLine(component_id, args...), which formats the supplied variadic argument list into
 *    the appropriate engineering log format. The current time will be inserted at the appropriate
 * location.
 *     @code
 *     Logger::logEngLine(VERBOSE, "param1", "param2", string_param);
 *     @endcode
 * - logEngLine(component_id, DateTimeStruct, args...), which uses the supplied time information to
 *    generate a time string and insert it at the appropriate spot.
 *     @code
 *     Logger::logEngLine(VERBOSE, timestamp, "param1", "param2", string_param);
 *     @endcode
 *
 * The proper variation of logEngLine will be selected depending on whether or not a DateTimeStruct
 * is supplied as an input parameter.
 *
 * The general format of a logged engineering line is:
 * - STX + component_id + DELIM + timestamp + DELIM + current state + [(DELIM + args)...] + ETX
 */
class Logger
{
  public:
	static bool initialize(uint16_t profileNum);
	static bool stop();
	static bool isLoggerReady();

	static void setStateLabel(const std::string& label);

	/** Save a line to the log file without further modification.
	 *
	 * This logging function should be used to write contents to the log file.
	 * Unlike logEngLine, this function does not modify the supplied contents or
	 * coerce them into any special format.
	 *
	 * @pre Logger module has been initialized
	 * @post Input line has been logged to the currently open log file without
	 *  further modification.
	 *
	 * @param verbosity [in] Verbosity level of the log statement.
	 * @param line [in] The string contents to write to the log file.
	 */
	static unsigned logLine(LogLevels verbosity, const std::string_view line);

	/** Logger "worker" function
	 *
	 * This function does the work of taking a list of string arguments and
	 * formatting them into the proper engineering line format, e.g.,
	 * [STX]param1[delimiter]param2[delimiter]param3[ETX].
	 *
	 * The actual formatting is controlled by the implementation of this function.
	 *
	 * Users are not required to invoke this function unless it is desired
	 * to use a std::initializer_list. Instead the templated logEngLine variant
	 * is intended to be used - this will coerce all the arguments into a list
	 * and invoke this function.
	 *
	 * If you wish to use this version, parameters can be suppiled either with an
	 * initializer_list variable, or in the following manner:
	 * @code
	 * Logger::logEngLine(VERBOSE, {"param1", "param2", string_param});
	 * @endcode
	 *
	 * @pre Logger module has been initialized
	 * @post Arguments have been formatted into engineering line format and saved
	 *  to the currently open log file.
	 *
	 * @param verbosity [in] Verbosity level of the log statement.
	 * @param args [in] List of arguments to format into an engineering log
	 *
	 * @input A std::initializer_list of string_view arguments that
	 */
	static void logEngLine(LogLevels verbosity, const std::initializer_list<std::string_view> args);

	/** Log a list of parameters in engineering line format (internal timestamping)
	 *
	 * This function does the work of taking a list of arguments and putting them
	 * into the target enginering line format, such as:
	 *  [STX]param1[delimiter]param2[delimiter]param3...[ETX].
	 *
	 * When calling this variant of the function, a timestamp will automatically be taking
	 * and inserted at the proper location.
	 *
	 * The actual formatting is controlled by the std::initializer_list variation
	 * of logEngLine. This function is invoked like:
	 *
	 * @code
	 * Logger::logEngLine(ALWAYS, component_id_string, "param1", "param2", string_param);
	 * @endcode
	 *
	 *
	 * @pre Logger module has been initialized
	 * @post Arguments have been formatted into engineering line format and saved
	 *  to the currently open log file.
	 *
	 * @param [in] component_id The name of the component that made teh log statement.
	 * @param [in] vs... Variadic list of values that are to be logged in engineering
	 * line format. The parameters can be any type that can be implicitly converted
	 * to std::string_view, including:
	 *  - const char*
	 *  - char[]
	 *  - std::string
	 *  - std::string_view
	 *
	 */
	template<typename... Values>
	static inline void logEngLine(LogLevels verbosity, std::string_view component_id,
								  Values const&... vs)
	{
		auto timestamp = systime::getTimestamp();
		logEngLine(verbosity, {component_id, timestamp.data(), currentStateLabel, vs...});
	}

	/** Log a list of parameters in engineering line format (external timestamping)
	 *
	 * This function does the work of taking a list of arguments and putting them
	 * into the target enginering line format, such as:
	 *  [STX]param1[delimiter]param2[delimiter]param3...[ETX].
	 *
	 * This variant of the logging function is used when there is an external DateTimeStruct
	 * provided as an input source. In this case, the logger will not
	 *
	 * The actual formatting is controlled by the std::initializer_list variation
	 * of logEngLine. This function is invoked like:
	 *
	 * @code
	 * Logger::logEngLine(ALWAYS, component_id_string, data_acquisition_time,
	 *  "param1", "param2", string_param);
	 * @endcode
	 *
	 *
	 * @pre Logger module has been initialized
	 * @post Arguments have been formatted into engineering line format and saved
	 *  to the currently open log file.
	 *
	 * @param [in] component_id The name of the component that made teh log statement.
	 * @param [in] tickstamp The timestamp that will be used with the logging statement.
	 *  This DateTimeStruct will be automatically convered to the appropriate string format
	 *  as part of the process.
	 * @param [in] vs... Variadic list of values that are to be logged in engineering
	 * line format. The parameters can be any type that can be implicitly converted
	 * to std::string_view, including:
	 *  - const char*
	 *  - char[]
	 *  - std::string
	 *  - std::string_view
	 */
	template<typename... Values>
	static inline void logEngLine(LogLevels verbosity, std::string_view component_id,
								  systime::DateTimeStruct tickstamp, Values const&... vs)
	{
		auto timestamp = systime::dateTimeToTimestamp(tickstamp);
		logEngLine(verbosity, {component_id, timestamp.data(), currentStateLabel, vs...});
	}

	// TODO: when all uses of these variables have been eliminated following
	// the conversion to the new function, these variable declarations can be
	// moved to Logger.cpp
	static constexpr char STX = '$';
	static constexpr char ETX = '\n';
	static constexpr char DELIMITER = '\t';

	static void logEngFileHeader();

  private:
	Logger() = default;

	static std::string currentStateLabel;
};
