#include "server_client.h"
#include "server.h"
#include "piqt.h"
#include "SH_network_types.h"
#include "SH_packages.h"
#include <QDebug>
#include <QCryptographicHash>
#include <QMetaObject>
#include <piethernet.h>
#include "ccm_SHS_shared.h"


ServerClient::ServerClient(ServerCore * s, SHNetworkTypes::ChannelType c): PIThread(), server(s), channel_type(c) {
	info.channel = c;
}


void ServerClient::init(PIIODevice * e) {
	device = e;
	tcp = new SHStreamTCP();
	diag = new PIDiagnostics();
	diag->setDisconnectTimeout(3);
	info.connected_time = QDateTime::currentDateTime();
	info.address = PI2QString(e->path());
	//ka_tm.reset();
	CONNECTU(device, threadedReadEvent, tcp, received);
	CONNECTU(tcp, packetReceivedSH, this, packetRec);
	CONNECTU(tcp, sendRequest, this, sendRequest);
	CONNECTU_QUEUED(this, postGraphicsRequest, this, recGraphicsRequest, this);
	PIThread::start();
}


void ServerClient::close() {
	//if (device)
	//	device->stopAndWait();
	closing = true;
}


void ServerClient::destroy() {
	//piCout << "~ServerClient ...";
	PIThread::stop();
	//piCout << "~ServerClient stop";
	if (!PIThread::waitForFinish(10000))
		PIThread::terminate();
	if (device)
		device->stopAndWait();
	//piCout << "~ServerClient stop done";
	piDeleteSafety(tcp);
	piDeleteSafety(diag);
	//piCout << "~ServerClient done";
}


bool ServerClient::isConnected() {
	if (!device || closing) return false;
	PIEthernet * eth = device->cast<PIEthernet>();
	if (eth) return eth->isConnected();
	PICloudServer::Client * cloud = device->cast<PICloudServer::Client>();
	if (cloud) return cloud->isOpened();
	return false;
}


void ServerClient::queueSend(const PIByteArray & d, bool gr_force) {
	if (d.isEmpty()) return;
	PIMutexLocker _ml(queue_mutex);
	if ((info.role != SHServer::RoleGraphics) || gr_force) {
		send_queue << d;
	} else {
		if (is_graphics_sended) {
			send_queue << d;
		} else {
			new_gr_queue << d;
		}
	}
	//piCout << "queueSend" << Q2PIString(hostname) << d.size();
}


bool ServerClient::isQueueSending() {
	PIMutexLocker _ml(queue_sending_mutex);
	return queue_sending;
}


void ServerClient::setQueueSending(bool yes) {
	PIMutexLocker _ml(queue_sending_mutex);
	queue_sending = yes;
}


void ServerClient::run() {
	queue_mutex.lock();
	if (info.role == SHServer::RoleGraphics && is_graphics_sended && !new_gr_queue.isEmpty()) {
		send_queue << new_gr_queue;
		new_gr_queue.clear();
	}
	PIDeque<PIByteArray> dq = send_queue;
	send_queue.clear();
	queue_mutex.unlock();
	if (dq.isEmpty()) {
		piMSleep(5);
		return;
	}
	setQueueSending(true);
	for (const PIByteArray & d: dq) {
		if (terminating)
			break;
		tcp->send(d);
	}
	setQueueSending(false);
	//ka_tm.reset();
}


PIString ServerClient::hostType() {
	PIString a = Q2PIString(info.OS_arch);
	if (info.OS_type == SHPlatform::Android) a = "all";
	return ::hostType(info.OS_type, a);
}


void ServerClient::updateInfo() {
	if (!diag) return;
	info.sended_bytes = diag->state().sended_bytes;
	info.received_bytes = diag->state().received_bytes;
	info.send_speed = PI2QString(diag->sendSpeed());
	info.receive_speed = PI2QString(diag->receiveSpeed());
}


void ServerClient::applyAdminParameter(const PacketParameter & p) {
	qDebug() << "applyAdminParameter" << p.id << p.value;
	switch (p.id) {
	case pLogIO: admin_log_io = p.value.toBool(); break;
	default: break;
	}
}


void ServerClient::setRole(int index) {
	if (new_user_role == index) return;
	new_user_role = index;
	role_changed = true;
}


void ServerClient::newRoleDone() {
	info.user_role = user_role = new_user_role;
	role_changed = false;
}


void ServerClient::sendRequest(PIByteArray data) {
	if (device) device->write(data);
	if (diag) diag->sended(data.size_s());
}


void ServerClient::sendGraphicHistoty(const QString & vname) {
	PIVector<PIPair<ullong, double>> history = server->engine.getVariableHistory(vname);
	//piCout << "sendGraphicHistoty" << Q2PIString(vname) << "...";
	const int frame_size = 1024;
	PIByteArray frag;
	frag.reserve(frame_size * 8 + sizeof(PacketHeader));
	if (!history.isEmpty()) {
		int fcnt = (history.size_s() - 1) / frame_size + 1, pst = 0, pcnt = 0;
		//piCout << "crypt_frag send" << fcnt << "frags";
		SHS::PacketGraphicData gd(vname);
		gd.data.reserve(frame_size);
		for (int i = 0; i < fcnt; ++i) {
			if (i == fcnt - 1) pcnt = history.size_s() - pst;
			else pcnt = frame_size;
			gd.data = PIVector<PIPair<ullong, double>>(history.data(pst), pcnt);
			pst += frame_size;
			frag = SHNetworkTypes::makeHeader(SHNetworkTypes::GraphicData);
			frag << gd;
			queueSend(frag, true);
			if (terminating) return;
		}
		graphics_last_sended[vname] = history.back().first;
		if (terminating) return;
	}
	frag = SHNetworkTypes::makeHeader(SHNetworkTypes::GraphicDone);
	frag << Q2PIString(vname);
	queueSend(frag, true);
	//piCout << "sendGraphicHistoty" << Q2PIString(vname) << "done";
}


void ServerClient::recGraphicsRequest(PIStringList gl) {
	piCoutObj << "GraphicRequest" << gl;
	graphics = PI2QStringList(gl);
	graphics_last_sended.clear();
	foreach (QString v, graphics) {
		graphics_last_sended[v] = 0;
		sendGraphicHistoty(v);
		if (terminating) return;
	}
	is_graphics_sended = true;
}


void ServerClient::packetRec(PIByteArray data) {
	//piCout << "packetRec" << data.toHex() << "...";
	if (diag) diag->received(data.size_s());
	//piCout << "packetRec" << data.toHex();
	PacketHeader hdr = SHNetworkTypes::takeHeader(data);
	if (hdr.type < 0) return;
	PIMutex & rmutex(server->rmutex);
	if ((hdr.type != SHNetworkTypes::Identification) && (hdr.type != SHNetworkTypes::ServerInfoRequest)) {
		//PIMutexLocker _ml(rmutex);
		if (!tcp->isCheckHeader() || tcp->wasError()) {
			qDebug() << "wrong in" << info.hostname << ", disconnect" << PI2QString(device->path());
			return;
		}
	}
	switch ((SHNetworkTypes::PacketType)hdr.type) {
	case SHNetworkTypes::KeepAlive: {
		PIByteArray kap = SHNetworkTypes::makeHeader(SHNetworkTypes::KeepAlive);
		rmutex.lock();
		if (!isQueueSending()) {
			device->write(kap);
			if (diag) diag->sended(kap.size_s());
		}
		rmutex.unlock();
		}
		break;
	case SHNetworkTypes::Identification: {
		PIMutexLocker _ml(rmutex);
		server->receivedIdentification(this, data);
		} break;
	case SHNetworkTypes::KeyProbe: {
		PIMutexLocker _ml(rmutex);
		server->receivedKey(this, data);
		} break;
	case SHNetworkTypes::GuiSyncEvent:
		server->sync.receiveObjectEvent(piqt(data));
		break;
	case SHNetworkTypes::ClientReady:
		active = true;
		break;
	case SHNetworkTypes::RemotePultPackageNameRequest: {
		PIMutexLocker _ml(rmutex);
		SHPackages::Package p = PACKAGES->packageForHost(hostType());
		PIByteArray ba = SHNetworkTypes::makeHeader(SHNetworkTypes::RemotePultPackageName);
		ba << p.name;
		piCoutObj << "answer to RemotePultPackageNameRequest" << p.name << "(" << info.OS_type << Q2PIString(info.OS_arch) << ")";
		queueSend(ba);
		} break;
	case SHNetworkTypes::RemotePultPackageContentRequest: {
		PIString host_type;
		{
		PIMutexLocker _ml(rmutex);
		host_type = hostType();
		}
		PACKAGES->loadPackageForHost(host_type);
		{
		PIMutexLocker _ml(rmutex);
		PIByteArray ba = SHNetworkTypes::makeHeader(SHNetworkTypes::RemotePultPackageContent);
		ba << PACKAGES->packageForHost(host_type);
		piCoutObj << "send package" << ba.size_s();
		queueSend(ba);
		} } break;
	case SHNetworkTypes::IdentificationRequest: {
		PIMutexLocker _ml(rmutex);
		PacketIdentification ident = PacketIdentification::makeLocal(0, SHS_NETWORK_VERSION, SHS_UISYNC_VERSION);
		PIByteArray ba = SHNetworkTypes::makeHeader(SHNetworkTypes::Identification);
		ba << ident;
		queueSend(ba);
		} break;
	case SHNetworkTypes::ServerInfoRequest: {
		PIMutexLocker _ml(rmutex);
		PacketServerInfo info;
		info.identify = PacketIdentification::makeLocal(0, SHS_NETWORK_VERSION, SHS_UISYNC_VERSION);
		info.channel = channel_type;
		if (channel_type == SHNetworkTypes::Cloud)
			info.connect_address = server->cloud_server.path() + "@" + server->cloud_server.serverName();
		info.name = server->server_name_;
		info.setUserRoles(RUNTIME->server_project.roles);
		PIByteArray ba = SHNetworkTypes::makeHeader(SHNetworkTypes::ServerInfo);
		ba << info;
		queueSend(ba);
		} break;
	case SHNetworkTypes::ChangeUserRole: {
		PIMutexLocker _ml(rmutex);
		ServerUserRole role;
		data >> role;
		int role_index = role.index;
		PIScopeExitCall failure([this,role_index](){
			PIByteArray ba = SHNetworkTypes::makeHeader(SHNetworkTypes::ChangeUserRoleResult);
			ba << role_index << int(1);
			queueSend(ba);
		});
		if (role.index < 0 || role.index >= RUNTIME->server_project.roles.size()) break;
		if (role.password_hash != RUNTIME->server_project.roles[role.index].password_hash) break;
		setRole(role.index);
		failure.cancel();
		PIByteArray ba = SHNetworkTypes::makeHeader(SHNetworkTypes::ChangeUserRoleResult);
		ba << role_index << int(0);
		queueSend(ba);
		} break;
	case SHNetworkTypes::AdminBAFUpload: {
		PIByteArray baf;
		data >> baf;
		piCoutObj << "received new baf" << baf.size_s();
		rmutex.lock();
		server->uploadedBAF(baf, this);
		rmutex.unlock();
		} break;
	case SHNetworkTypes::AdminDeviceForwardingRequest: {
		piCoutObj << "AdminDeviceForwardingRequest";
		PIVector<ServerDevice> dl;
		PIVector<PIIODevice * > devs = RUNTIME->connection.boundedDevices();
		piForeachC (PIIODevice * d, devs) {
			dl << ServerDevice(d, RUNTIME->connection.deviceNames(d));
		}
		PIByteArray ba = SHNetworkTypes::makeHeader(SHNetworkTypes::AdminDeviceForwardingPermit);
		ba << dl;
		PIMutexLocker _ml(rmutex);
		if (info.role != SHServer::RoleDeviceForward) {
			piCoutObj << Q2PIString(info.hostname) << "is not in DeviceForward role, ignore";
			return;
		}
		server->df_client = this;
		server->df_device.clear();
		server->engine.connection()->setInactive(true);
		tcp->send(ba);
		} break;
	case SHNetworkTypes::AdminDeviceForwardingSelect: {
		PIMutexLocker _ml(rmutex);
		if (info.role != SHServer::RoleDeviceForward) {
			piCoutObj << Q2PIString(info.hostname) << "is not in DeviceForward role, ignore";
			return;
		}
		data >> server->df_device;
		piCoutObj << "AdminDeviceForwardingSelect" << server->df_device;
		} break;
	case SHNetworkTypes::AdminDeviceForwardingWrite: {
		PIString path;
		PIByteArray ba;
		data >> path >> ba;
		piCoutObj << "AdminDeviceForwardingWrite" << path << ba.size_s();
		RUNTIME->connection.writeByFullPath(path, ba);
		} break;
	case SHNetworkTypes::AdminRestart:
		piCoutObj << "AdminRestart";
		tcp->send(SHNetworkTypes::makeHeader(SHNetworkTypes::AdminRestartQueued));
		server->restart(QString());
		break;
	case SHNetworkTypes::AdminSetParameter: {
		PacketParameter p;
		data >> p;
		applyAdminParameter(p);
		} break;
	case SHNetworkTypes::GraphicRequest: {
		PIStringList vars;
		data >> vars;
		postGraphicsRequest(vars);
		//QMetaObject::invokeMethod(this, "recGraphicsRequest", Qt::QueuedConnection, Q_ARG(QStringList, PI2QStringList(vars)));
		} break;
	default: break;
	}
}
