#include "item.h"

#include "actors/sensoractor.h"
#include "actors/regulator.h"
#include "actors/polynomalactor.h"
#include "actors/scriptactor.h"
#include "programmode.h"
#include "relay.h"
#include "messageitem.h"
#include "systemitem.h"
#include "auxitem.h"
#include "poweritem.h"
#include "rgbitem.h"
#include "mqttitem.h"

#include <QJsonArray>

ItemData::ItemData(uint32_t itemIdIn, QString name, uint8_t value, bool loaded, bool hidden, item_value_type_t type, QString groupName):
	name_(name), value_(value), itemId_(itemIdIn), loaded_(loaded), hidden_(hidden), type_(type), groupName_(groupName)
{

}

QString ItemData::getName() const
{
	return name_;
}

void ItemData::setName(QString name)
{
	name_ = name;
}

uint8_t ItemData::getValue() const
{
	return value_;
}

void ItemData::setValueData(uint8_t value)
{
	value_ = value;
}

uint32_t ItemData::id() const
{
	return  itemId_;
}

void ItemData::store(QJsonObject &json)
{
	storeWithChanges(json, ItemFieldChanges(true));
}

void ItemData::storeWithChanges(QJsonObject& json, const ItemFieldChanges& changes)
{
	json["ItemId"] = static_cast<double>(itemId_);
	json["ValueType"] = type_;
	if(changes.name)
		json["Name"] = name_;
	if(changes.value)
		json["Value"] = static_cast<double>(value_);
	if(changes.groupName && !groupName_.isEmpty() && groupName_ != "All")
		json["GroupName"] = groupName_;
	if(changes.valueNames && !valueNames_.empty())
	{
		QJsonArray valueNamesArray;
		for(const QString& name : valueNames_)
			valueNamesArray.append(name);
		json["ValueNames"] = valueNamesArray;
	}
	if(changes.override)
		json["override"] = override_;
}

void ItemData::load(const QJsonObject &json, const bool preserve)
{
	loadWithChanges(json, preserve);
}

ItemFieldChanges ItemData::loadWithChanges(const QJsonObject& json, const bool preserve)
{
	ItemFieldChanges changes;
	if(!preserve)
	{
		if(json.contains("Name"))
		{
			name_ = json["Name"].toString();
			changes.name = true;
		}
		if(json.contains("Value"))
		{
			value_ = json["Value"].toInt();
			changes.value = true;
		}
		if(json.contains("GroupName"))
		{
			groupName_ = json["GroupName"].toString();
			changes.groupName = true;
		}
		if(json.contains("ValueType"))
		{
			type_ = static_cast<item_value_type_t>(json["ValueType"].toInt());
			changes.type = true;
		}
		if(json.contains("ValueNames"))
		{
			valueNames_.clear();
			QJsonArray valueNamesArray = json["ValueNames"].toArray();
			for(int i = 0; i < valueNamesArray.size(); ++i)
				valueNames_.push_back(valueNamesArray[i].toString());
			changes.valueNames = true;
		}
		if(json.contains("override"))
		{
			override_ = json["override"].toBool(false);
			changes.override = true;
		}
		itemId_ = static_cast<uint32_t>(json["ItemId"].toDouble(0));
	}
	return changes;
}

bool ItemData::getLoaded() const
{
	return loaded_;
}

void ItemData::setLoaded(bool loaded)
{
	loaded_ = loaded;
}

bool ItemData::hasChanged(const ItemData& other) const
{
	ItemFieldChanges changes(true);
	return hasChanged(other, changes);
}

bool ItemData::hasChanged(const ItemData& other, const ItemFieldChanges& changes) const
{
	if(changes.name && other.getName() != getName())
		return true;
	if(changes.value && other.getValue() != getValue())
		return true;
	if(changes.hidden && other.isHidden() != isHidden())
		return true;
	if(changes.groupName && other.getGroupName() != getGroupName())
		return true;
	if(changes.actors)
		return true;
	if(changes.valueNames && other.getValueNames() != getValueNames())
		return true;
	if(changes.override && other.getOverride() != getOverride())
		return true;
	return false;
}

bool ItemData::isHidden() const
{
	return hidden_;
}

void ItemData::setHidden(bool hidden)
{
	hidden_ = hidden;
}

item_value_type_t ItemData::getValueType()
{
	return type_;
}

QString ItemData::getGroupName() const
{
	return groupName_;
}

void ItemData::setGroupName(QString groupName)
{
	groupName_ = groupName;
}

std::vector<QString> ItemData::getValueNames() const
{
	return valueNames_;
}

void ItemData::setValueNames(std::vector<QString> valueNames)
{
	valueNames_ = std::move(valueNames);
}

int ItemData::valueNameToIndex(const QString& name) const
{
	for(size_t i = 0; i < valueNames_.size(); ++i)
	{
		if(valueNames_[i] == name)
			return static_cast<int>(i);
	}
	return -1;
}

QString ItemData::indexToValueName(int index) const
{
	if(index >= 0 && static_cast<size_t>(index) < valueNames_.size())
		return valueNames_[index];
	return QString();
}

bool ItemData::getOverride() const
{
	return override_;
}

void ItemData::setOverride(bool overrideVal)
{
	override_ = overrideVal;
}

//item

Item::Item(uint32_t itemIdIn, QString name, uint8_t value,  QObject *parent): QObject(parent), ItemData (itemIdIn, name,
	        value, false, false, ITEM_VALUE_BOOL, "All")
{

}

Item::Item(const ItemData& itemData,  QObject *parent): QObject(parent), ItemData(itemData)
{

}

Item::~Item()
{
}

void Item::store(QJsonObject &json)
{
	ItemData::store(json);
	if(!actors_.empty())
	{
		QJsonArray actorsArray;
		for(size_t i = 0; i < actors_.size(); ++i)
		{
			if(!actors_[i]->isExausted())
			{
				QJsonObject actorObject;
				actors_[i]->store(actorObject);
				actorsArray.append(actorObject);
			}
		}
		json["Actors"] = actorsArray;
	}
}

void Item::load(const QJsonObject &json, const bool preserve)
{
	ItemData::load(json, preserve);
	if(json.contains("Actors"))
	{
		const QJsonArray actorsArray(json["Actors"].toArray(QJsonArray()));
		for(int i = 0; i < actorsArray.size(); ++i)
		{
			if(actorsArray[i].isObject())
			{
				std::shared_ptr<Actor> actor = Actor::loadActor(actorsArray[i].toObject());
				if(actor != nullptr)
					addActor(actor);
			}
		}
	}
}

Item& Item::operator=(const ItemData& other)
{
	name_ = other.getName();
	value_ = other.getValue();
	itemId_ = other.id();
	hidden_ = other.isHidden();
	groupName_ = other.getGroupName();
	valueNames_ = other.getValueNames();
	return *this;
}

void Item::requestUpdate(ItemUpdateRequest update)
{
	assert(update.type != ITEM_UPDATE_INVALID);
	assert(!update.changes.isNone());

	if(update.type == ITEM_UPDATE_LOADED)
	{
		qDebug()<<__func__<<update.changes.actors<<update.newActors.size();
	}

	if(!hasChanged(update.payload, update.changes))
		return;

	if(update.type == ITEM_UPDATE_ACTOR && override_)
		return;

	qDebug()<<"Item Update Request for"<<getName()<<" type "<<update.type<<" value "<<update.payload.getValue();

	if(update.type != ITEM_UPDATE_LOADED && update.type != ITEM_UPDATE_BACKEND &&
		(programMode == PROGRAM_MODE_PRIMARY || programMode == PROGRAM_MODE_HEADLESS_PRIMARY) &&
		update.changes.value)
		enactValue(update.payload.getValue());


	if(update.changes.value)
		value_ = update.payload.getValue();
	if(update.changes.name)
		name_ = update.payload.getName();
	if(update.changes.hidden)
		hidden_ = update.payload.isHidden();
	if(update.changes.groupName)
		groupName_ = update.payload.getGroupName();
	if(update.changes.type)
		type_ = update.payload.getValueType();
	if(update.changes.actors)
	{
		actors_.clear();
		for(std::shared_ptr<Actor>& actor : update.newActors)
			addActor(actor);
	}
	if(update.changes.valueNames)
		valueNames_ = update.payload.getValueNames();
	if(update.changes.override)
		override_ = update.payload.getOverride();
	update.payload = *this;
	updated(update);
}

void Item::enactValue(uint8_t value)
{
	(void)value;
}

void Item::addActor(std::shared_ptr<Actor> actor)
{
	actor->setParent(this);
	actors_.push_back(actor);
	if(programMode == PROGRAM_MODE_PRIMARY || programMode == PROGRAM_MODE_HEADLESS_PRIMARY)
		connect(actor.get(), &Actor::sigItemUpdate, this, &Item::requestUpdate);
	connect(this, &Item::updated, actor.get(), &Actor::onItemUpdated);

	std::shared_ptr<SensorActor> sensorActor = std::dynamic_pointer_cast<SensorActor>(actor);
	if(sensorActor)
		connect(&globalSensors, &SensorStore::sensorChangedState, sensorActor.get(), &SensorActor::sensorEvent);

	std::shared_ptr<Regulator> regulator = std::dynamic_pointer_cast<Regulator>(actor);
	if(regulator)
		connect(&globalSensors, &SensorStore::sensorChangedState, regulator.get(), &Regulator::sensorEvent);

	std::shared_ptr<PolynomalActor> polynomalActor = std::dynamic_pointer_cast<PolynomalActor>(actor);
	if(polynomalActor != nullptr )
		connect(&globalSensors, &SensorStore::sensorChangedState, polynomalActor.get(), &PolynomalActor::sensorEvent);

	std::shared_ptr<ScriptActor> scriptActor = std::dynamic_pointer_cast<ScriptActor>(actor);
	if(scriptActor != nullptr)
		connect(&globalSensors, &SensorStore::sensorChangedState, scriptActor.get(), &ScriptActor::sensorEvent);
}

bool Item::removeActor(std::shared_ptr<Actor> actor)
{
	for(unsigned int i = 0; i < actors_.size(); i++)
	{
		if(actors_[i] == actor)
		{
			actors_.erase(actors_.begin()+i);
			return true;
		}
	}
	return false;
}

void Item::setOverride(const bool in)
{
	ItemData::setOverride(in);
}

bool Item::getOverride()
{
	return ItemData::getOverride();
}

void Item::removeAllActors()
{
	actors_.clear();
}

std::vector< std::shared_ptr<Actor> >& Item::getActors()
{
	return actors_;
}

bool Item::hasActors()
{
	return actors_.size() > 0;
}

void Item::setActorsActive(bool in)
{
	for(unsigned i = 0; i < actors_.size(); i++)
		in ? actors_[i]->makeActive() : actors_[i]->makeInactive();
}

std::shared_ptr<Item> Item::loadItem(const QJsonObject& json)
{
	std::shared_ptr<Item> newItem = nullptr;
	if(json["Type"].toString("Item") == "Item")
		newItem = std::shared_ptr<Item>(new Item);
	else if(json["Type"].toString("") == "Relay")
		newItem = std::shared_ptr<Relay>(new Relay);
	else if(json["Type"].toString("") == "Message")
		newItem = std::shared_ptr<MessageItem>(new MessageItem);
	else if(json["Type"].toString("") == "System")
		newItem = std::shared_ptr<SystemItem>(new SystemItem);
	else if(json["Type"].toString("") == "Aux")
		newItem = std::shared_ptr<AuxItem>(new AuxItem);
	else if(json["Type"].toString("") == "Power")
		newItem = std::shared_ptr<PowerItem>(new PowerItem);
	else if(json["Type"].toString("") == "Rgb")
		newItem = std::shared_ptr<RgbItem>(new RgbItem);
	else if(json["Type"].toString("") == "Mqtt")
		newItem = std::shared_ptr<MqttItem>(new MqttItem);
	else
		qWarning()<<"Unable to load unkown item type: "<<json["Type"].toString();
	if(newItem)
	{
		newItem->load(json);
		newItem->setLoaded(true);
	}
	return newItem;
}

ItemUpdateRequest Item::createValueUpdateRequest(item_update_type_t type,
                                                 bool withActors)
{
	ItemUpdateRequest update;
	update.type = type;
	update.payload = *this;
	if(withActors)
		update.newActors = actors_;
	return update;
}
