#include <QJsonArray>
#include <QJsonObject>
#include <QtDebug>
#include <QString>
#include <unistd.h>
#include <iostream>
#include <QtCore>
#include <message.h>
#include "taskmanager.h"

TaskManager::TaskManager(QObject* parent) : QObject(parent), m_loop(nullptr) {
}

TaskManager::~TaskManager() {
	delete m_loop;
}

void TaskManager::open(const QUrl &bindingAddress)
{
	m_loop = new MessageEngine(bindingAddress);
	QObject::connect(m_loop, &MessageEngine::connected, this, &TaskManager::onConnected);
	QObject::connect(m_loop, &MessageEngine::messageReceived, this, &TaskManager::onMessageReceived);
}

void TaskManager::onConnected()
{
	query(); // let's not wait 3 seconds and issue the first call directly
	timer = new QTimer();
	connect(timer, SIGNAL(timeout()), this, SLOT(query()));
	timer->start(3000);

	loadAvg();
	loadAvgTimer = new QTimer();
	connect(loadAvgTimer, SIGNAL(timeout()), this, SLOT(loadAvg()));
	loadAvgTimer->start(1000);
}
void TaskManager::kill(int tid) {
	callService("kill_process", QJsonValue(tid));
}

void TaskManager::getExtraInfo(int tid) {
	callService("get_extra_info", QJsonValue(tid));
}

void TaskManager::loadAvg() {
	callService("get_load_avg", QJsonValue());
}

void TaskManager::query() {
	callService("get_process_list",
		QJsonValue(QJsonObject({{"processes", QJsonValue()}})));
	callService("get_netstat", QJsonValue());
}

void TaskManager::callService(const QString& command, QJsonValue value) {
	Message *msg = new Message();
	msg->createRequest("taskmanager", command, value);
	qDebug() << "sending message " << msg->toJson();
	m_loop->sendMessage(msg);
	delete msg;
}

void TaskManager::onMessageReceived(MessageType type, Message *message)
{
	if (message->isReply())
		ProcessResponse(message);
}

void TaskManager::ProcessResponse(Message *message)
{
	qDebug() << "got message " << message->toJson();
	QString msgType = message->replyData()["msgType"].toString();
	if (msgType.isNull())
		return; // no type supplied, ignoring

	if (QString::compare(msgType, "processList") == 0) {
		QJsonArray processes = message->replyData()["processes"].toArray();
		ProcessResponseTasklist(processes);
	}
	if (QString::compare(msgType, "extraInfo") == 0) {
		QJsonObject info = message->replyData()["info"].toObject();
		ProcessResponseExtraInfo(info);
	}
	if (QString::compare(msgType, "loadAvgInfo") == 0) {
		QJsonObject loadInfo = message->replyData()["loadInfo"].toObject();
		ProcessResponseLoadAvg(loadInfo);
	}
	if (QString::compare(msgType, "netStatInfo") == 0) {
		QJsonObject netstat = message->replyData()["netstat"].toObject();
		ProcessResponseNetStat(netstat);
	}
	// more response types to follow
}

void TaskManager::ProcessResponseTasklist(QJsonArray& processes)
{
	std::vector<ProcInfo> procs;

	if (processes.size() == 0) {
		// this is not a valid process list response
		return;
	}

	for(auto it = processes.constBegin(); it != processes.constEnd(); ++it) {
		ProcInfo Proc(it->toObject());
		procs.push_back(Proc);
	}

	int flag;
	if(m_procinfos.empty()){
		for(auto it = procs.begin(); it != procs.end(); ++it){ // if m_procinfos is empty then this is first call
			emit addProcess(it->cmd(), it->tid(), it->euid(), it->scpu(), it->ucpu(), it->resident_memory(), it->state()); 
		}			
	} else {
		for(auto it = procs.begin(); it != procs.end(); ++it){						// loop through procs, it = procs element (ProcInfo obj)
			flag = 0;
			for(auto it2 = m_procinfos.begin(); it2 != m_procinfos.end(); ++it2){	// loop through m_procinfos, it2 m_procinfos element (ProcInfo obj)
				// if(*it == *it2){  // if the same ID exists in both vectors
				if(it->tid() == it2->tid()){
					if(it->cmd() == it2->cmd()){ // if the name is still the same
						if(!(it == it2)){ // if the same process has new data
							emit updateProcess(it->cmd(), it->tid(), it->euid(), it->scpu(), it->ucpu(), it->resident_memory(), it->state());
							m_procinfos.erase(it2);
							flag = 1;
							break;
						}
					} else { // process in m_procinfos has died and a new one has its ID
						qDebug() << "The process ID has been reused for another process";
						emit updateProcess(it->cmd(), it->tid(), it->euid(), it->scpu(), it->ucpu(), it->resident_memory(), it->state());
						m_procinfos.erase(it2);
						flag = 1;
					}
				}
			}
			if(flag == 0){ // if no ID was found in old vector; that means it's a new process
				qDebug() << it->cmd() << " process has been added";
				emit addProcess(it->cmd(), it->tid(), it->euid(), it->scpu(), it->ucpu(), it->resident_memory(), it->state()); 
			}
		}
		for(auto it2 = m_procinfos.begin(); it2 != m_procinfos.end(); ++it2){ // remaining processes in m_procinfos are all dead
			qDebug() << "Dead processes are removed";
			qDebug() << it2->cmd();
			emit removeProcess(it2->tid());
		}
	}
	m_procinfos = procs;
}

void TaskManager::ProcessResponseExtraInfo(QJsonObject &info)
{
	QString infoString;

	if (info.size() == 0) {
		// this is not a valid process list response
		QTextStream(&infoString) << "procces is not available";
	} else {
		infoString = "Task : " + info["cmd"].toString() + "\n"
			+ "Exec start : " + info["exec_start"].toString() + "\n"
			+ "Exec runtime : " + info["vruntime"].toString() + "\n"
			+ "Prio : " + info["prio"].toString();
	}

	emit showProcessInfo(infoString);
}

void TaskManager::ProcessResponseLoadAvg(QJsonObject &loadInfo)
{
	if (loadInfo.size() == 0) {
		return;
	}

	emit updateLoadAverage(loadInfo["value"].toDouble());
}

void TaskManager::ProcessResponseNetStat(QJsonObject &netstat)
{
	if (netstat.size() == 0) {
		return;
	}

	unsigned int newInOctets = netstat["InOctets"].toInt();
	unsigned int newOutOctets = netstat["OutOctets"].toInt();

	emit updateNetworkStats(newInOctets - inOctets, newOutOctets - outOctets);
	inOctets = newInOctets;
	outOctets = newOutOctets;
}