#include "SH_client_channel.h"
#include "SH_base.h"

const double keepAliveInterval_s = 10.;


SHClientChannel::SHClientChannel(bool mc_send_only): SHMulticast(mc_send_only), eth_tcp(PIEthernet::TCP_Client) {
	cloud_client.setThreadedReadBufferSize(16*1024*1024);
	packer.assignDevice(&eth_tcp);
	packer.assignDevice(&cloud_client);
	CONNECTU(&eth_tcp, connected, this, tcpConnectedH);
	CONNECTU(&eth_tcp, disconnected, this, tcpDisconnectedH);
	CONNECTU(&cloud_client, connected, this, cloudConnectedH);
	CONNECTU(&cloud_client, disconnected, this, cloudDisconnectedH);
	CONNECTU(&packer, packetReceivedSH, this, packetReceivedSH);
	CONNECTU(&timer_req, tickEvent, this, timerRequest);
	setDebug(false);
	eth_tcp.setDebug(false);
	eth_tcp.setReopenEnabled(false);
	eth_tcp.setParameter(PIEthernet::KeepConnection, false);
	eth_tcp.setReadTimeout(60000.);
	eth_tcp.setWriteTimeout(60000.);
	addressesChanged();
	timer_req.start(500);
	waiting = true;
	ignore_disconnect = false;
}


SHClientChannel::~SHClientChannel() {
	timer_req.stop();
	stop();
}


void SHClientChannel::setID(uint id) {
	packer.setID(id);
}


void SHClientChannel::start() {
	startRead();
	timerRequest(0, 1);
}


void SHClientChannel::stop() {
	ignore_disconnect = true;
	stopRead();
	dataStop();
	ignore_disconnect = false;
	active_channel = SHNetworkTypes::None;
	current_address = ServerAddress();
}


void SHClientChannel::tcpStop() {
	eth_tcp.stopAndWait();
	eth_tcp.close();
}


void SHClientChannel::dataStop() {
	tcpStop();
	cloudStop();
}


void SHClientChannel::tcpConnect(const PINetworkAddress & a) {
	//piCout << "SHClientChannel::tcpConnect" << a;
	waiting = false;
	packer.clear();
	eth_tcp.connect(a);
	eth_tcp.startThreadedRead();
	resetKeepAliveElapsed();
}


void SHClientChannel::connectAs(const SHClientChannel & other) {
	current_address = other.address();
	if (other.active_channel == SHNetworkTypes::TCP)
		tcpConnect(other.eth_tcp.readIP(), SHNetworkTypes::clientPort);
	if (other.active_channel == SHNetworkTypes::Cloud) {
		waiting = false;
		cloudSetServerAddress(other.cloud_client.path());
		cloudSetServerName(other.cloud_client.serverName());
		cloudStart();
	}
}


void SHClientChannel::connectToServer(const QString & addr) {
	connectToServer(parseServerAddress(addr));
}


void SHClientChannel::connectToServer(const PacketServerInfo & info) {
	connectToServer(parseServerAddress(PI2QString(info.connect_address)));
}


void SHClientChannel::connectToServer(const ServerAddress & addr) {
	qDebug() << "SHClientChannel::connectToServer" << addr.server << ':' << addr.port << '@' << addr.cloud_name << addr.channel;
	current_address = addr;
	if (addr.channel == SHNetworkTypes::TCP)
		tcpConnect(PINetworkAddress::resolve(addr.ipAddress()));
	if (addr.channel == SHNetworkTypes::Cloud) {
		waiting = false;
		cloudSetServerAddress(addr.ipAddress());
		cloudSetServerName(Q2PIString(addr.cloud_name));
		cloudStart();
	}
}


void SHClientChannel::tcpSend(const PIByteArray & data) {
	mutex_tcp_send.lock();
	packer.send(data);
	mutex_tcp_send.unlock();
	resetKeepAliveElapsed();
}


void SHClientChannel::cloudSend(const PIByteArray & data) {
	mutex_tcp_send.lock();
	packer.send(data);
	mutex_tcp_send.unlock();
	resetKeepAliveElapsed();
}


void SHClientChannel::broadcastSend(const PIByteArray & data) {
	SHMulticast::send(data, packer.ID());
}


void SHClientChannel::dataSend(const PIByteArray & data) {
	switch (active_channel) {
	case SHNetworkTypes::TCP  : tcpSend(data); break;
	case SHNetworkTypes::Cloud: cloudSend(data); break;
	default: break;
	}
}


void SHClientChannel::setReopenEnabled(bool on) {
	eth_tcp.setReopenEnabled(on);
}


void SHClientChannel::cloudSetServerAddress(const PIString & a) {
	cloud_client.setPath(a);
}


void SHClientChannel::cloudSetServerName(const PIString & n) {
	cloud_client.setServerName(n);
}


void SHClientChannel::cloudStart() {
	packer.clear();
	//cloud_client.open();
	cloud_client.startThreadedRead();
}


void SHClientChannel::cloudStop() {
	cloud_client.stopAndWait();
	cloud_client.close();
}


PIString SHClientChannel::cloudServerAddress() const {
	return cloud_client.path();
}


PIString SHClientChannel::cloudServerName() const {
	return cloud_client.serverName();
}


void SHClientChannel::tcpDisconnectedH() {
	if (!ignore_disconnect)
		disconnected(active_channel);
	active_channel = SHNetworkTypes::None;
	current_address = ServerAddress();
	waiting = true;
}


void SHClientChannel::cloudDisconnectedH() {
	if (!ignore_disconnect)
		disconnected(active_channel);
	active_channel = SHNetworkTypes::None;
	current_address = ServerAddress();
	waiting = true;
}


void SHClientChannel::tcpConnectedH() {
	active_channel = SHNetworkTypes::TCP;
	connected(active_channel);
}


void SHClientChannel::cloudConnectedH() {
	active_channel = SHNetworkTypes::Cloud;
	connected(active_channel);
}


void SHClientChannel::addressesChanged() {
	addresses_.clear();
	PIEthernet::InterfaceList ilist = PIEthernet::interfaces();
	piForeachC (PIEthernet::Interface & i, ilist)
		if (!i.isLoopback()) {
			PIString a = PIEthernet::applyMask(i.address, i.netmask);
			if (a == "127.0.0.0") continue;
			addresses_ << a;
	}
	addressesChangedEvent();
}


void SHClientChannel::receivedSH(PIByteArray data, uint id) {
	if (id != packer.ID()) return;
	broadcastReceived(data);
	broadcastReceiveEvent(data);
}


double SHClientChannel::keepAliveElapsed() {
	PIMutexLocker _ml(mutex_ka);
	return tm_ka.elapsed_s();
}


void SHClientChannel::resetKeepAliveElapsed() {
	PIMutexLocker _ml(mutex_ka);
	tm_ka.reset();
}


void SHClientChannel::packetReceivedSH(PIByteArray data) {
	if (data.size_s() == 4) {
		PacketHeader hdr;
		memcpy(&hdr, data.data(), sizeof(hdr));
		if (hdr.type == SHNetworkTypes::KeepAlive)
			return;
	}
	//piCout << "SHClientChannel::packetReceivedSH" << data.toHex() << active_channel;
	dataReceived(data);
}


void SHClientChannel::timerRequest(void * , int ) {
	PIByteArray ba;
	// broadcast request
	if (!isConnecting() && !isConnected()) {
		ba = SHNetworkTypes::makeHeader(SHNetworkTypes::Request);
		ba << addresses_;
		broadcastSend(ba);
		return;
	}
	// keep-alive
	if (isConnected() && (keepAliveElapsed() >= keepAliveInterval_s)) {
		ba = SHNetworkTypes::makeHeader(SHNetworkTypes::KeepAlive);
		//mutex_send.lock();
		tcpSend(ba);
		return;
	}
}
