/*
	PIP - Platform Independent Primitives
	PICloud Server
	Ivan Pelipenko peri4ko@yandex.ru, Andrey Bychkov work.a.b@yandex.ru

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "picloudserver.h"


PICloudServer::PICloudServer(const PIString & path, PIIODevice::DeviceMode mode) : PIIODevice(path, mode), PICloudBase() {
	PIString server_name = "PCS_" + PIString::fromNumber(randomi()%1000);
	tcp.setRole(PICloud::TCP::Server);
	tcp.setServerName(server_name);
	setName("cloud_server__" + server_name);
	CONNECTU(&streampacker, packetReceiveEvent, this, _readed);
	CONNECTL(&eth, connected, [this](){tcp.sendStart();});
	CONNECTL(&eth, disconnected, [this](bool){
		piCoutObj << "disconnected";
		opened_ = false;
		piMSleep(100);
	});
}


PICloudServer::~PICloudServer() {
	stop();
	close();
}


void PICloudServer::setServerName(const PIString & server_name) {
	setName("cloud_server__" + server_name);
	tcp.setServerName(server_name);
}


PIVector<PICloudServer::Client *> PICloudServer::clients() const {
	PIMutexLocker _ml(clients_mutex);
	return clients_;
}


bool PICloudServer::openDevice() {
	piCout << "PICloudServer open device" << path();
	bool op = eth.connect(PIEthernet::Address::resolve(path()), false);
	if (op) {
		eth.startThreadedRead();
		return true;
	}
	eth.close();
	return false;
}


bool PICloudServer::closeDevice() {
	eth.stop();
	clients_mutex.lock();
	for (auto c : clients_) {
		c->close();
		c->stop();
	}
	clients_mutex.unlock();
	eth.close();
	return true;
}


int PICloudServer::readDevice(void * read_to, int max_size) {
	//piCoutObj << "readDevice";
	piMSleep(eth.readTimeout());
	return -1;
}


int PICloudServer::writeDevice(const void * data, int max_size) {
	//piCoutObj << "writeDevice";
	return -1;
}


void PICloudServer::clientDisconnect(uint client_id) {
	tcp.sendDisconnected(client_id);
}


int PICloudServer::sendData(const PIByteArray & data, uint client_id) {
	return tcp.sendData(data, client_id);
}


PICloudServer::Client::Client(PICloudServer * srv, uint id) : server(srv), client_id(id) {
	is_connected = true;
}


PICloudServer::Client::~Client() {
	if (is_connected) {
		is_connected = false;
		cond_buff.notifyOne();
	}
	close();
	stop();
}


bool PICloudServer::Client::openDevice() {
	return is_connected;
}


bool PICloudServer::Client::closeDevice() {
	if (is_connected) {
		server->clientDisconnect(client_id);
		is_connected = false;
		cond_buff.notifyOne();
	}
	return true;
}


int PICloudServer::Client::readDevice(void * read_to, int max_size) {
	//piCoutObj << "readDevice";
	if (!is_connected) return -1;
	mutex_buff.lock();
	cond_buff.wait(mutex_buff, [this](){return !buff.isEmpty();});
	int sz = piMini(max_size, buff.size());
	memcpy(read_to, buff.data(), sz);
	buff.remove(0, sz);
	mutex_buff.unlock();
	return sz;
}


int PICloudServer::Client::writeDevice(const void * data, int size) {
	return server->sendData(PIByteArray(data, size), client_id);
}


void PICloudServer::Client::pushBuffer(const PIByteArray & ba) {
	if (!is_connected) return;
	mutex_buff.lock();
	buff.append(ba);
	cond_buff.notifyOne();
	mutex_buff.unlock();
	while (buff.size_s() > threadedReadBufferSize()) piMSleep(100);
}


void PICloudServer::_readed(PIByteArray & ba) {
	PIPair<PICloud::TCP::Type, PICloud::TCP::Role> hdr = tcp.parseHeader(ba);
	if (hdr.second == tcp.role()) {
		switch (hdr.first) {
		case PICloud::TCP::Connect: {
			uint id = tcp.parseConnect(ba);
			clients_mutex.lock();
			Client * oc = index_clients.value(id, nullptr);
			clients_mutex.unlock();
			if (oc) {
				tcp.sendDisconnected(id);
			} else {
				piCoutObj << "new Client" << id;
				Client * c = new Client(this, id);
				CONNECTU(c, deleted, this, clientDeleted);
				clients_mutex.lock();
				clients_ << c;
				index_clients.insert(id, c);
				clients_mutex.unlock();
				newConnection(c);
			}
		} break;
		case PICloud::TCP::Disconnect: {
			uint id = tcp.parseDisconnect(ba);
			clients_mutex.lock();
			Client * oc = index_clients.value(id, nullptr);
			clients_mutex.unlock();
			if (oc) {
				oc->is_connected = false;
				oc->close();
			}
		} break;
		case PICloud::TCP::Data: {
			PIPair<uint, PIByteArray> d = tcp.parseDataServer(ba);
			clients_mutex.lock();
			Client * oc = index_clients.value(d.first, nullptr);
			clients_mutex.unlock();
			if (oc && !d.second.isEmpty()) oc->pushBuffer(d.second);
		} break;
		default:
		break;
		}
	}
}


void PICloudServer::clientDeleted(PIObject * o) {
	PICloudServer::Client * c = (PICloudServer::Client*)o;
	clients_mutex.lock();
	clients_.removeOne(c);
	auto it = index_clients.makeIterator();
	while (it.hasNext()) {
		it.next();
		if (it.value() == c) {
			index_clients.remove(it.key());
			break;
		}
	}
	clients_mutex.unlock();
}
