#include <algorithm>
#include <iostream>
#include <opts.h>
#include <componentThread.h>
#include "broadcastListener.h"

namespace livoxProto1 {
namespace comms {

BroadcastListener::BroadcastListener(
	const std::shared_ptr<smo::ComponentThread>& componentThread,
	uint16_t listeningPort, uint16_t connectPort
)
: componentThread(componentThread),
listeningPort(listeningPort),
connectPort(connectPort),
deviceGoneAwayCb(nullptr),
socket(componentThread->getIoService()),
listeningEndpoint(boost::asio::ip::udp::v4(), listeningPort),
isListening(false)
{
}

std::shared_ptr<DiscoveredDevice>
BroadcastListener::getDevice(const std::string &deviceIdentifier) const
{
	auto it = std::find_if(discoveredDevices.begin(), discoveredDevices.end(),
		[&deviceIdentifier](const std::shared_ptr<DiscoveredDevice>& device) {
			return comms::deviceIdentifiersEqual(
				device->deviceIdentifier, deviceIdentifier);
		}
	);

	return it != discoveredDevices.end() ? *it : nullptr;
}

void BroadcastListener::broadcastMsgInd(
	const boost::system::error_code& ec, std::size_t bytes_received)
{
	if (ec)
	{
		std::cerr << __func__ << ": Error receiving broadcast message: "
			<< ec.message() << std::endl;
		return;
	}

	if (bytes_received < sizeof(BroadcastMessage))
	{
		std::cerr << __func__
			<< ": Received packet too small: " << bytes_received
			<< " bytes (expected at least "
			<< sizeof(BroadcastMessage) << ")" << std::endl;
		return;
	}


	// Use placement new to construct BroadcastMessage in the buffer
	BroadcastMessage* msg = new (bcastMsgRecvBuffer) BroadcastMessage;

	// Following the clean receiving flow:
	// 1. Swap CRC32 to host endianness first
	msg->footer.swapCrc32ToHostEndianness();
	// 2. Validate CRC32 (on whole message excluding footer CRC32 field)
	if (!msg->validateCrc32())
	{
		std::cerr << __func__
			<< ": Broadcast message failed CRC32 validation" << std::endl;
		return;
	}

	// 3. Swap CRC16 to host endianness
	msg->header.swapCrc16ToHostEndianness();
	// 4. Validate CRC16 (on header only)
	if (!msg->header.validateCrc16())
	{
		std::cerr << __func__
			<< ": Broadcast message failed CRC16 validation" << std::endl;
		return;
	}
	// 5. Swap content to host endianness
	msg->swapContentsToHostEndianness();
	// 6. Validate message sanity
	if (!msg->sanityCheck())
	{
		std::cerr << __func__
			<< ": Broadcast message failed sanity check" << std::endl;
		return;
	}

	// Extract device information
	std::string senderIP = senderEndpoint.address().to_string();
	std::string broadcastCode(
		reinterpret_cast<const char*>(msg->broadcast_code));

	// Early return if device already exists
	if (deviceExists(broadcastCode))
	{
		// Device already exists, just log the update
		if (OptionParser::getOptions().verbose)
		{
			std::cout << __func__
				<< ": Received broadcast from known device: "
				<< broadcastCode << " at " << senderIP << "\n";
		}
		return;
	}

	// Create new DiscoveredDevice using conversion constructor
	auto device = std::make_shared<DiscoveredDevice>(*msg, senderIP);
	discoveredDevices.push_back(device);

	// Output device information using stringify
	std::cout << __func__ << ": Discovered new Livox device: "
		<< device->stringify() << "\n";
}

void BroadcastListener::start(void)
{
	if (isListening.load()) { return; }

	try
	{
		/**		EXPLANATION:
		 * Set up a boost::asio udp listening socket on the broadcast listening
		 * port.
		 *
		 *		FIXME:
		 * We should also set up a timer to check for devices that have gone
		 * away.
		 */
		socket.open(boost::asio::ip::udp::v4());
		socket.bind(listeningEndpoint);

		isListening.store(true);
		// Start the first async receive operation
		startReceive();
		std::cout << __func__ << ": BroadcastListener started on port "
			<< listeningPort << std::endl;
	}
	catch (const boost::system::system_error& e)
	{
		isListening.store(false);
		std::cerr << __func__ << ": Failed to start BroadcastListener: "
			<< e.what() << std::endl;
		throw;
	}
}

void BroadcastListener::startReceive(void)
{
	if (!isListening.load()) { return; }

	socket.async_receive_from(
		boost::asio::buffer(bcastMsgRecvBuffer, sizeof(bcastMsgRecvBuffer)),
		senderEndpoint,
		[this](const boost::system::error_code& ec, std::size_t bytes_received)
		{
			broadcastMsgInd(ec, bytes_received);

			// Continue listening for the next packet
			if (isListening.load())
				{ startReceive(); }
		}
	);
}

void BroadcastListener::stop(void)
{
	if (!isListening.load()) { return; }

	isListening.store(false);

	try
	{
		socket.close();
		std::cout << __func__ << ": BroadcastListener stopped" << std::endl;
	}
	catch (const boost::system::system_error& e)
	{
		std::cerr << __func__ << ": Error stopping BroadcastListener: " << e.what()
			<< std::endl;
		throw;
	}
}

} // namespace comms
} // namespace livoxProto1
