#include "server_core.h"
#include "piqt.h"
#include "SH_packages.h"
#include "SH_network_types.h"
#include "SH_plugins_manager.h"
#include <QDebug>
#include <QMetaEnum>
#include <QCryptographicHash>
#include <QResource>
#include <qad_locations.h>
#include <piethernet.h>
#include "ccm_SHS_shared.h"

//const double keepAliveInterval_s = 2.;


ServerCore::ServerCore(bool reservation): QObject(), SHMulticast(), eth_tcp(PIEthernet::TCP_Server) {
	PACKAGES;
	file_ui_state     = QAD::userPath(QAD::ltCache, "ui_state"    );
	file_ui_state_new = QAD::userPath(QAD::ltCache, "ui_state_new");
	with_reservation = reservation;
	auto il = PIEthernet::interfaces();
	for (const auto & i: il) {
		piCout << i.name << i.address;
		if (!i.isLoopback() && !i.address.isEmpty() && i.address != "0.0.0.0") {
			my_ip = i.address;
			break;
		}
	}
	seed = generateID();//PINetworkAddress(my_ip, 0).ip();
	addressesChanged();
	piCoutObj << PICoutManipulators::Hex << seed << PINetworkAddress(seed, 0);
	connect(&engine, SIGNAL(variableHistoryAdded(QString,QPair<QDateTime,double>)), this, SLOT(variableHistoryAdded(QString,QPair<QDateTime,double>)), Qt::DirectConnection);
	connect(&engine, SIGNAL(debug(QString)), this, SLOT(scriptDebug(QString)), Qt::DirectConnection);
	CONNECTU(&eth_tcp, newConnection, this, newTCPConnection);
	CONNECTU(&cloud_server, newConnection, this, newCloudConnection);
}


ServerCore::~ServerCore() {
	cloud_server.close();
	cloud_server.stop();
	engine.remove();
	PIByteArray sba = SHNetworkTypes::makeHeader(SHNetworkTypes::ServerRemove);
	sba << seed;
	piCoutObj << "send ServerRemove seed =" << seed;
	send(sba);
	killTimer(timer_save_state);
	killTimer(timer_admin);
	killTimer(timer_sync);
	killTimer(timer);
	stopRead();
	PIVector<ServerClient * > rl = remotes.values();
	for (ServerClient * r: rl)
		if (r) {
			r->destroy();
			delete r;
		}
	remotes.clear();
	eth_tcp.stop();
	PIVector<PIEthernet * > cl ; eth_tcp.clients();
	for (PIEthernet * c: cl)
		c->stop();
	if (form) {
		delete form;
		form = 0;
	}
	if (bqr_content.data())
		QResource::unregisterResource((uchar*)bqr_content.data());
}


void ServerCore::startEth() {
	eth_tcp.listen("0.0.0.0", SHNetworkTypes::clientPort, true);
	QString cloud_addr = RUNTIME->server_data.project.propertyValueByName("cloud_address").toString();
	bool cloud_enabled = RUNTIME->server_data.project.propertyValueByName("cloud_enabled").toBool();
	if (!cloud_addr.isEmpty() && cloud_enabled) {
		auto sa = parseServerAddress(cloud_addr);
		cloud_server.setPath(sa.ipAddress());
		cloud_server.setServerName(getThisCloudName());
		cloud_server.startThreadedRead();
		log(QString("Connect to cloud %1@%2").arg(sa.server, PI2QString(cloud_server.serverName())));
	}
	timer_sync = startTimer(100);
	timer_admin = startTimer(1000);
	SHMulticast::startRead();
}


void ServerCore::start() {
	log("Server started as " + QString(server_state == ServerCore::ssWorkMain ? "main" : "reserve"), SHServer::LogSuccess);
}


void ServerCore::loadRCC(const QString & p) {
	QFile f(p);
	if (!f.exists()) {
		log("File \"" + QFileInfo(f).absoluteFilePath() + "\" doesn`t exists!", SHServer::LogError);
		return;
	}
	if (!f.open(QIODevice::ReadOnly)) {
		log("Can`t open resource \"" + QFileInfo(f).absoluteFilePath() + "\"!", SHServer::LogWarning);
		return;
	}
	bqr_content = Q2PIByteArray(f.readAll());
	rcc_hash = bqr_content.hash();
	f.close();
	if(bqr_content.isEmpty()) return;
	if (!QResource::registerResource((uchar*)bqr_content.data())) {
		log("Can`t open resource \"" + QFileInfo(f).absoluteFilePath() + "\"!", SHServer::LogWarning);
		return;
	}
	log("Loaded resource \"" + QFileInfo(f).absoluteFilePath() + "\"", SHServer::LogSuccess);
}


void ServerCore::loadUI(const QString & p) {
	RUNTIME->widget_list.clear();
	QFile f(p);
	if (!f.exists()) {
		log("File \"" + QFileInfo(f).absoluteFilePath() + "\" doesn`t exists!", SHServer::LogError);
		return;
	}
	f.open(QIODevice::ReadOnly);
	form = ui_loader.load(&f);
	if (!form) {
		log("Can`t load form \"" + QFileInfo(f).absoluteFilePath() + "\"!", SHServer::LogError);
		return;
	}
	f.seek(0);
	ui_content = Q2PIByteArray(f.readAll());
	ui_hash = ui_content.hash();
	ui_content = Q2PIByteArray(qCompress(PI2QByteArray(ui_content)));
	sync.assign(&ui_loader, true);
	RUNTIME->json_ui = sync.json_ui;
	RUNTIME->widget_list = ui_loader.loaded_widgets;
	form->setWindowTitle(QFileInfo(p).baseName());
	ui_loader.adjust(form);
	log("Loaded form \"" + QFileInfo(f).absoluteFilePath() + "\"", SHServer::LogSuccess);
	loadedUI();
}


void ServerCore::loadQS(const QString & p) {
	QFile f(p);
	if (!f.exists()) {
		log("File \"" + QFileInfo(f).absoluteFilePath() + "\" doesn`t exists!", SHServer::LogWarning);
		return;
	}
	if (!f.open(QIODevice::ReadOnly)) {
		log("Can`t open script \"" + QFileInfo(f).absoluteFilePath() + "\"!", SHServer::LogWarning);
		return;
	}
	log("Loaded script \"" + QFileInfo(f).absoluteFilePath() + "\"", SHServer::LogSuccess);
	script_dir = QFileInfo(f).absoluteDir();
	//script = QString::fromUtf8(f.readAll());
	file_script = f.fileName();
	QObject::connect(&engine, SIGNAL(error(int,QString,QString,QString)), this, SLOT(scriptError(int,QString,QString,QString)));
	QObject::connect(&engine, SIGNAL(log(QString,int)), this, SLOT(scriptLog(QString,int)));
	QObject::connect(&engine, SIGNAL(message(QString,int)), this, SLOT(scriptMessage(QString,int)));
	QObject::connect(&engine, SIGNAL(logIO(QString)), this, SLOT(IOLog(QString)));
}


void ServerCore::loadDAT(const QString & p) {
	QFile f(p);
	if (!f.exists()) {
		log("File \"" + QFileInfo(f).absoluteFilePath() + "\" doesn`t exists!", SHServer::LogWarning);
		return;
	}
	if (!QFile::exists(p)) {
		log("Can`t open database \"" + QFileInfo(f).absoluteFilePath() + "\"!", SHServer::LogWarning);
		return;
	}
	log("Loaded database \"" + QFileInfo(f).absoluteFilePath() + "\"", SHServer::LogSuccess);
	RUNTIME->setServerData(loadFromFile<SHS::ServerData>(p));
	server_key_  = Q2PIString(RUNTIME->server_data.project.propertyValueByName("key" ).toString());
	server_name_ = Q2PIString(RUNTIME->server_data.project.propertyValueByName("name").toString());
	piCout << "key" << server_key_;
	loadedDAT();
}


void ServerCore::loadCM(const QString & p) {
	QFile f(p);
	if (!f.exists()) {
		log("File \"" + QFileInfo(f).absoluteFilePath() + "\" doesn`t exists!", SHServer::LogWarning);
		return;
	}
	log("Loaded connection \"" + QFileInfo(f).absoluteFilePath() + "\"", SHServer::LogSuccess);
	RUNTIME->connection.loadFromCMFile(p);
	connect(&RUNTIME->connection, SIGNAL(qDataReceivedEvent(QString,QByteArray)), this, SLOT(connDataReceivedEvent(QString,QByteArray)));
	loadedCM();
}


void ServerCore::loadVAR(const QString & p) {
	QFile f(p);
	if (!f.exists()) {
		log("File \"" + QFileInfo(f).absoluteFilePath() + "\" doesn`t exists!", SHServer::LogWarning);
		return;
	}
	log("Loaded variables \"" + QFileInfo(f).absoluteFilePath() + "\"", SHServer::LogSuccess);
	vars = loadFromFile<typeof(vars)>(p, SHS::ScriptVariable::Version);
	state.variables.clear();
	for (const auto & v: vars) {
		ServerVariable sv;
		sv.name = v.name;
		sv.store = v.store;
		state.variables << sv;
	}
	loadedVAR();
}


void ServerCore::loadBAF(const PIString & p) {
	piCout << "loadBAF" << p;
	baf_path = p;
	RUNTIME->server_project = SHS::ServerProject();
	if (baf_path.isEmpty()) return;
	RUNTIME->server_data.res_path = QDir::tempPath() + "/res";
	QStringList fl = ass.unpack(PI2QString(baf_path)), qspl;
	QString bqrp, uip, dbp, cmp, varp, sdpp;
	for (const auto & s: fl) {
		if (s.endsWith(".rcc")) bqrp = s;
		if (s.endsWith(".uip")) uip  = s;
		if (s.endsWith(".dat") && !s.endsWith(QString(SHFileSuffix) + ".dat")) dbp = s;
		if (s.endsWith(".qs"))  qspl << s;
		if (s.endsWith(".cm"))  cmp  = s;
		if (s.endsWith(".var")) varp = s;
		if (s.endsWith(".sdp")) sdpp = s;
	}
	if (!bqrp.isEmpty()) loadRCC(bqrp);
	loadUI(uip);
	if (!cmp.isEmpty())  loadCM(cmp);
	if (!dbp.isEmpty())  loadDAT(dbp);
	RUNTIME->server_data.res_path = QDir::tempPath() + "/res";
	if (!varp.isEmpty())
		loadVAR(varp);
	if (!sdpp.isEmpty())
		loadProject(sdpp);
	else if (qspl.size() == 1) {
		loadQS(qspl[0]);
		RUNTIME->server_project.qs_main = file_script;
		RUNTIME->server_project.qs << file_script;
	}
	if (!loadPlugins())
		engine.remove();
}


void ServerCore::loadProject(const QString & p) {
	QFile f(p);
	if (!f.exists()) {
		log("File \"" + QFileInfo(f).absoluteFilePath() + "\" doesn`t exists!", SHServer::LogWarning);
		return;
	}
	if (!RUNTIME->server_project.load(p, false)) {
		log("Can`t load project \"" + QFileInfo(f).absoluteFilePath() + "\"!", SHServer::LogWarning);
		return;
	}
	log("Loaded server project \"" + QFileInfo(f).absoluteFilePath() + "\"", SHServer::LogSuccess);
	loadQS(QFileInfo(f).dir().absoluteFilePath(RUNTIME->server_project.qs_main));
}


void ServerCore::timerEvent(QTimerEvent * e) {
	if (e->timerId() == timer_save_state) {
		saveUIState();
		return;
	}
	if (e->timerId() == timer_sync) {
		if (with_reservation) {
			smutex.lock();
			if (server_state > ServerCore::ssSync) {
				for (int i = 0; i < servers.size_s(); ++i) {
					servers[i].missed++;
					if (servers[i].missed > 10) {
						piCoutObj << "remove by timeout" << servers[i].seed;
						if (servers[i].seed == seed_main)
							selectNewMain();
						servers.remove(i);
						i--;
					}
				}
			}
			smutex.unlock();
			if (sync_skip > 0) {
				sync_skip--;
				return;
			}
			switch (server_state) {
			case ServerCore::ssStart: {
				PIByteArray sba = SHNetworkTypes::makeHeader(SHNetworkTypes::ServerNew);
				sba << seed;
				piCoutObj << "send ServerNew seed =" << seed << ", ip =" << my_ip;
				send(sba);
				selectNewMain();
				} break;
			case ServerCore::ssSync:
				if (seed_main == 0) {
					int mseed = seed;
					int mi = -1;
					smutex.lock();
					for (int i = 0; i < servers.size_s(); ++i) {
						if (mseed > servers[i].seed) {
							mseed = servers[i].seed;
							mi = i;
						}
					}
					smutex.unlock();
					seed_main = mseed;
					main_ip = PINetworkAddress(mseed, 0).ipString();
					if (mi < 0)
						startAsMain();
					else
						startAsReserve();
				}
				break;
			case ServerCore::ssWorkReserve:
				main_missed++;
				if (main_missed > 10) {
					main_missed = 0;
					selectNewMain();
				}
			case ServerCore::ssWorkMain:
				{
				PIByteArray sba = SHNetworkTypes::makeHeader(SHNetworkTypes::ServerPing);
				sba << seed;
				//piCoutObj << "send ServerPing seed =" << seed;
				send(sba);
				}
				break;
			}
		} else startAsMain();
		return;
	}
	if (e->timerId() == timer_admin) {

		lmutex.lock();
		PacketServerLog pck_log, pck_log_hist;
		pck_log.entries = std::move(admin_log);
		admin_log.clear();
		auto mq = std::move(message_queue);
		message_queue.clear();
		lmutex.unlock();

		rmutex.lock();
		state.clients.clear();
		PIVector<ServerClient * > rl = remotes.values();
		for (ServerClient * r: rl) {
			if (!r) continue;
			r->updateInfo();
			state.clients << r->info;
		}
		state.devices.clear();
		PIVector<PIIODevice * > devs = RUNTIME->connection.boundedDevices();
		for (PIIODevice * d: devs) {
			if (!d) continue;
			ServerDeviceInfo di;
			di.classname = QLatin1String(d->className());
			di.path = PI2QString(d->path());
			di.mode = d->mode();
			PIDiagnostics * diag = RUNTIME->connection.diagnostic(d);
			if (diag) {
				di.sended_bytes = diag->state().sended_bytes;
				di.received_bytes = diag->state().received_bytes;
				di.send_speed = PI2QString(diag->sendSpeed());
				di.receive_speed = PI2QString(diag->receiveSpeed());
			}
			state.devices << di;
		}
		for (auto & v: state.variables) {
			auto val = engine.getVariableValue(v.name);
			v.changed = val.first;
			v.value = val.second;
		}

		PIByteArray msg_state;
		msg_state = SHNetworkTypes::makeHeader(SHNetworkTypes::AdminState);
		msg_state << state;

		for (ServerClient * r: rl) {
			if (!r) continue;

			if (r->info.role == SHServer::RoleMessages) {
				for (const auto & m: mq) {
					PIByteArray ba = SHNetworkTypes::makeHeader(SHNetworkTypes::Message);
					ba << m;
					r->queueSend(ba);
				}
			}

			if (r->info.role == SHServer::RoleAdmin) {
				if (!r->log_sended) {
					r->log_sended = true;
					pck_log_hist.entries = admin_log_history;
					PIByteArray msg = SHNetworkTypes::makeHeader(SHNetworkTypes::AdminLog);
					msg << pck_log_hist;
					r->queueSend(msg);
				}
				if (!msg_state.isEmpty())
					r->queueSend(msg_state);
				if (!pck_log.entries.isEmpty() || !r->admin_io_log.isEmpty()) {
					PacketServerLog pckt;
					pckt.entries << pck_log.entries << r->admin_io_log;
					pckt.entries.sort([](const LogEntry & t0, const LogEntry & t1){return t0.time < t1.time;});
					r->admin_io_log.clear();
					PIByteArray msg = SHNetworkTypes::makeHeader(SHNetworkTypes::AdminLog);
					msg << pckt;
					r->queueSend(msg);
				}
			}
		}
		rmutex.unlock();

		for (const auto & e: pck_log.entries) {
			admin_log_cur_size += e.text.size();
			admin_log_history << e;
			while (admin_log_cur_size > admin_log_max_size)
				admin_log_cur_size -= admin_log_history.take_front().text.size();
		}

		return;
	}
	rmutex.lock();

	sync.buildDiffForRoles();

	PIVector<ServerClient * > rl = remotes.values(), to_remove;
	for (ServerClient * r: rl) {
		bool disconn = !r->isConnected();
		if (r) {if (r->tcp->wasError()) disconn = true;}
		if (disconn) {
			log("Client " + PI2QString(r->device->path()) + " disconnected", SHServer::LogInfo);
			//piCout << "disconn ...";
			r->close();
			//piCout << "disconn close";
			if (df_client == r) {
				df_client = 0;
				df_device.clear();
				engine.connection()->setInactive(false);
			}
			to_remove << r;
			//piCout << "disconn done";
		}
		r->maybeCallQueuedEvents();
		r->updateInfo();
		if (!r->tcp || !r->active || (r->info.role != SHServer::RoleGui)) continue;
		PIByteArray diff;
		if (r->role_changed) {
			diff = Q2PIByteArray(sync.buildTransitionDiff(r->user_role, r->new_user_role));
			qDebug() << "send transitionsl diff" << r->user_role << "->" << r->new_user_role << diff.size();
			r->newRoleDone();
			if (!diff.isEmpty()) {
				diff.insert(0, SHNetworkTypes::makeHeader(SHNetworkTypes::GuiSyncDiff));
				r->queueSend(diff);
			}
			diff.clear();
		}
		diff = sync.diffForRole(r->user_role);
		if (!diff.isEmpty()) {
			diff.insert(0, SHNetworkTypes::makeHeader(SHNetworkTypes::GuiSyncDiff));
			r->queueSend(diff);
		}
		//piCout << "diff role" << r->user_role << diff.size();
	}
	for (ServerClient * r: to_remove) {
		remotes.remove(r->device);
		r->destroy();
		delete r->device;
		delete r;
	}
	rmutex.unlock();
}


bool ServerCore::hasServer(const PIString & ip) const {
	for (const auto & s: servers)
		if (s.ip == ip)
			return true;
	return false;
}


bool ServerCore::loadPlugins() {
	auto plugins_list = RUNTIME->server_data.used_plugins | RUNTIME->server_project.used_plugins;
	qDebug() << "used plugins:" << plugins_list;
	if (!RUNTIME->plugins_manager->loadMandatory(plugins_list.values())) {
		auto remains = RUNTIME->plugins_manager->notLoadedPlugins();
		qDebug() << "Can`t load necessary plugins:" << remains << "!";
		log("Can`t load necessary plugins: " + remains.join(", "), SHServer::LogError);
		return false;
	}
	engine.assignForm(form);
	engine.assignConnection(&(RUNTIME->connection));
	engine.createBlockObjects();
	engine.assignVariables(vars);
	engine.prepare();
	return true;
}


void ServerCore::startAsMain() {
	if (server_state == ServerCore::ssWorkMain) return;
	piCoutObj << "startAsMain";
	server_state = ServerCore::ssWorkMain;
	if (timer > 0) killTimer(timer);
	timer = startTimer(100);
	if (timer_save_state > 0) killTimer(timer_save_state);
	timer_save_state = startTimer(10000);
	sync_skip = 0;
	start();
	engine.setDir(script_dir);
	engine.loadFromProject(RUNTIME->server_project);
	engine.start();
	sync.resetRoles();
	for (const auto & r: RUNTIME->server_project.roles) {
		sync.restoreSourceState();
		engine.prepareForRole(r);
		sync.saveForRole(r.index);
	}
	sync.restoreSourceState();
	engine.connection()->start();
	restoreUIState();
	engine.started();
	startedAsMain();
	ass.clear();
}


void ServerCore::startAsReserve() {
	if (server_state == ServerCore::ssWorkReserve) return;
	piCoutObj << "startAsReserve";
	server_state = ServerCore::ssWorkReserve;
	if (timer > 0) killTimer(timer);
	timer = 0;
	sync_skip = 0;
	start();
	startedAsReserve();
}


void ServerCore::selectNewMain() {
	piCoutObj << "selectNewMain";
	seed_main = 0;
	server_state = ServerCore::ssSync;
	sync_skip = 10;
}


void ServerCore::saveUIState() {
	QByteArray diff = sync.buildDiff(true);
	QFile f(file_ui_state_new);
	f.open(QIODevice::ReadWrite);
	f.resize(0);
	QDataStream s(&f);
	s.setVersion(QDataStream::Qt_5_13);
	s << uint(SHUISync::CurrentVersion) << diff;
	f.close();
	QFile::remove(file_ui_state);
	QFile::rename(file_ui_state_new, file_ui_state);
}


void ServerCore::restoreUIState() {
	if (!form) return;
	QFile f(file_ui_state);
	if (!f.open(QIODevice::ReadOnly)) {
		f.setFileName(file_ui_state_new);
		if (!f.open(QIODevice::ReadOnly))
			return;
	}
	if (f.size() < 8) return;
	QDataStream s(&f);
	s.setVersion(QDataStream::Qt_5_13);
	QByteArray data;
	uint v = 0;
	s >> v >> data;
	if (v == SHUISync::Version3) {
		data = sync.convertFromVersion3(data);
		v = SHUISync::CurrentVersion;
		piCoutObj << "UI state converted from version" << SHUISync::Version3;
	}
	if (v != SHUISync::CurrentVersion) {
		piCoutObj << "Saved UI state with wrong version, ignore";
		return;
	}
	sync.applyDiff(data);
}


void ServerCore::uploadedBAF(const PIByteArray & baf, ServerClient * r) {
	if (baf.isEmpty() || !r) return;
	if (r->info.role != SHServer::RoleAdmin) {
		piCoutObj << Q2PIString(r->info.hostname) << "is not in Admin role, ignore new .baf!";
		return;
	}
	if (baf_path.isEmpty()) {
		piCoutObj << "Can`t update .baf, server was started without .baf file!";
		r->tcp->send(SHNetworkTypes::makeHeader(SHNetworkTypes::AdminBAFFail));
		return;
	}
	QFile bf(PI2QString(baf_path));
	if (!bf.open(QIODevice::ReadWrite)) {
		piCoutObj << "Can`t update .baf, can`t open \"" << baf_path << "\"!";
		r->tcp->send(SHNetworkTypes::makeHeader(SHNetworkTypes::AdminBAFFail));
		return;
	}
	bf.resize(0);
	bf.write(PI2QByteArray(baf));
	bf.close();
	piCoutObj << "Restarting ...";
	r->tcp->send(SHNetworkTypes::makeHeader(SHNetworkTypes::AdminBAFOk));
	emit zeroFailure();
	emit restart(PI2QString(baf_path));
}


PacketParameterList ServerCore::adminParameters() const {
	PacketParameterList ret;
	ret.parameters << PacketParameter(ServerClient::pLogIO, "Log IO", false);
	return ret;
}


void ServerCore::connDataReceivedEvent(QString from, QByteArray data) {
	PIMutexLocker _ml(rmutex);
	if (!df_client || df_device.isEmpty()) return;
	if (PI2QString(df_device) != from) return;
	PIByteArray ba = SHNetworkTypes::makeHeader(SHNetworkTypes::AdminDeviceForwardingRead);
	ba << (Q2PIByteArray(data));
	df_client->tcp->send(ba);
}


void ServerCore::variableHistoryAdded(QString vname, QPair<QDateTime, double> value) {
	//qDebug() << "history add" << vname;
	SHS::PacketGraphicData gd(vname);
	ullong time = value.first.toMSecsSinceEpoch();
	gd.data << PIPair<ullong, double>(time, value.second);
	PIByteArray ba = SHNetworkTypes::makeHeader(SHNetworkTypes::GraphicData);
	ba << gd;
	PIMutexLocker _ml(rmutex);
	PIVector<ServerClient * > rl = remotes.values();
	piForeach (ServerClient * r, rl)
		if (r->tcp && r->active && r->info.role == SHServer::RoleGraphics) {
			if (r->graphics.contains(vname)) {
				r->queueSend(ba);
				r->graphics_last_sended[vname] = time;
			}
		}
}


void ServerCore::receivedSH(PIByteArray data, uint rec_id) {
	//piCout << "received" << data;
	PacketHeader hdr = SHNetworkTypes::takeHeader(data);
	if (hdr.type < 0) return;
	//piCout << "received type" << hdr.type << rec_id << data.size();
	int _seed;
	PIString _ip;
	PIByteArray ba;
	//piCout << "received" << type;
	if (with_reservation) {
		switch ((SHNetworkTypes::MulticastType)hdr.type) {
		case SHNetworkTypes::ServerNew:
			data >> _seed;
			if (_seed == seed) return;
			_ip = PINetworkAddress(_seed, 0).ipString();
			piCoutObj << "recv ServerNew seed =" << _seed << ", ip =" << _ip;
			smutex.lock();
			if (hasServer(_ip)) {
				smutex.unlock();
				return;
			}
			servers << (ServerSync){_seed, ssStart, _ip, 0};
			piCoutObj << "add server seed =" << _seed << ", ip =" << _ip;
			ba = SHNetworkTypes::makeHeader(SHNetworkTypes::ServerNew);
			ba << seed << my_ip;
			piCoutObj << "send ServerNew seed =" << seed << ", ip =" << my_ip;
			send(ba);
			if (server_state == ServerCore::ssWorkMain) {
				piMSleep(50);
				ba = SHNetworkTypes::makeHeader(SHNetworkTypes::ServerAlreadyMain);
				ba << seed;
				piCoutObj << "send ServerAlreadyMain seed =" << seed << ", ip =" << my_ip;
				send(ba);
			}
			smutex.unlock();
			break;
		case SHNetworkTypes::ServerRemove:
			data >> _seed;
			if (_seed == seed) return;
			piCoutObj << "recv ServerRemove seed =" << _seed;
			smutex.lock();
			for (int i = 0; i < servers.size_s(); ++i)
				if (servers[i].seed == _seed) {
					if (seed_main == _seed)
						selectNewMain();
					servers.remove(i);
					i--;
				}
			smutex.unlock();
			break;
		case SHNetworkTypes::ServerAlreadyMain:
			data >> _seed;
			if (_seed == seed) return;
			piCoutObj << "recv ServerAlreadyMain seed =" << _seed;
			seed_main = _seed;
			startAsReserve();
			break;
		case SHNetworkTypes::ServerPing:
			data >> _seed;
			if (seed_main == _seed)
				main_missed = 0;
			smutex.lock();
			for (int i = 0; i < servers.size_s(); ++i)
				if (servers[i].seed == _seed)
					servers[i].missed = 0;
			smutex.unlock();
			//seed_main = _seed;
			//piCoutObj << "recv ServerPing seed =" << _seed;
			break;
		default: break;
		}
	}
	if (server_state != ServerCore::ssWorkMain) return;
	//piCout << "received type" << type << rec_id << data.size();
	if (hdr.type == SHNetworkTypes::Request) {
		//PIMutexLocker locker(mcast_mutex);
		PIStringList addresses;
		data >> addresses;
		PIString addr;
		for (const PIString & a: addresses) {
			for (const PISSPair & sa: addresses_server) {
				if (a == sa.first) {
					addr = sa.second;
					break;
				}
			}
			if (!addr.isEmpty())
				break;
		}
		//piCout << "return address" << addr;
		PacketServerInfo info;
		info.identify = PacketIdentification::makeLocal(0, SHS_NETWORK_VERSION, SHS_UISYNC_VERSION);
		info.identify.hashes.hash_ui = ui_hash;
		info.identify.hashes.hash_rcc = rcc_hash;
		info.connect_address = addr;
		info.name = server_name_;
		PIByteArray sba = SHNetworkTypes::makeHeader(SHNetworkTypes::Reply);
		sba << info;
		send(sba, rec_id);
	}
}


void ServerCore::addressesChanged() {
	addresses_server.clear();
	PIEthernet::InterfaceList ilist = PIEthernet::interfaces();
	piForeachC (PIEthernet::Interface & i, ilist) {
		addresses_server << PISSPair(PIEthernet::applyMask(i.address, i.netmask), i.address);
	}
}


void ServerCore::newTCPConnection(PIEthernet * client) {
	//piCout << "newConnection" << client;
	log("Client connected from " + PI2QString(client->path()) + " (TCP)", SHServer::LogInfo);
	client->setDebug(false);
	client->setReadTimeout(60000.);
	client->setWriteTimeout(60000.);
	client->setParameter(PIEthernet::DisonnectOnTimeout, false);
	ServerClient * r = new ServerClient(this, SHNetworkTypes::TCP);
	//CONNECTU(r, packetReceived, this, packetReceived)
	r->init(client);
	//piCout << "newConnection lock ...";
	rmutex.lock();
	remotes[client] = r;
	rmutex.unlock();
	client->startThreadedRead();
	//piCout << "newConnection lock done";
}


void ServerCore::newCloudConnection(PICloudServer::Client * client) {
	piCout << "newCloudConnection" << client;
	log("Client connected from " + PI2QString(client->path()) + " (Cloud)", SHServer::LogInfo);
	//client->setDebug(false);
	ServerClient * r = new ServerClient(this, SHNetworkTypes::Cloud);
	//CONNECTU(r, packetReceived, this, packetReceived)
	r->init(client);
	//piCout << "newConnection lock ...";
	rmutex.lock();
	remotes[client] = r;
	rmutex.unlock();
	//piCout << "newConnection lock done";
	client->startThreadedRead();
}


void ServerCore::receivedIdentification(ServerClient * r, PIByteArray data) {
	PIByteArray ba;
	PacketIdentification p;
	data >> p;
	r->id = r->tcp->lastRecID();
	r->info.id = r->id;
	r->info.OS_type = (SHPlatform::OSType)p.os_type;
	r->info.OS_arch = PI2QString(p.arch);
	r->info.OS_version = PI2QString(p.os_version);
	r->info.hostname = PI2QString(p.hostname);
	r->info.role = (SHServer::ClientRole)p.role;

	r->net_version = p.net_version;
	if (r->net_version != SHS_NETWORK_VERSION) {
		qDebug() << "invalid network version" << r->net_version;
		log(tr("Client \"%1\" invalid network version: %2!").arg(r->info.hostname).arg(r->net_version), SHServer::LogWarning);
		ba = SHNetworkTypes::makeHeader(SHNetworkTypes::NetworkVersionMismatch);
		ba << int(SHS_NETWORK_VERSION);
		r->tcp->send(ba);
		r->close();
		return;
	}

	r->sync_version = p.sync_version;
	if (r->sync_version != SHS_UISYNC_VERSION) {
		qDebug() << "invalid sync version" << r->sync_version;
		log(tr("Client \"%1\" invalid sync version: %2!").arg(r->info.hostname).arg(r->sync_version), SHServer::LogWarning);
		ba = SHNetworkTypes::makeHeader(SHNetworkTypes::GuiSyncVersionMismatch);
		ba << int(SHS_UISYNC_VERSION);
		r->tcp->send(ba);
		r->close();
		return;
	}

	if (r->info.role == SHServer::RoleAdmin || r->info.role == SHServer::RoleDeviceForward) {
		if (admin_password != p.admin_password) {
			qDebug() << "invalid admin password!";
			log(tr("Client \"%1\" invalid admin password!").arg(r->info.hostname), SHServer::LogWarning);
			ba = SHNetworkTypes::makeHeader(SHNetworkTypes::AdminPasswordFail);
			r->tcp->send(ba);
			r->close();
			return;
		}
		ba = SHNetworkTypes::makeHeader(SHNetworkTypes::AdminPasswordAccepted);
		r->tcp->send(ba);
		ba = SHNetworkTypes::makeHeader(SHNetworkTypes::AdminParameterList);
		ba << adminParameters();
		r->tcp->send(ba);
	}

	//piCoutObj << "my" << ui_hash << rcc_hash;
	//piCoutObj << "cl" << p.hashes.hash_ui << p.hashes.hash_rcc;
	r->upload_ui = (p.hashes.hash_ui != ui_hash);
	r->upload_rcc = (p.hashes.hash_rcc != rcc_hash);
	r->tcp->setCheckHeader(true);
	r->tcp->setID(r->id);
	//qDebug() << "ident" << int(r->info.OS_type) << r->info.OS_arch << r->info.OS_version;
}


void ServerCore::receivedKey(ServerClient * r, PIByteArray data) {
	PIByteArray ba;
	if (r->sync_version != SHS_UISYNC_VERSION) {
		qDebug() << "invalid sync version" << r->sync_version;
		log(tr("Client \"%1\" invalid sync version: %2!").arg(r->info.hostname).arg(r->sync_version), SHServer::LogWarning);
		ba = SHNetworkTypes::makeHeader(SHNetworkTypes::GuiSyncVersionMismatch);
		ba << int(SHS_UISYNC_VERSION);
		r->tcp->send(ba);
		r->close();
		return;
	}
	PIString key;
	data >> key;
	bool ok = (key == server_key_);
	ba = SHNetworkTypes::makeHeader(SHNetworkTypes::KeyProbeResult);
	ba << uchar(ok ? 1 : 0);
	piCout << "received key" << key;
	r->tcp->send(ba);
	if (ok) {

		if (r->upload_rcc) {
			ba = SHNetworkTypes::makeHeader(SHNetworkTypes::GuiSyncRCC);
			ba.append(bqr_content);
			//qDebug() << "send rcc" << int(SHNetworkTypes::GuiSyncRCC) << bqr_content.size();
		} else {
			ba = SHNetworkTypes::makeHeader(SHNetworkTypes::GuiRestoreRCC);
		}
		r->queueSend(ba);

		if (r->upload_ui) {
			ba = SHNetworkTypes::makeHeader(SHNetworkTypes::GuiSyncUI);
			ba.append(ui_content);
		} else {
			ba = SHNetworkTypes::makeHeader(SHNetworkTypes::GuiRestoreUI);
		}
		//qDebug() << "send ui" << int(SHNetworkTypes::GuiSyncUI) << ba.size();
		r->queueSend(ba);

		ba = Q2PIByteArray(sync.buildFullDiffForRole(r->user_role));
		//qDebug() << "send init diff" << r->user_role << ba.size();
		if (!ba.isEmpty()) {
			PIByteArray hdr = SHNetworkTypes::makeHeader(SHNetworkTypes::GuiSyncDiff);
			ba.insert(0, hdr);
		}
		r->queueSend(ba);

		ba = SHNetworkTypes::makeHeader(SHNetworkTypes::ConnectingDone);
		r->queueSend(ba);

		//r->active = true;
	}
}


void ServerCore::sendToAll(const PIByteArray & data, SHServer::ClientRole role, bool no_check) {
	PIMutexLocker _ml(rmutex);
	PIVector<ServerClient * > rl = remotes.values();
	for (ServerClient * r: rl) {
		qDebug() << r << r->tcp << r->active << r->info.role;
		if (!r->tcp || r->info.role != role) continue;
		if (!no_check && !r->active) continue;
		r->queueSend(data);
	}
}


void ServerCore::log(const QString & text, SHServer::LogEntryType type) {
	LogEntry e;
	e.time = QDateTime::currentDateTime();
	e.text = text;
	e.type = type;
	lmutex.lock();
	log_queue.enqueue(e);
	admin_log << e;
	lmutex.unlock();
}


void ServerCore::message(const QString & text, int type) {
	lmutex.lock();
	PacketMessage m(type, Q2PIString(text), PIDateTime::current());
	message_queue.enqueue(m);
	piCout << "msg:" << m.time.toString() << "- (" << m.type << ")" << m.text;
	lmutex.unlock();
}


void ServerCore::scriptError(int line, QString file, QString message, QString stack) {
	if (lqs_err_line == line && lqs_err_msg == message) return;
	lqs_err_line = line;
	lqs_err_msg = message;
	log(QString("Script error, %1:%2: %3\n%4").arg(file).arg(line).arg(message, stack), SHServer::LogError);
	PIByteArray msg = SHNetworkTypes::makeHeader(SHNetworkTypes::AdminScriptError);
	PacketScriptError se;
	se.message = message;
	se.stack = stack;
	se.file = file;
	se.line = line;
	se.project = PI2QString(server_name_);
	msg << se;
	sendToAll(msg, SHServer::RoleAdmin, true);
}


void ServerCore::scriptDebug(QString text) {
	LogEntry e;
	e.time = QDateTime::currentDateTime();
	e.text = text;
	e.type = SHServer::LogScript;
	lmutex.lock();
	admin_log << e;
	lmutex.unlock();
}


void ServerCore::IOLog(QString text) {
	LogEntry e;
	e.time = QDateTime::currentDateTime();
	e.text = text;
	e.type = SHServer::LogIO;
	rmutex.lock();
	PIVector<ServerClient * > rl = remotes.values();
	for (ServerClient * r: rl) {
		if (!r) continue;
		if ((r->info.role != SHServer::RoleAdmin) || !r->admin_log_io) continue;
		r->admin_io_log << e;
	}
	rmutex.unlock();
}
