/*
 * rdr_remote_observer.cpp
 *
 */

#include "rdr_remote_observer.h"


#include "mrk_plus.h"
#include "mrk_net.h"

#include "rtlog.h"

#include <string.h>

#define MARKER_ROBS	0x524F4253
#define MARKER_SYNC 0x53594E43

//#define BITE_OBS

struct bite_buffer_t
{
	unsigned int bite_marker;
	unsigned int unit;
	unsigned int counter;
	unsigned int tmp_size;
	unsigned int size;
	char data[1024];
};

struct robs_cx_t
{
	mrk_net_ip_raw_address_t tgt_ip;
	mrk_net_uint16_t tgt_port;
	volatile int connected;

	unsigned int sent;
	unsigned int received;
	unsigned int tx_errs;
	unsigned int rx_errs;
	unsigned int spt_errs;
	unsigned int spt_sent;

	unsigned int max_msg;

	unsigned int sync_counter;

	mrk_net_ip_raw_address_t bite_ip;
	mrk_net_uint16_t bite_port;

	volatile int bite_connected;
	unsigned int bite_unit;
	unsigned int bite_size;
	unsigned int bite_pending;
	volatile int bite_ping_pong;
	volatile bite_buffer_t bite_buffer[2];
};

#ifdef BITE_OBS
static mrk_mutex_t bite_obs_mutex;
#endif

static robs_cx_t autoMonitorable_robs;
#define cx autoMonitorable_robs

struct msg_synch_t
{
	unsigned int marker;
	unsigned int type;
	unsigned int version;
	unsigned int counter;
	unsigned int timetag;
	unsigned int sync_word;
};

class NotifierThread: public mrk_thread_functor_with_stack<4*1024>
{
public:
	mrk_net_udp_socket_t s;

	void mrk_exec() override
	{
		mrk_net_udp_socket(&s, "ROBS");

#define SERVER_PORT	55000
#define REMOTE_PORT	55001

#ifdef __MINGW32__
#define SERVER_IP	0x0100007F
#define REMOTE_IP	0x0100007F

		mrk_net_udp_bind(&s, SERVER_IP, SERVER_PORT);
		cx.tgt_ip=REMOTE_IP;

		cx.bite_ip=0;
		cx.bite_port=0;

#else
#define SERVER_IP	0x8401A8C0
#define REMOTE_IP   0xFF01A8C0

#define SERVER_IF	1

		mrk_net_udp_bind_if(&s, SERVER_IF, SERVER_PORT);
		mrk_net_udp_set_capacity(&s, 1);

		cx.tgt_ip=mrk_net_myip_address_broadcast(SERVER_IF);

		//cx.tgt_ip=REMOTE_IP;
#endif


		cx.tgt_port=REMOTE_PORT;

		mrk_net_ip_raw_address_t from_ip;
		mrk_net_ip_port_t from_port;
		unsigned int rcv_len;

		static char dummy_buffer[2048];

		mrk_net_udp_set_capacity(&s, 2);

		for(;;)
		{
			mrk_net_result_t res=mrk_net_udp_receive(&s, dummy_buffer, sizeof dummy_buffer, &from_ip, &from_port, &rcv_len, mrk_wait_forever);
			if (res==mrk_net_ok)
			{
#ifdef BITE_OBS
				if (strcmp(dummy_buffer, "BITE-OBS")==0)
				{
					cx.bite_ip=from_ip;
					cx.bite_port=from_port;
					cx.bite_connected=1000;
				}
				else
#endif
				{
					cx.tgt_ip=from_ip;
					cx.tgt_port=from_port;

					cx.connected=2;

					rtlog_printf("ROBS", rtlog_success|rtlog_breaking_news, "TGT: Connected 0x%X:%u", (unsigned int)cx.tgt_ip, cx.tgt_port);
					++cx.received;
				}
			}
			else
			{
				mrk_thread_sleep(10);
				++cx.rx_errs;
			}


		}
	}

	void set_unit(unsigned int unit)
	{
		cx.bite_unit=unit;
	}

	void biteFlush()
	{
#ifdef BITE_OBS
		//Try to use a lock-free buffer swap. Note it is not perfect!!!!!
		mrk_mutex_acquire(&bite_obs_mutex, mrk_wait_forever);
		cx.bite_buffer[cx.bite_ping_pong^1].size=0;
		cx.bite_ping_pong^=1;
		mrk_mutex_release(&bite_obs_mutex);

		if (cx.bite_connected>0)
		{
			int ping_pong=cx.bite_ping_pong^1;

			volatile bite_buffer_t* b=&cx.bite_buffer[ping_pong];
			unsigned int size=b->size;
			++b->counter;
			auto r=mrk_net_udp_send(&s, (void*)b, size, cx.bite_ip, cx.bite_port, 0);
			if (r!=mrk_net_ok)
				cx.bite_connected=0;
			else
			{
				rtlog_printf("ROBS", rtlog_info, "[%d:%d]BITE info: %ubytes", b->counter, cx.bite_connected, size);
				--cx.bite_connected;
			}
		}
#endif
	}

	void biteSend(unsigned int id, const void* data, unsigned int size)
	{
#ifndef BITE_OBS
		(void)id;
		(void)data;
		(void)size;
#else
		if (cx.bite_connected<=0)
			return;

		auto r=mrk_mutex_acquire(&bite_obs_mutex, 0);
		if (r!=mrk_ok)
			return;
		if (size & 1)
			++size;

		volatile bite_buffer_t* b=&cx.bite_buffer[cx.bite_ping_pong];
		unsigned int current_size=b->size;
		unsigned int new_size=(current_size + size)+sizeof(uint32_t);
		if (new_size<sizeof b->data)
		{
			unsigned int marker=id|(size);
			*reinterpret_cast<volatile unsigned int*>(b->data[current_size])=marker;
			current_size+=sizeof(unsigned int);
			memcpy((void*)&b->data[current_size], data, size);
			b->size=new_size;
		}
		else
		{
		}
		mrk_mutex_release(&bite_obs_mutex);
#endif
	}

	void send(const void* data, unsigned int size, unsigned int size_byte_spt, unsigned int n_spt)
	{
		static bool initialized;
		if (!initialized)
		{
			if (cx.connected>0)
			{
				create("ROBS", 0, mrk_std_priority_below_normal, 0);
				initialized=true;
				cx.max_msg=1;
			}
		}
		if (cx.connected>1)
		{
			if (size>1400)
			{
				rtlog_printf("ROBS", rtlog_warning|rtlog_breaking_news, "Message too big: %u", size);
				size=1400;
			}
			mrk_net_result_t r=mrk_net_udp_send(&s, data, size, cx.tgt_ip, cx.tgt_port, 0);
			if (r!=mrk_net_ok)
				++cx.tx_errs;
			else
			{
				++cx.sent;
				const unsigned char* p=reinterpret_cast<const unsigned char*>(data);
				p=&p[size];
				if (n_spt>cx.max_msg)
					n_spt=cx.max_msg;
				for(unsigned int i=0; i<n_spt; ++i)
				{
					r=mrk_net_udp_send(&s, p, size_byte_spt, cx.tgt_ip, cx.tgt_port, 0);
					if (r!=mrk_net_ok)
					{
						++cx.spt_errs;
						break;
					}
					else
						++cx.spt_sent;
					p=&p[size_byte_spt];
				}
			}
		}
	}


};

static NotifierThread notifierThread;
#if 0
void rdr_remote_observer_send(unsigned int type, unsigned int timetag, const void* data, unsigned int size_bytes)
{
	struct msg_data_t
	{
		msg_synch_t h;
		unsigned int data[1024];
	};
	static msg_data_t msg={{MARKER_ROBS, 0, 0, 0, 0, 0}, {}};

	++msg.h.counter;
	msg.h.sync_word=size_bytes;
	msg.h.timetag=timetag;
	msg.h.type=type;

	memcpy(msg.data, data, size_bytes);
	notifierThread.send(&msg, size_bytes+(sizeof msg.h));
}
#endif

void rdr_remote_observer_synchronize(unsigned int timetag, unsigned int synch_word)
{
	static msg_synch_t msg_synch={MARKER_ROBS, MARKER_SYNC, 0, 0, 0, 0};

	msg_synch.timetag=timetag;
	msg_synch.sync_word=synch_word;

	msg_synch.counter=cx.sync_counter;

	++cx.sync_counter;
	notifierThread.send(&msg_synch, sizeof msg_synch, 0, 0);
}

void rdr_remote_observer_send(const void* data, unsigned int size_bytes, unsigned int size_byte_spt, unsigned int n_spt)
{
	notifierThread.send(data, size_bytes, size_byte_spt, n_spt);
}

bool rdr_remote_observer_connected()
{
	return cx.connected;
}

void rdr_remote_observer_bite_add(unsigned int id, const void* data, unsigned int size_bytes)
{
#ifdef BITE_OBS
	(void)id;
	(void)data;
	(void)size_bytes;
#else
	notifierThread.biteSend(id, data, size_bytes);
#endif
}

void rdr_remote_observer_bite_flush(void)
{
#ifdef BITE_OBS
	notifierThread.biteFlush();
#endif
}

void rdr_remote_observer_set_unit_id(unsigned int id)
{
	cx.bite_unit=id;
}
