// SPDX-License-Identifier: GPL-3.0-or-later
/*
 * Qt mutizone MQTT thermostat
 *
 * Copyright (C) 2019 Richard Genoud
 *
 */

#include <QLoggingCategory>
#include <QtMqtt/QtMqtt>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>


#include "mqttclient.h"
#include "settings.h"

#define TIMER_PERIOD_MS 30000

MQTTClient::MQTTClient(const QString& host, int port, QObject* parent)
	: QMqttClient(parent)
{
	this->setHostname(host);
	this->setPort(port);

	connect(this, SIGNAL(connected(void)), this, SLOT(onConnected(void)));
	connect(this, SIGNAL(disconnected(void)), this, SLOT(onDisconnected(void)));
	connect(this, SIGNAL(messageReceived(const QByteArray &, const QMqttTopicName &)),
		this, SLOT(onReceived(const QByteArray &, const QMqttTopicName &)));
	connect(this, SIGNAL(messageSent(qint32)), this, SLOT(onPublished(qint32)));
	connect(this, SIGNAL(errorChanged(ClientError)),
		this, SLOT(onError(ClientError)));
	connect(this, SIGNAL(stateChanged(ClientState)),
		this, SLOT(onStateChanged(ClientState)));

	connect(&m_timer, SIGNAL(timeout()), this, SLOT(onConnectTimeout()));
	m_timer.start(TIMER_PERIOD_MS);
}

MQTTClient::~MQTTClient() {
}

/*
 * the QMqttClient doesn't handle auto-reconnection.
 * It can stay in the connecting state for ever, even
 * if the server is back on.
 */
void MQTTClient::onConnectTimeout(void)
{
	switch (state()) {
	case Connecting:
		qDebug() << "force to disconnect";
		disconnectFromHost();
		/* fall through */
	case Disconnected:
		qDebug() << "re-try do connect";
		connectToHost();
		break;
	case Connected:
		break;
	}
}

void MQTTClient::onStateChanged(ClientState state)
{
	qDebug() << "mqtt state: ";

	switch (state) {
	case Disconnected:
		qDebug() << "disconnected";
		break;
	case Connecting:
		qDebug() << "Connecting";
		break;
	case Connected:
		qDebug() << "Connected";
		break;
	}
}

void MQTTClient::onError(ClientError error)
{
	qDebug() << "mqtt error " << error;
	/* TODO */
	switch (error) {
	case NoError: break;
	case InvalidProtocolVersion: break;
	case IdRejected: break;
	case ServerUnavailable: break;
	case BadUsernameOrPassword: break;
	case NotAuthorized: break;
	case TransportInvalid: break;
	case ProtocolViolation: break;
	case UnknownError: break;
	default: break;
	}
}

void MQTTClient::onConnected()
{
	qDebug() << "subscribe";
	this->subscribe(QString("sensors/#"), 1);
}

void MQTTClient::onReceived(const QByteArray &message, const QMqttTopicName &topic)
{
	Settings *s = Settings::getInstance();
	QJsonDocument sensorData;
	QJsonValue val;

	qDebug() << "publish received: \"" << QString::fromUtf8(message)
		<< "\"" << " from: " << topic.name();

	for (int i = 0; i < s->nbZones(); ++i) {
		if (s->getRoom(i).sensor_topic == topic.name()) {
			qDebug() << "this is for us !";
			sensorData = QJsonDocument::fromJson(message);
			if (!sensorData.isObject()) {
				qWarning() << "malformed JSON data";
				goto out;
			}
			QJsonObject obj(sensorData.object());
			val = obj["temperature"];
			if (val.isDouble()) {
				emit new_temperature(i, val.toDouble());
				qDebug() << val.toDouble();
			}

			val = obj["humidity"];
			if (val.isDouble()) {
				emit new_hygro(i, val.toDouble());
				qDebug() << val.toDouble();
			}

			val = obj["battery"];
			if (val.isDouble()) {
				emit new_battery(i, val.toDouble());
				qDebug() << val.toDouble();
			}

			break;
		}

		if (s->getRoom(i).availability_topic == topic.name()) {

			emit new_availability(i, message == QString("online").toUtf8());
		}
	}
out:
	return;
}

void MQTTClient::onPublished(qint32 msgid)
{
	qDebug() << "published id " << msgid;
}

void MQTTClient::onDisconnected(void)
{
	/* TODO */
	qDebug() << "disconnected";
}

void MQTTClient::publish_msg(const QString& topic, const QString& payload)
{
	qint32 msgid;

	msgid = this->publish(QMqttTopicName(topic), payload.toUtf8(), 1, true);
	if (msgid == -1) {
		qDebug() << "fail to publish message " << payload << " on " << topic;
	}
}

void MQTTClient::allHeatersOn(bool on) {
	Settings *s = Settings::getInstance();

	QString payload = on ? QString("1") : QString("0");

	for (int i = 0; i < s->nbZones(); i++) {
		const struct Room r = s->getRoom(i);
		for (int j = 0; j < r.heaters.count(); j++) {
			const struct Heater *h = &(r.heaters.at(j));

			publish_msg(h->ctrl_topic, payload);
		}
	}
}

/* vim: set tabstop=8 shiftwidth=8 softtabstop=0 noexpandtab: */
