/*
 * Copyright 2015 - 2025 gary@drinkingtea.net
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

#pragma once

#ifdef OX_USE_STDLIB
#include <array>
#endif

#include "istring.hpp"
#include "def.hpp"
#include "fmt.hpp"
#include "hashmap.hpp"
#include "string.hpp"
#include "typetraits.hpp"
#include "units.hpp"

extern "C" {

void oxTraceInitHook();

void oxTraceHook(const char *file, int line, const char *ch, const char *msg);

}

namespace ox::trace {

enum class MsgId: char {
	Init = 2,
	TraceEvent = 1,
	Json = '{',
};

struct TraceMsgRcv {
	static constexpr auto TypeName = "net.drinkingtea.ox.trace.TraceMsg";
	static constexpr auto TypeVersion = 1;
	BasicString<50> file{""};
	int line = 0;
	uint64_t time = 0;
	BasicString<50> ch{""};
	BasicString<100> msg;
};

template<typename T>
constexpr Error model(T *io, ox::CommonPtrWith<TraceMsgRcv> auto *obj) noexcept {
	OX_RETURN_ERROR(io->template setTypeInfo<TraceMsgRcv>());
	OX_RETURN_ERROR(io->field("file", &obj->file));
	OX_RETURN_ERROR(io->field("line", &obj->line));
	OX_RETURN_ERROR(io->field("time", &obj->time));
	OX_RETURN_ERROR(io->field("ch", &obj->ch));
	OX_RETURN_ERROR(io->field("msg", &obj->msg));
	return {};
}

struct TraceMsg {
	static constexpr auto TypeName = "net.drinkingtea.ox.trace.TraceMsg";
	static constexpr auto TypeVersion = 1;
	const char *file = "";
	int line = 0;
	uint64_t time = 0;
	const char *ch = "";
	BasicString<100> msg;
};

template<typename T>
constexpr Error model(T *io, ox::CommonPtrWith<TraceMsg> auto *obj) noexcept {
	OX_RETURN_ERROR(io->template setTypeInfo<TraceMsg>());
	OX_RETURN_ERROR(io->fieldCString("file", &obj->file));
	OX_RETURN_ERROR(io->field("line", &obj->line));
	OX_RETURN_ERROR(io->field("time", &obj->time));
	OX_RETURN_ERROR(io->fieldCString("ch", &obj->ch));
	OX_RETURN_ERROR(io->field("msg", &obj->msg));
	return {};
}


struct InitTraceMsgRcv {
	static constexpr auto TypeName = "net.drinkingtea.ox.trace.InitTraceMsg";
	static constexpr auto TypeVersion = 1;
	ox::String appName;
};

template<typename T>
constexpr Error model(T *io, ox::CommonPtrWith<InitTraceMsgRcv> auto *obj) noexcept {
	OX_RETURN_ERROR(io->template setTypeInfo<InitTraceMsgRcv>());
	OX_RETURN_ERROR(io->field("appName", &obj->appName));
	return {};
}


struct InitTraceMsg {
	static constexpr auto TypeName = "net.drinkingtea.ox.trace.InitTraceMsg";
	static constexpr auto TypeVersion = 2;
	ox::BasicString<128> appName;
};

template<typename T>
constexpr Error model(T *io, ox::CommonPtrWith<InitTraceMsg> auto *obj) noexcept {
	OX_RETURN_ERROR(io->template setTypeInfo<InitTraceMsg>());
	OX_RETURN_ERROR(io->field("appName", &obj->appName));
	return {};
}

class Logger {
	public:
		constexpr virtual ~Logger() noexcept = default;
		virtual ox::Error send(const TraceMsg&) noexcept = 0;
		virtual ox::Error sendInit(const InitTraceMsg&) noexcept = 0;
};

/**
 * @param logger pointer to the new logger, does NOT take ownership
 */
void setLogger(Logger *logger) noexcept;

void send(const TraceMsg &msg) noexcept;

class OutStream {

	protected:
		const char *m_delimiter = " ";
		TraceMsg m_msg;

	public:
		constexpr OutStream(const char *file, int line, const char *ch, const StringView &msg) noexcept {
			m_msg.file = file;
			m_msg.line = line;
			m_msg.ch = ch;
			m_msg.msg = BasicString<100>(msg);
		}

		constexpr OutStream(const char *file, int line, const char *ch, const char *msg = "") noexcept {
			m_msg.file = file;
			m_msg.line = line;
			m_msg.ch = ch;
			m_msg.msg = msg;
		}

		template<std::size_t fmtSegmentCnt, typename ...Args>
		constexpr OutStream(const char *file, int line, const char *ch, detail::Fmt<fmtSegmentCnt> fmtSegments, Args const&...elements) noexcept {
			static_assert(sizeof...(elements) == fmtSegmentCnt - 1, "Wrong number of trace arguments for format.");
			m_msg.file = file;
			m_msg.line = line;
			m_msg.ch = ch;
			const auto &firstSegment = fmtSegments.segments[0];
			std::ignore = m_msg.msg.append(firstSegment.str, firstSegment.length);
			//const detail::FmtArg elements[sizeof...(args)] = {args...};
			for (size_t i = 0; auto const&e : std::initializer_list<detail::FmtArg>{elements...}) {
				m_msg.msg += e.out;
				const auto &s = fmtSegments.segments[i + 1];
				std::ignore = m_msg.msg.append(s.str, s.length);
				++i;
			}
		}

		inline ~OutStream() noexcept {
			oxTraceHook(m_msg.file, m_msg.line, m_msg.ch, m_msg.msg.c_str());
			send(m_msg);
		}

		constexpr OutStream &operator<<(Integer_c auto v) noexcept;

		constexpr OutStream &operator<<(char v) noexcept {
			if (m_msg.msg.size()) {
				m_msg.msg += m_delimiter;
			}
			m_msg.msg += v;
			return *this;
		}

		constexpr OutStream &operator<<(StringViewCR v) noexcept {
			if (m_msg.msg.size()) {
				m_msg.msg += m_delimiter;
			}
			m_msg.msg += v;
			return *this;
		}

		constexpr OutStream &operator<<(const char *v) noexcept {
			return operator<<(StringView{v});
		}

		template<std::size_t sz>
		constexpr OutStream &operator<<(const IString<sz> &v) noexcept {
			return operator<<(v.c_str());
		}

		template<std::size_t sz>
		constexpr OutStream &operator<<(const BasicString<sz> &v) noexcept {
			return operator<<(v.c_str());
		}

		constexpr OutStream &operator<<(const Error &err) noexcept {
			return appendSignedInt(static_cast<int64_t>(err));
		}

		/**
		 * del sets the delimiter between log segments.
		 */
		constexpr OutStream &del(const char *delimiter) noexcept {
			m_delimiter = delimiter;
			return *this;
		}

	private:
		constexpr OutStream &appendSignedInt(int64_t v) noexcept {
			if (m_msg.msg.size()) {
				m_msg.msg += m_delimiter;
			}
			m_msg.msg += static_cast<int64_t>(v);
			return *this;
		}

		constexpr OutStream &appendUnsignedInt(uint64_t v) noexcept {
			if (m_msg.msg.size()) {
				m_msg.msg += m_delimiter;
			}
			m_msg.msg += static_cast<int64_t>(v);
			return *this;
		}

};

constexpr OutStream &OutStream::operator<<(Integer_c auto v) noexcept {
	if (m_msg.msg.size()) {
		m_msg.msg += m_delimiter;
	}
	m_msg.msg += v;
	return *this;
}


class NullStream {

	public:
		constexpr NullStream(const char*, int, const char*, const StringView&) noexcept {
		}

		constexpr NullStream(const char*, int, const char*, const char* = "") noexcept {
		}

		template<std::size_t fmtSegmentCnt, typename ...Args>
		constexpr NullStream(const char*, int, const char*, detail::Fmt<fmtSegmentCnt>, Args const&...elements) noexcept {
			static_assert(sizeof...(elements) == fmtSegmentCnt - 1, "Wrong number of trace arguments for format.");
		}

		template<typename T>
		constexpr const NullStream &operator<<(const T&) const noexcept {
			return *this;
		}

		constexpr const NullStream &del(const char*) const noexcept {
			return *this;
		}

};

#if defined(DEBUG)
using TraceStream = OutStream;
#else
using TraceStream = NullStream;
#endif

inline void logError(const char *file, int line, const char *fmt, const Error &err) noexcept {
	if (err) {
		TraceStream trc(file, line, "ox.error");
		if (err.src.file_name() != nullptr) {
			trc << "Error: (" << err.src.file_name() << ":" << err.src.line() << "):";
		} else {
			trc <<  "Error:";
		}
		trc << ox::sfmt<BasicString<100>>(fmt, static_cast<uint64_t>(err));
	}
}

inline void logError(const char *file, int line, const Error &err) noexcept {
	if (err) {
		TraceStream trc(file, line, "ox.error");
		trc <<  "Error:" << err;
		if (err.src.file_name() != nullptr) {
			trc << "(" << err.src.file_name() << ":" << err.src.line() << ")";
		}
	}
}

void init();
void init(Logger *logger);

}
