#include "mainobject.h"

#include<QJsonObject>
#include<QJsonArray>
#include<QMessageBox>
#include<QHostInfo>

#include "mqttclient.h"
#include "items/mqttitem.h"
#include "items/itemstore.h"

MainObject::MainObject(QObject *parent) :
	QObject(parent)
{
}

MainObject::~MainObject()
{
}

void MainObject::refresh()
{
	globalItems.refresh();
}

void MainObject::addSensor(Sensor sensor, Sensor::sensor_backend_type_t backend, QJsonObject payload)
{
	// Default implementation does nothing - derived classes override
}

QJsonObject MainObject::getJsonObjectFromDisk(const QString& filename, bool* error)
{
	QFile file;
	file.setFileName(filename);

	bool ret = file.open(QIODevice::ReadOnly);
	if(!file.isOpen() || !ret)
	{
		std::cerr<<"Can not open config file: "<<filename.toLatin1().data()<<std::endl;
	}
	else
	{
		QJsonParseError qerror;
		QJsonDocument document(QJsonDocument::fromJson(file.readAll(), &qerror));
		file.close();
		if(qerror.error != QJsonParseError::NoError)
		{
			qDebug()<<filename<<" "<<qerror.errorString();
			if(error)
				(*error) = true;
		}
		return document.object();
	}
	return QJsonObject();
}

bool MainObject::storeJsonObjectToDisk(const QString& filename, const QJsonObject& json)
{
	QFile file(filename + ".out");

	qDebug()<<"config file: "<<filename;
	bool ret = file.open(QIODevice::WriteOnly);
	if(!file.isOpen() || !ret)
	{
		std::cerr<<"Can not open config file: "<<filename.toLatin1().data()<<std::endl;
		return false;
	}
	else
	{
		QJsonDocument document(json);
		file.write(document.toJson());
		file.close();
		QFile::remove(filename);
		file.rename(filename);
		return true;
	}
}

PrimaryMainObject::PrimaryMainObject(QIODevice* microDevice, const QString& settingsPath, const QString& host, int port, QObject *parent) :
	MainObject(parent),
	settingsPath(settingsPath),
	micro(microDevice),
	tcpServer(new TcpServer(this)),
	webServer(new WebSocketServer("shinterface", this)),
	mqttClient(new MqttClient),
	sunSensorSource(49.824972, 8.702194),
	fixedItems(&micro)
{
	MqttItem::client = mqttClient;

	connect(tcpServer, &Server::sigRequestSave, this, [this](){storeToDisk(this->settingsPath);});

	//connect sensors subsystem
	connect(&globalSensors, &SensorStore::sensorChangedState, tcpServer, &TcpServer::sensorEvent);
	connect(tcpServer, &TcpServer::gotSensor, &globalSensors, &SensorStore::sensorGotState);
	connect(webServer, &WebSocketServer::gotSensor, &globalSensors, &SensorStore::sensorGotState);
	connect(&sunSensorSource, &SunSensorSource::stateChanged, &globalSensors, &SensorStore::sensorGotState);
	connect(&micro, &Microcontroller::gotSensorState, &globalSensors, &SensorStore::sensorGotState);
	connect(&mqttSensorSource, &MqttSensorSource::stateChanged, &globalSensors, &SensorStore::sensorGotState);
	connect(tcpServer, &TcpServer::sensorAdded, &mqttSensorSource, &MqttSensorSource::onSensorAdded);
	connect(webServer, &WebSocketServer::sensorAdded, &mqttSensorSource, &MqttSensorSource::onSensorAdded);

	globalItems.registerItemSource(&fixedItems);
	globalItems.registerItemSource(tcpServer);
	globalItems.registerItemSource(webServer);
	globalItems.registerItemSource(&micro);
	globalItems.registerItemSource(&itemLoader);

	Relay::setMicrocontroller(&micro);

	loadFromDisk(settingsPath);

	sunSensorSource.run();

	QJsonObject mqttJson = settings["Mqtt"].toObject();
	mqttClient->start(mqttJson);
	mqttSensorSource.start(mqttClient, mqttJson);

	tcpServer->launch(QHostAddress(host), port);
	webServer->launch(QHostAddress(host), port+1);
	connect(&globalItems, &ItemStore::itemUpdated, tcpServer, &TcpServer::itemUpdated);
	connect(&globalItems, &ItemStore::itemUpdated, webServer, &WebSocketServer::itemUpdated);
}

PrimaryMainObject::~PrimaryMainObject()
{
	storeToDisk(settingsPath);
}

void PrimaryMainObject::store(QJsonObject &json)
{
	globalItems.store(json);
	globalSensors.store(json);
	QJsonObject mqttJson = json["Mqtt"].toObject();
	mqttClient->store(mqttJson);
	mqttSensorSource.store(mqttJson);
	json["Mqtt"] = mqttJson;
}

void PrimaryMainObject::load(const QJsonObject& json)
{
	settings = json;
	itemLoader.updateJson(json);
	globalSensors.load(json);
	globalItems.clear();
	globalItems.refresh();
}

bool PrimaryMainObject::storeToDisk(const QString& filename)
{
	store(settings);
	itemLoader.updateJson(settings);
	return storeJsonObjectToDisk(filename, settings);
}

bool PrimaryMainObject::loadFromDisk(const QString& filename)
{
	bool error = false;
	QJsonObject json = getJsonObjectFromDisk(filename, &error);
	if(!error)
		load(json);
	return error;
}

SecondaryMainObject::SecondaryMainObject(QString host, int port, QObject *parent) :
	MainObject(parent),
	tcpClient(new TcpClient)
{
	connect(tcpClient, &TcpClient::gotSensor, &globalSensors, &SensorStore::sensorGotState);
	globalItems.registerItemSource(tcpClient);
	connect(&globalSensors, &SensorStore::sensorChangedState, tcpClient, &TcpClient::sensorEvent);

	if(!tcpClient->launch(QHostInfo::fromName(host).addresses()[0], port))
	{
		QMessageBox::critical(nullptr, "Error", "Could not connect to "+host+":"+QString::number(port));
		QMetaObject::invokeMethod(this, [](){exit(1);}, Qt::QueuedConnection);
	}

	connect(&globalItems, &ItemStore::itemUpdated, tcpClient, &TcpClient::itemUpdated);

	globalItems.refresh();
}

SecondaryMainObject::~SecondaryMainObject()
{
}

void SecondaryMainObject::addSensor(Sensor sensor, Sensor::sensor_backend_type_t backend, QJsonObject payload)
{
	tcpClient->addSensor(sensor, backend, payload);
}

