#pragma once
#include <stdint.h>
#include<QString>
#include<QDateTime>
#include<QObject>
#include<vector>
#include<QJsonObject>

class Sensor
{
public:

	typedef enum {
		TYPE_DOOR = 0,
		TYPE_TEMPERATURE,
		TYPE_HUMIDITY,
		TYPE_PRESSURE,
		TYPE_BRIGHTNESS,
		TYPE_BUTTON,
		TYPE_ADC,
		TYPE_CO2,
		TYPE_FORMALDEHYD,
		TYPE_PM25,
		TYPE_TOTAL_VOC,
		TYPE_ENERGY_USE,
		TYPE_POWER,
		TYPE_VOLTAGE,
		TYPE_LOWBATTERY = 128,
		TYPE_SHUTDOWN_IMMINENT = 251,
		TYPE_OCUPANCY,
		TYPE_SUN_ALTITUDE,
		TYPE_AUDIO_OUTPUT,
		TYPE_DUMMY,
	} sensor_type_t;

	typedef enum {
		BACKEND_MICROCONTROLLER = 0,
		BACKEND_MQTT,
		BACKEND_SUN,
	} sensor_backend_type_t;

	sensor_type_t type;
	uint64_t id;
	float field;
	QString name;
	QString groupName;
	QDateTime lastSeen;
	bool hidden;

	Sensor(sensor_type_t typeIn, uint64_t idIn, float fieldIn = 0, QString nameIn = "", bool hiddenIn = false, QString groupNameIn = ""): type(typeIn),
		id(idIn), field(fieldIn), name(nameIn), groupName(groupNameIn), hidden(hiddenIn)
	{
		lastSeen = QDateTime::currentDateTime();
		if(nameIn == "")
			generateName();
	}
	Sensor(QString nameIn = "dummy"): type(TYPE_DUMMY), id(0), field(0), name(nameIn), groupName(""), hidden(false)
	{
		lastSeen = QDateTime::currentDateTime();
	}
	Sensor(const QJsonObject& json)
	{
		type = static_cast<sensor_type_t>(json["SensorType"].toInt(0));
		id = json["Id"].toInteger(0);
		field = json["Field"].toDouble(0);
		lastSeen = QDateTime::fromString(json["LastSeen"].toString(""));
		hidden = json["Hidden"].toBool(false);
		name = json["Name"].toString();
		groupName = json["GroupName"].toString();
		if(name == "")
			generateName();
	}
	inline bool operator==(const Sensor& in) const
	{
		return type==in.type && id == in.id;
	}
	inline bool operator!=(const Sensor& in) const
	{
		return !(*this==in);
	}
	inline void updateSeen()
	{
		lastSeen = QDateTime::currentDateTime();
	}
	static Sensor sensorFromString(const QString& str)
	{
		QStringList bufferList = str.split(' ');
		if(bufferList.size() >= 7)
		{
			Sensor sensor(static_cast<sensor_type_t>(bufferList[2].toUInt()), bufferList[4].toUInt(), bufferList[6].toUInt());
			if(sensor.type == Sensor::TYPE_HUMIDITY ||  sensor.type == Sensor::TYPE_TEMPERATURE)
				sensor.field = sensor.field/10;

			if(bufferList.size() >= 9)
				sensor.lastSeen = QDateTime::fromSecsSinceEpoch(bufferList[8].toLongLong());
			return sensor;
		}
		else
		{
			return Sensor(TYPE_DUMMY, 0, 0, "", true);
		}
	}
	QString toString()
	{
		return QString("SENSOR TYPE: ")+QString::number(type)+" ID: "+QString::number(id)+" FIELD: "+
			   QString::number((type == Sensor::TYPE_HUMIDITY ||  type == Sensor::TYPE_TEMPERATURE) ? field*10 : field) +
			   " TIME: " + QString::number(lastSeen.toSecsSinceEpoch());
	}
	inline void store(QJsonObject& json) const
	{
		json["Type"] = "Sensor";
		json["SensorType"] = static_cast<int>(type);
		json["Id"] = static_cast<qint64>(id);
		json["Field"] = field;
		json["Name"] = name;
		json["GroupName"] = groupName;
		json["LastSeen"] = lastSeen.toString();
		json["Hidden"] = hidden;
		json["Unit"] = getUnit();
	}
	inline void generateName()
	{
		if(type == TYPE_TEMPERATURE)
			name = "Temperature " + QString::number(id);
		else if(type == TYPE_DOOR)
			name = "Door " + QString::number(id);
		else if(type == TYPE_BUTTON)
			name = "Button " + QString::number(id);
		else if(type == TYPE_AUDIO_OUTPUT)
			name = "Speakers " + QString::number(id);
		else if(type == TYPE_HUMIDITY)
			name = "Humidity " + QString::number(id);
		else if(type == TYPE_SUN_ALTITUDE)
			name = "Solar Altitude";
		else if(type == TYPE_SHUTDOWN_IMMINENT)
			name = "Shutdown Imminent";
		else name = "Sensor Type " + QString::number(type) + " Id " + QString::number(id);
	}
	QString getUnit() const
	{
		switch(type)
		{
			case TYPE_TEMPERATURE:
				return "°C";
			case TYPE_HUMIDITY:
				return "%";
			case TYPE_PRESSURE:
				return "hPa";
			case TYPE_BRIGHTNESS:
				return "lx";
			case TYPE_CO2:
				return "ppm";
			case TYPE_FORMALDEHYD:
			case TYPE_PM25:
				return "µg/m³";
			case TYPE_TOTAL_VOC:
				return "ppb";
			case TYPE_SUN_ALTITUDE:
				return "°";
			case TYPE_POWER:
				return "W";
			case TYPE_ENERGY_USE:
				return "kWh";
			case TYPE_VOLTAGE:
				return "V";
			default:
				return "";
		}
	}
};

typedef enum {
	SENSOR_UPDATE_USER = 0,
	SENSOR_UPDATE_BACKEND,
	SENSOR_UPDATE_REMOTE,
	SENSOR_UPDATE_INVALID
} sensor_update_type_t;

class SensorStore: public QObject
{
	Q_OBJECT
private:
	std::vector<Sensor> sensors_;
	std::vector<Sensor> knownSensors_;

public:

	SensorStore(QObject *parent = nullptr);
	virtual ~SensorStore() {}

	inline std::vector<Sensor>* getSensors()
	{
		return &sensors_;
	}

	void store(QJsonObject& json);
	void load(const QJsonObject& json);
	std::vector<QString> allGroups() const;

public slots:

	void sensorGotState(const Sensor& sensor, sensor_update_type_t type = SENSOR_UPDATE_BACKEND);

signals:

	void stateChenged(std::vector<Sensor> sensors);
	void sensorChangedState(Sensor sensor, sensor_update_type_t type);
	void sensorDeleted(Sensor sensor);

};

extern SensorStore globalSensors;
