/*
 * 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/.
 */

#include <ox/std/defines.hpp>

#ifndef OX_OS_BareMetal
#include <cstdio>

#ifndef _WIN32
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#else
#include <winsock.h>
#undef interface
#undef min
#undef max
#endif


#include <ox/logconn/logconn.hpp>


namespace ox {

#ifdef _WIN32
using Socket = SOCKET;
using LenType = int;
#else
using Socket = int;
using LenType = size_t;
#endif

using namespace trace;

static void closeSock(auto s) noexcept {
#ifdef _WIN32
	closesocket(static_cast<Socket>(s));
#else
	close(s);
#endif
}

LoggerConn::LoggerConn() noexcept: m_netThread([this]{this->msgSend();}) {
}

LoggerConn::~LoggerConn() noexcept {
	m_running = false;
	m_waitCond.notify_one();
	m_netThread.join();
	if (m_socket) {
		closeSock(m_socket);
	}
}

ox::Error LoggerConn::initConn(ox::StringViewCR appName) noexcept {
	sockaddr_in addr{};
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
	addr.sin_port = htons(5590);
	m_socket = static_cast<int>(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
	OX_RETURN_ERROR(ox::Error(static_cast<ox::ErrorCode>(connect(static_cast<Socket>(m_socket), reinterpret_cast<sockaddr*>(&addr), sizeof(addr)))));
	return sendInit({.appName = ox::BasicString<128>(appName)});
}

ox::Error LoggerConn::send(const char *buff, std::size_t len) const noexcept {
	std::size_t totalSent = 0;
	while (totalSent < len) {
		//std::fprintf(stdout, "Sending %lu/%lu bytes on socket %d\n", len, totalSent, m_socket);
		const auto sent = ::send(static_cast<Socket>(m_socket), buff, static_cast<LenType>(len), 0);
		if (sent < 0) {
			std::ignore = std::fprintf(stderr, "Could not send msg\n");
			return ox::Error(1, "Could not send msg");
		}
		totalSent += static_cast<std::size_t>(sent);
	}
	return {};
}

ox::Error LoggerConn::send(const TraceMsg &msg) noexcept {
	return send(MsgId::TraceEvent, msg);
}

ox::Error LoggerConn::sendInit(const InitTraceMsg &msg) noexcept {
	return send(MsgId::Init, msg);
}

void LoggerConn::msgSend() noexcept {
	try {
		while (true) {
			std::unique_lock lk(m_waitMut);
			m_waitCond.wait(lk);
			if (!m_running) {
				break;
			}
			std::lock_guard const buffLk(m_buffMut);
			while (true) {
				Array<char, units::KB> tmp;
				const auto read = m_buff.read(tmp.data(), tmp.size());
				if (!read) {
					break;
				}
				oxAssert(read <= tmp.size(), "logger trying to read too much data");
				//std::printf("LoggerConn: sending %lu bytes\n", read);
				std::ignore = send(tmp.data(), read);
			}
		}
	} catch (std::exception const &e) {
		oxErrf("Exception in logger thread: {}\n", e.what());
		oxAssert(false, "logger thread exception");
	}
}

}
#endif
