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

#include <QtWidgets>
#include <QPushButton>
#include <QSizePolicy>
#include <QDateTime>
#include <QTime>
#include <QDate>
#include <QtMath>
#include <QLocale>
#include <QVector>

#include "boost_dlg.h"
#include "menu_dlg.h"
#include "edit_dlg.h"
#include "holiday_dlg.h"
#include "mqttclient.h"
#include "zoneitem.h"
#include "settings.h"
#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
	QMainWindow(parent)
{
	ZoneItem *zone;

	m_boost = NULL;
	m_menu = NULL;

	Settings *s = Settings::getInstance();

	for (int i = 0; i < s->nbZones(); i++) {
		zone = new ZoneItem(s->getRoom(i).name, this);
		zone->setProperty("idx", i);
		zone->m_target_temperature = get_target_temperature(i);
		connect(zone, SIGNAL(clicked()), this, SLOT(show_boost()));
		connect(zone, SIGNAL(long_clicked()), this, SLOT(do_show_edit_dlg()));
		m_zones << zone;
	}

	/*
	 * Sensors-related layout
	 */
	QGridLayout *mainLayout = new QGridLayout;

	m_state_btn = new QPushButton("Auto");
	QPushButton *menu_btn = new QPushButton(tr("Menu"));

	if (s->nbZones() < MAX_NB_ZONES) {
		// TODO
		qDebug() << "bad configuration" ;
	} else {
		mainLayout->addWidget(m_zones.at(0), 0, 0);
		mainLayout->addWidget(m_zones.at(1), 0, 1);
		mainLayout->addWidget(menu_btn, 0, 2);
		mainLayout->addWidget(m_zones.at(2), 1, 0);
		mainLayout->addWidget(m_zones.at(3), 1, 1);
		mainLayout->addWidget(m_state_btn, 1, 2);
	}
	update_state_btn(s->getPowerState());

	QSizePolicy *szPolicy = new QSizePolicy(QSizePolicy::Minimum,
						QSizePolicy::MinimumExpanding,
						QSizePolicy::PushButton);

	QFont font = menu_btn->font();
	font.setPointSize(12);
	font.setBold(true);
	menu_btn->setFont(font);
	m_state_btn->setFont(font);

	menu_btn->setSizePolicy(*szPolicy);
	m_state_btn->setSizePolicy(*szPolicy);

	QWidget *mainWidget = new QWidget;
	mainWidget->setLayout(mainLayout);

	m_central_widget.addWidget(mainWidget);

	/*
	 * Top widget
	 */
	setCentralWidget(&m_central_widget);

	setWindowTitle(tr("Sorico's thermostat"));

	/*
	 * MQTT client
	 */
	m_mqtt = new MQTTClient(s->m_broker.address, s->m_broker.port, NULL);

	m_mqtt->connectToHost();

	connect(m_mqtt, SIGNAL(new_temperature(int, double)),
		this, SLOT(temperature_slot(int, double)));
	connect(m_mqtt, SIGNAL(new_hygro(int, double)),
		this, SLOT(hygro_slot(int, double)));
	connect(m_mqtt, SIGNAL(new_availability(int, bool)),
		this, SLOT(availability_slot(int, bool)));

	connect(this, SIGNAL(setAllHeatersOn(bool)), m_mqtt, SLOT(allHeatersOn(bool)));
	connect(m_mqtt, SIGNAL(connected(void)), this, SLOT(apply_order_to_heaters(void)));

	connect(m_state_btn, SIGNAL(clicked()), this, SLOT(change_state()));

	connect(menu_btn, SIGNAL(clicked()), this, SLOT(show_menu()));
	/*
	 * Heater timer
	 */
	m_timer = new QTimer(this);
	connect(m_timer, SIGNAL(timeout()), this, SLOT(apply_order_to_heaters()));
	m_timer->start(60000);
}

MainWindow::~MainWindow()
{
}

void MainWindow::boost_slot(int idx, struct boost_data &data)
{
	if (idx == -1) {
		/*
		 * user has hit cancel
		 */
		goto out;
	}

	qDebug() << "room " << m_zones.at(idx)->m_name << " new BOOST " << data.temperature << " end: " << data.end_date;

	m_zones.at(idx)->m_boost = data;

	apply_order_to_heaters();
out:
	show_main();
}

void MainWindow::show_boost(void)
{
	QObject* obj = sender();
	QVariant v;

	v = obj->property("idx");
	if (!v.isValid())
		return;

	int idx = v.toInt();

	if ((idx < 0) || (idx >= MAX_NB_ZONES))
		return;

	m_boost = new BoostDlg(idx, m_zones.at(idx)->m_name, m_zones.at(idx)->m_target_temperature, NULL);

	connect(m_boost, SIGNAL(boost_changed(int, struct boost_data &)), this, SLOT(boost_slot(int, struct boost_data &)));
	m_central_widget.addWidget(m_boost);
	m_central_widget.setCurrentWidget(m_boost);
}

void MainWindow::do_show_edit_dlg(void)
{
	QObject* obj = sender();
	QVariant v;

	v = obj->property("idx");
	if (!v.isValid())
		return;

	int idx = v.toInt();

	if ((idx < 0) || (idx >= MAX_NB_ZONES))
		return;

	m_edit_dlg = new EditDlg(idx);

	connect(m_edit_dlg, SIGNAL(close_edit_dlg(void)), this, SLOT(show_main(void)));
	m_central_widget.addWidget(m_edit_dlg);
	m_central_widget.setCurrentWidget(m_edit_dlg);
}

void MainWindow::do_show_holiday_dlg(void)
{
	HolidayDlg *holiday_dlg = new HolidayDlg(this);

	m_central_widget.addWidget(holiday_dlg);
	m_central_widget.setCurrentWidget(holiday_dlg);
	connect(holiday_dlg, SIGNAL(close_holiday_dlg(void)), this, SLOT(do_close_holiday_dlg(void)));
	connect(holiday_dlg, SIGNAL(holiday_mode(QDateTime)), this, SLOT(set_holiday_mode(QDateTime)));
}

void MainWindow::do_close_holiday_dlg(void)
{
	QWidget *current = m_central_widget.currentWidget();

	if (current == NULL)
		return;

	m_central_widget.removeWidget(current);
	delete current;
}

void MainWindow::set_holiday_mode(QDateTime end_date)
{
	Settings *s = Settings::getInstance();

	s->setEndHoliday(end_date);
	apply_order_to_heaters();
}

void MainWindow::show_menu(void)
{
	m_menu = new MenuDlg(NULL);

	connect(m_menu, SIGNAL(closed(void)), this, SLOT(show_main(void)));
	connect(m_menu, SIGNAL(show_holiday_dlg(void)), this, SLOT(do_show_holiday_dlg(void)));

	m_central_widget.addWidget(m_menu);
	m_central_widget.setCurrentWidget(m_menu);
}

void MainWindow::show_main(void)
{
	QWidget *current = m_central_widget.currentWidget();

	if (current == NULL)
		goto out;

	if (m_central_widget.count() < 1) {
		/* WTF ?! */
		return;
	}

	if (m_central_widget.count() == 1) {
		/* nothing to do */
		return;
	}

	if (current == m_boost) {
		m_boost = NULL;
	}

	if (current == m_menu) {
		m_menu = NULL;
	}

	if (current == m_edit_dlg) {
		m_edit_dlg = NULL;
	}

	m_central_widget.removeWidget(current);
	delete current;

out:
	m_central_widget.setCurrentIndex(0);
}

void MainWindow::update_state_btn(enum power_states st)
{
	switch (st) {
	case OFF:
		m_state_btn->setText(tr("Force off"));
		break;
	case ON:
		m_state_btn->setText(tr("Force on"));
		break;
	case AUTO:
		m_state_btn->setText(tr("Auto"));
		break;
	default:
		m_state_btn->setText(tr("Error"));
		break;
	}
}

double MainWindow::get_target_temperature(int room_idx)
{
	Settings *s = Settings::getInstance();
	const struct Room *r = NULL;
	const struct Program *p = NULL;
	const struct boost_data *b = NULL;
	double target = FORCE_OFF;
	QDateTime now = QDateTime::currentDateTime();
	uint8_t dow = QDate::currentDate().dayOfWeek();

	if ((room_idx < 0) || (room_idx >= MAX_NB_ZONES)) {
		goto out;
	}

	r = &(s->getRoom(room_idx));
	target = r->default_temperature;
	if (!dow) {
		goto out;
	}

	/*
	 * Check if there's a boost programmation
	 */
	if (room_idx < m_zones.count()) {
		b = &(m_zones.at(room_idx)->m_boost);
		if (b->end_date.isValid() && (b->end_date > now)) {
			/* BOOST is active */
			target = b->temperature;
			goto out;
		}
	}

	/*
	 * dayOfWeek() returns 1 for monday, 7 for sunday.
	 * so dow -= 1 gives 0 for monday, 6 for sunday.
	 * And (1 << dow) gives the day of week as a bit field
	 */
	dow -= 1;
	for (int i = 0; i < r->progs.count(); i++) {
		p = &(r->progs.at(i));
		if ((1 << dow) & p->DoW) {
			if (p->start_time == p->end_time) {
				continue;
			}

			/*
			 * Ex: from 10h to 18h
			 */
			if (p->start_time < p->end_time) {
				if ((now.time() >= p->start_time) && (now.time() < p->end_time)) {
					target = p->temperature;
				}
			} else {
				/*
				 * Ex: from 22h to 6h
				 */
				if (now.time() >= p->start_time) {
					target = p->temperature;
				} else {
					if (now.time() < p->end_time) {
					target = p->temperature;
					}
				}
			}
		}
	}

out:
	return target;
}

bool MainWindow::get_heater_order(int room_idx)
{
	bool should_heat = false;

	if ((room_idx < 0) || (room_idx >= MAX_NB_ZONES)) {
		goto out;
	}

	if (m_zones.at(room_idx)->m_temperature_value == FORCE_OFF) {
		goto out;
	}
	if (!m_zones.at(room_idx)->m_available) {
		goto out;
	}

	/* TODO: make it smarter */
	if (qFabs(get_target_temperature(room_idx)
		  - m_zones.at(room_idx)->m_temperature_value) < 0.1) {
		/*
		 * Stay like before
		 */
		should_heat = m_zones.at(room_idx)->m_heating_on;
	} else {

		if (get_target_temperature(room_idx) >
		    m_zones.at(room_idx)->m_temperature_value) {
			should_heat = true;
		}
	}

out:
	return should_heat;
}

void MainWindow::apply_automatic_state(void)
{
	Settings *s = Settings::getInstance();
	int i;

	qDebug() << "apply auto state";
	for (i = 0; i < s->nbZones(); i++) {
		const struct Room r = s->getRoom(i);
		m_zones.at(i)->m_heating_on = get_heater_order(i);
		m_zones.at(i)->m_target_temperature = get_target_temperature(i);
		m_zones.at(i)->refresh();
		qDebug() << "room " << r.name;
		for (int j = 0; j < r.heaters.count(); j++) {
			const struct Heater *h = &(r.heaters.at(j));
		qDebug() << "heater " << h->ctrl_topic;
			m_mqtt->publish_msg(h->ctrl_topic,
					    m_zones.at(i)->m_heating_on ? "1" : "0");
		}
	}
}

void MainWindow::change_state(void)
{
	Settings *s = Settings::getInstance();

	switch (s->getPowerState()) {
	case OFF:
		s->setPowerState(ON);
		break;
	case ON:
		s->setPowerState(AUTO);
		break;
	case AUTO:
		/* fall through */
	default:
		s->setPowerState(OFF);
		break;
	}

	update_state_btn(s->getPowerState());

	apply_order_to_heaters();
}

void MainWindow::apply_order_to_heaters(void)
{
	Settings *s = Settings::getInstance();
	bool heating_order = false;

	if (s->getEndHoliday() > QDateTime::currentDateTime()) {
		qDebug() << "Holiday mode => emit ALL_OFF order";
		emit setAllHeatersOn(false);
	} else {
		switch (s->getPowerState()) {
		case AUTO:
			qDebug() << "apply AUTO state";
			apply_automatic_state();
			return;
		case ON:
			heating_order = true;
			/* fall through */
		case OFF:
			/* fall through */
		default:
			emit setAllHeatersOn(heating_order);
			break;
		}
	}

	for (int i = 0; i < s->nbZones(); i++) {
		m_zones.at(i)->refresh();
	}
}

void MainWindow::temperature_slot(int idx, double val)
{
	static bool temp_received[MAX_NB_ZONES];

	Settings *s = Settings::getInstance();

	qDebug() << "temperature idx:" << idx << " val: " << val << endl;
	if (idx < s->nbZones()) {
		m_zones.at(idx)->m_temperature_value = val;
		m_zones.at(idx)->refresh();
		if (!temp_received[idx]) {
			temp_received[idx] = true;
			apply_order_to_heaters();
		}
	}
}

void MainWindow::hygro_slot(int idx, double val)
{
	Settings *s = Settings::getInstance();

	qDebug() << "hygro idx:" << idx << " val: " << val << endl;
	if (idx < s->nbZones()) {
		m_zones.at(idx)-> m_hygro_value = val;
		m_zones.at(idx)->refresh();
	}
}

void MainWindow::availability_slot(int idx, bool ok)
{
	Settings *s = Settings::getInstance();

	if (idx < s->nbZones()) {
		m_zones.at(idx)->m_available = ok;
		if (!ok) {
			m_zones.at(idx)->m_hygro_value = 0;
			m_zones.at(idx)->m_temperature_value = 0;
			m_zones.at(idx)->refresh();
		}
	}
}

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