#include "CanController.h"

#include <core/MeasurementSetup.h>
#include <core/MeasurementNetwork.h>
#include <core/MeasurementInterface.h>
#include <core/CanTrace.h>
#include <core/Log.h>

#include <driver/CanInterface.h>
#include <driver/SLCANDriver/SLCANDriver.h>
#include <driver/CANBlastDriver/CANBlasterDriver.h>

#if defined(__linux__)
#include <driver/SocketCanDriver/SocketCanDriver.h>
#else
#include <driver/CandleApiDriver/CandleApiDriver.h>
#endif

#include "CanStructs.h"

#include <QDataStream>
#include <QDateTime>
#include <QDebug>
#include <QTimer>
#include <QSettings>
#include <QRandomGenerator>

#include <bitset>
#include <functional>

CanController::CanController(QObject* parent)
	: QObject{parent}
	, lastPackageDateTime_(QDateTime::fromMSecsSinceEpoch(0))
{
	setupDrivers();

	QSettings settings("settings.ini", QSettings::IniFormat);

	auto& backend = Backend::instance();
	connect(backend.getTrace(), &CanTrace::messageEnqueued, this, &CanController::handlePackage);

	const auto requestParametersTimer = new QTimer(this);
	connect(requestParametersTimer, &QTimer::timeout, this, &CanController::requestParameters);
	const auto requestParametersTimeout = settings.value("requestParametersTimeout", 500).toInt();
	requestParametersTimer->start(requestParametersTimeout);

	const auto updateCanStatusTimer = new QTimer(this);
	connect(updateCanStatusTimer, &QTimer::timeout, this, &CanController::updateCanStatus);
	updateCanStatusTimer->start(2000);

	const auto reconnectTimer = new QTimer(this);
	connect(reconnectTimer, &QTimer::timeout, this, &CanController::tryConnectCan);
	reconnectTimer->start(5000);

	const auto sendMessagesTimer = new QTimer(this);
	connect(sendMessagesTimer, &QTimer::timeout, this, &CanController::sendMessages);
	const auto sendMessagesTimeout = settings.value("sendMessagesTimeout", 30).toInt();
	sendMessagesTimer->start(sendMessagesTimeout);

	initializeBatteries();

	QTimer::singleShot(0, this, &CanController::connectCan);
}

CanController::~CanController()
{
}

QObject* CanController::qmlInstance(QQmlEngine* /*engine*/, QJSEngine* /*scriptEngine*/)
{
	return new CanController;
}

void CanController::connectCan()
{
	auto& backend = Backend::instance();
	backend.clearTrace();

	QSettings settings("settings.ini", QSettings::IniFormat);
	auto bitrate = settings.value("bitrate", 125000).toInt();

	for (auto* network: backend.getSetup().getNetworks())
	{
		for (auto* mi: network->interfaces())
		{
			mi->setBitrate(bitrate);
		}
	}

	isFirstCheck_ = true;
	backend.startMeasurement();

	saveConfiguration();

	QTimer::singleShot(1000, this, [this]{ updateCanStatus(); });
}

void CanController::disconnectCan()
{
	Backend::instance().stopMeasurement();
	isConnected_ = false;
}

void CanController::tryConnectCan()
{
	if (!isConnected_)
	{
		disconnectCan();
		connectCan();
	}
}

void CanController::sendCanMessage(const CanMessage& message, bool highPriority)
{
	if (highPriority)
	{
		sendMessages_.prepend(message);
	}
	else
	{
		sendMessages_.push_back(message);
	}
}

void CanController::sendMessages()
{
	if (sendMessages_.isEmpty())
	{
		return;
	}

	const auto& message = sendMessages_.takeFirst();
	auto& backend = Backend::instance();
	bool isSent = false;

	for (const auto interfaceId: backend.getInterfaceList())
	{
		auto interface = backend.getInterfaceById(interfaceId);
		if (interface)
		{
			isSent = true;
			interface->sendMessage(message);
		}
	}

	if (isSent)
	{
		qDebug() << "Sent:" << message.getIdString() << message.getDataHexString();
	}
}

void CanController::requestParameters()
{
	for (auto address: registerAddresses())
	{
		CanId parametersId;
		parametersId.address = address;
		parametersId.type = 0x1;

		CanMessage parametersMessage(parametersId.serialize());
		parametersMessage.setLength(1);
		parametersMessage.setByte(0, 24);
		sendCanMessage(parametersMessage);
	}
}

void CanController::setBatteryAction()
{
	auto batteryController = qobject_cast<BatteryController*>(sender());
	if (!batteryController)
	{
		return;
	}

	CanId actionId;
	actionId.address = registerAddresses().at(batteryController->batteryIndex()) + 0x12;
	actionId.type = 0x0;

	CanMessage actionMessage(actionId.serialize());
	actionMessage.setLength(1);
	actionMessage.setByte(0, 1);
	sendCanMessage(actionMessage, true);
}

void CanController::handlePackage(int index)
{
	lastPackageDateTime_ = QDateTime::currentDateTime();
	if (isFirstCheck_)
	{
		updateCanStatus();
	}

	auto& backend = Backend::instance();
	auto message = backend.getTrace()->getMessage(index);
	if (!message)
	{
		return;
	}

	qDebug() << "Received:" << index << message->getIdString() << message->getDataHexString();

	CanId canId;
	canId.deserialize(message->getId());

	if (canId.destination != 0x1 || canId.source != 0x0)
	{
		qDebug() << "Ignoring message with destination" << canId.destination << "and source" << canId.source;
		return;
	}

	auto batteryIndex = qsizetype((canId.address - 0x200) / 128);
	if (batteryIndex < 0 || batteryIndex >= batteries_.size())
	{
		qDebug() << "Invalid battery index:" << batteryIndex << "for address" << canId.address;
		return;
	}

	QHash<quint16, std::function<void(const CanMessage*, qsizetype)>> packageHandlers =
	{
		{0x0, std::bind(&CanController::handleControlParameters, this, std::placeholders::_1, std::placeholders::_2)},
		{0x8, std::bind(&CanController::handleMeasuredParameters, this, std::placeholders::_1, std::placeholders::_2)},
		{0x10, std::bind(&CanController::handleStatusParameters, this, std::placeholders::_1, std::placeholders::_2)}
	};

	auto offset = canId.address - quint16(0x200 + batteryIndex * 128);
	qDebug() << "Address:" << canId.address << "Index:" << batteryIndex << "Offset:" << offset;

	if (packageHandlers.contains(offset))
	{
		packageHandlers[offset](message, batteryIndex);
	}
}

void CanController::handleControlParameters(const CanMessage* message, qsizetype batteryIndex)
{
	QDataStream stream(message->getData());
	stream.setByteOrder(QDataStream::LittleEndian);

	quint16 capacity = 0;
	stream >> capacity;
	batteries_.at(batteryIndex)->setControlCapacity(doubleParameterToVariant(capacity));

	quint16 temperature = 0;
	stream >> temperature;
	batteries_.at(batteryIndex)->setControlTemperature(intParameterToVariant(temperature));

	qint16 current = 0;
	stream >> current;
	batteries_.at(batteryIndex)->setControlCurrent(intParameterToVariant(current));

	quint16 voltage = 0;
	stream >> voltage;
	batteries_.at(batteryIndex)->setControlVoltage(doubleParameterToVariant(voltage));
}

void CanController::handleMeasuredParameters(const CanMessage* message, qsizetype batteryIndex)
{
	QDataStream stream(message->getData());
	stream.setByteOrder(QDataStream::LittleEndian);

	quint16 capacity = 0;
	stream >> capacity;
	batteries_.at(batteryIndex)->setMeasuredCapacity(doubleParameterToVariant(capacity));

	quint16 temperature = 0;
	stream >> temperature;
	batteries_.at(batteryIndex)->setMeasuredTemperature(intParameterToVariant(temperature));

	qint16 current = 0;
	stream >> current;
	batteries_.at(batteryIndex)->setMeasuredCurrent(intParameterToVariant(current));

	quint16 voltage = 0;
	stream >> voltage;
	batteries_.at(batteryIndex)->setMeasuredVoltage(doubleParameterToVariant(voltage));
}

void CanController::handleStatusParameters(const CanMessage* message, qsizetype batteryIndex)
{
	QDataStream stream(message->getData());
	stream.setByteOrder(QDataStream::LittleEndian);

	quint8 chargeType = 0;
	stream >> chargeType;
	batteries_.at(batteryIndex)->setChargeType(chargeType);

	quint8 workingMode = 0;
	stream >> workingMode;
	batteries_.at(batteryIndex)->setWorkingMode(workingMode);

	stream.skipRawData(1);

	quint32 status = 0;
	stream >> status;
	batteries_.at(batteryIndex)->addStatus(status);
}

void CanController::updateCanStatus()
{
	auto isConnected = lastPackageDateTime_.msecsTo(QDateTime::currentDateTime()) < 2000;
	if (isConnected_ != isConnected || isFirstCheck_)
	{
		isFirstCheck_ = false;
		setProperty("isConnected", isConnected);
	}
}

void CanController::setupDrivers()
{
	auto& backend = Backend::instance();

#if defined(__linux__)
	backend.addCanDriver(*(new SocketCanDriver(backend)));
#else
	backend.addCanDriver(*(new CandleApiDriver(backend)));
#endif

	backend.addCanDriver(*(new SLCANDriver(backend)));
	// backend.addCanDriver(*(new CANBlasterDriver(backend)));

	backend.setDefaultSetup();
}

void CanController::saveConfiguration()
{
	auto& backend = Backend::instance();

	QDomDocument doc;
	auto root = doc.createElement("configuration");
	backend.getSetup().saveXML(backend, doc, root);
	doc.appendChild(root);

	const auto configuration = doc.toString();
	qDebug() << "Configuration" << configuration;
}

void CanController::initializeBatteries()
{
	for (int i = 0; i < 8; ++i)
	{
		auto batteryContoller = new BatteryController(this);
		connect(batteryContoller, &BatteryController::actionButtonClicked, this, &CanController::setBatteryAction);
		batteries_.append(batteryContoller);
		emit batteriesChanged();
	}
}

QList<quint16> CanController::registerAddresses() const
{
	return {0x200, 0x280, 0x300, 0x380, 0x400, 0x480, 0x500, 0x580};
}

