/*
 * gc_remote_udp.cpp
 *
 *  Created on: 17/ott/2017
 *      Author: chessaa
 */

#ifndef _TMS320C6X
#ifdef __GNUC__
/* ARM compiler give an " warning: format '%u' expects argument of type 'unsigned int', but argument 5 has type 'uint32_t {aka long unsigned int"*/
#pragma GCC diagnostic ignored "-Wformat="
//#pragma GCC optimize("-Og")
#endif
#endif

#include "gc_remote_display.h"
//#include "GcEnvAdaptor.h"
#include "gc_geometry.h"

#include "mrk_net.h"
#include "mrk.h"

#include "rtlog.h"

#include "DataRecorder.h"

#include "StoredConfig.h"

#include <string.h>

#define GC_IP_LOCAL_PORT	51000
#define GC_IP_REMOTE_PORT	51001

#define LID	"GCREMOTE"

static mrk_event_t gc_remote_event;
static mrk_thread_t gc_remote_thread;
static mrk_mutex_t gc_remote_mutex;
static unsigned long long gc_remote_stack[1024];

typedef gc_remote_display_reply_t gc_reply_t;

static uint8_t* volatile gc_remote_memory;

static uint32_t gc_remote_palette[256];

static bool gc_remote_initialized;
static volatile gc_remote_display_buffer_t gc_remote_info;

//static volatile bool gc_remote_log=true;

#ifdef __arm_
#define GC_REMOTE_NICE_DEFAULT	false
#else
#define GC_REMOTE_NICE_DEFAULT	false
#endif

//static volatile bool gc_remote_nice=GC_REMOTE_NICE_DEFAULT;

struct gc_remote_cx_t
{
	volatile bool disable;
	volatile bool log; //=true;
	volatile bool nice; //=GC_REMOTE_NICE_DEFAULT;
	volatile unsigned int frame_errors;
	volatile bool push_mode;

	volatile int frame_decimator;
};

static gc_remote_cx_t autoMonitorable_gcrd;

#define gc_remote_log autoMonitorable_gcrd.log
#define gc_remote_nice autoMonitorable_gcrd.nice
#define gc_remote_disable autoMonitorable_gcrd.disable

static void (*gc_memory_release_handler)(const void*)=0;

//static volatile unsigned int frame_error_counter;
#if 0
static void frame_error() __attribute__((noinline));

static void frame_error()
{
	++autoMonitorable_gcrd.frame_errors; //++frame_error_counter;
}
#endif

//#define DX	640
//#define DY	512

#define DX	(91*8)
#define DY	(91*8)

#define GC_USE_SEND2

static void gc_remote_updater()
{
	int eth_if=-1;

	static gc_remote_display_buffer_t pmsg;

	gc_remote_display_header_t& header=pmsg.header;

	static mrk_net_udp_socket_t gcsocket;

	mrk_net_result_t net_res=mrk_net_udp_socket(&gcsocket, "GCREMOTE");
	if (net_res!=mrk_net_ok)
	{
		rtlog_printf(LID, rtlog_error|rtlog_breaking_news, "Socket create error (%d: %s)", mrk_net_error_code(), mrk_net_error_string());
		return;
	}

	mrk_net_udp_set_capacity(&gcsocket, 4);

	bool eth_defined=false; //StoredConfig::instance().get_env("GCIF", eth_if);
	if ((!eth_defined) || ((eth_if<0) || (eth_if>1)))
		eth_if=-1;

	if (eth_if<0)
	{
		net_res=mrk_net_udp_bind(&gcsocket, 0, GC_IP_LOCAL_PORT);
		eth_if=0;
	}
	else
	{
		net_res=mrk_net_udp_bind_if(&gcsocket, eth_if, GC_IP_LOCAL_PORT);
	}

	int connected=0;
	//int con_msg_filter=0;

	mrk_net_ip_raw_address_t receiver_address=mrk_net_myip_address_broadcast(eth_if);
	mrk_net_ip_port_t receiver_port=GC_IP_REMOTE_PORT;

	mrk_net_ip_raw_address_t from_address=0;;
	mrk_net_ip_port_t from_port=0;

	static gc_reply_t reply;

	int decimator_counter=0;

	unsigned int frag_num=0;
	unsigned int rcv_windows=0;
	unsigned int from_len=0;

	rtlog_printf("LID", rtlog_report|rtlog_breaking_news, "GC Remote over I/F %d", eth_if);

	for(;;)
	{

		frag_num=0;
		//rcv_windows=0;
		from_len=0;

		if (!autoMonitorable_gcrd.push_mode)
		{
			mrk_net_result_t net_res=mrk_net_udp_receive(&gcsocket, &reply, sizeof reply, &from_address, &from_port, &from_len, 100);
			if ((net_res!=mrk_net_ok) || (from_len!=sizeof(reply)) || reply.spare!=0x12)
			{
				if (connected>1)
					++connected;
				else if (connected==1)
				{
					rtlog_printf(LID, rtlog_success|rtlog_breaking_news, "GC Remote disconnected");
					connected=0;
				}
				//connected=0;
				continue;
			}
			else
			{
				receiver_address=from_address;
				receiver_port=from_port;
				if (!connected)
					rtlog_printf(LID, rtlog_success|rtlog_breaking_news, "GC Remote connected! (%u)", reply.max_capacity);
				connected=4;
				rcv_windows=reply.max_capacity;
			}
		}

		mrk_event_wait(&gc_remote_event, mrk_wait_forever);

		if (autoMonitorable_gcrd.frame_decimator)
		{
			++decimator_counter;
			if (decimator_counter<autoMonitorable_gcrd.frame_decimator)
			{
				if (gc_memory_release_handler)
					gc_memory_release_handler(gc_remote_memory);

				continue;
			}
			decimator_counter=0;
		}

		mrk_mutex_acquire(&gc_remote_mutex, mrk_wait_forever);

		bool skip=gc_remote_disable;

		if (receiver_address==0xFF000000) //invalid address?
		{
			skip=true;
			receiver_address=mrk_net_myip_address_broadcast(0);
		}

		if (skip)
		{
			if (gc_memory_release_handler)
				gc_memory_release_handler(gc_remote_memory);

			mrk_mutex_release(&gc_remote_mutex);

			continue;
		}

		const uint8_t* frame_buffer=gc_remote_memory;

		unsigned int rows=gc_remote_info.header.dy;
		unsigned int pitch=gc_remote_info.header.pitch;

		if (rows>DY)
			rows=DY;

		++header.sent_frames;
		header.generated_frames=gc_remote_info.header.generated_frames;
		header.type=0;
		header.dx=gc_remote_info.header.dx;
		if (header.dx>DX)
			header.dx=DX;
		header.dy=rows;
		header.pitch=pitch;
		if (header.pitch>DX) //gc_remote_display_header_t::max_dx)
			header.pitch=DX; //gc_remote_display_header_t::max_dx;

		if (gc_remote_log)
			rtlog_printf(LID, rtlog_trace, "GC Display begin %u-%u = %u ", header.sent_frames, header.generated_frames, header.generated_frames-header.generated_frames);

		unsigned int row_per_msg=gc_remote_info.header.pitch/gc_remote_display_header_t::max_dx;
		if (row_per_msg<1)
			row_per_msg=1;

		row_per_msg=2;

		header.gev.gev_status=0;
		header.gev.gev_flags=0;
		header.gev.fmt=gc_remote_display_header_t::GEV_LEADER;
		header.gev.spare=0;
		header.gev.spare2=0;
		header.gev.blockid_h=0;
		header.gev.blockid_l=0;
		header.gev.packet_id=0;

		{//Send palette when required
			static gc_remote_display_header_t::small_word_t palette_cookie;

			pmsg.header.type=gc_remote_display_header_t::t_palette;
			pmsg.header.ext_code=++palette_cookie;

#ifdef GC_USE_SEND2
			net_res=mrk_net_udp_send2(&gcsocket, &pmsg, sizeof pmsg.header, gc_remote_palette, sizeof gc_remote_palette, receiver_address, receiver_port, 0);
#else
			uint32_t* dp=(uint32_t*)pmsg.data;
			for(int i=0; i<256; ++i)
			{
				dp[i]=gc_remote_palette[i];
			}
			net_res=mrk_net_udp_send(&gcsocket, &pmsg, sizeof pmsg, receiver_address, receiver_port, 0);
#endif
			if (net_res!=mrk_net_ok)
			{
				rtlog_printf("GC", rtlog_error|rtlog_breaking_news, "send error: %s", mrk_net_error_string());
				continue;
			}
		}

		pmsg.header.type=gc_remote_display_header_t::t_leader;

		pmsg.header.gev.blockid_l=mrk_htonl(pmsg.header.sent_frames);
		pmsg.header.number_of_rows=row_per_msg;
		pmsg.header.gev.gev_flags=0;

		unsigned int contiguous_packets=0; //Contiguous

		for(unsigned int i=0; i<rows; i+=row_per_msg, ++frag_num)
		{
			bool ack_required=false;

			pmsg.header.gev.packet_id=mrk_htonl(frag_num);

			const uint8_t* frag_base=&frame_buffer[i*pitch];

#if 0
			gc_remote_display_header_t& msg=(reinterpret_cast<gc_remote_display_header_t*>(frag_base))[-1];

			msg.gev.gev_status=0;
			msg.gev.gev_flags=0;
			msg.gev.fmt=gc_remote_display_header_t::GEV_LEADER;
			msg.gev.spare=0;
			msg.gev.spare2=0;
			msg.gev.blockid_h=0;
			msg.gev.blockid_l=0;
			msg.gev.packet_id=0;

			msg.row=i;
			msg.dx=header.dx;
			msg.dy=header.dy;
			msg.pitch=header.pitch;

		    msg.generated_frames=header.generated_frames;
		    msg.sent_frames=header.sent_frames;
#endif

			pmsg.header.row=i;

			pmsg.header.frag_number=frag_num;
			//memcpy(sendbuffer.data, (void*)&(gc_remote_memory[i*pitch]), pitch*2);

			//pmsg.header.gev.packet_id=mrk_htonl(frag_num);
			//pmsg.header.gev.blockid_l=mrk_htonl(pmsg.header.sent_frames);

			//pmsg.header.gev.gev_flags=0;

			if (i==0)
			{
				pmsg.header.type=0;
				pmsg.header.gev.fmt=gc_remote_display_header_t::GEV_LEADER;
			}
			else if (i==(rows-row_per_msg))
			{
				pmsg.header.type=4;
				pmsg.header.gev.fmt=gc_remote_display_header_t::GEV_TRAILER;
			}
			else if (rcv_windows && ((frag_num % rcv_windows)==0))
			{
				ack_required=true;
				pmsg.header.type=3;
				pmsg.header.gev.fmt=gc_remote_display_header_t::GEV_PAYLOAD;
				//pmsg.header.gev.gev_flags=0x0100;
			}
			else
			{
				pmsg.header.type=1;
				pmsg.header.gev.fmt=gc_remote_display_header_t::GEV_PAYLOAD;
			}

			//pmsg.header.number_of_rows=row_per_msg;

#ifdef GC_USE_SEND2
			++contiguous_packets;
			unsigned int completition_timeout=(contiguous_packets & (8-1)) ? 2 : 0;
			//net_res=mrk_net_udp_send2a32(&gcsocket, &pmsg, sizeof pmsg.header, frag_base, header.pitch*row_per_msg, receiver_address, receiver_port, 0);
			//net_res=mrk_net_udp_send2a32x2(&gcsocket, &pmsg, sizeof pmsg.header, frag_base, header.pitch*row_per_msg, receiver_address, receiver_port, 0);
			net_res=mrk_net_udp_send2x2_wait(&gcsocket, &pmsg, sizeof pmsg.header, frag_base, header.pitch*row_per_msg, receiver_address, receiver_port, 0, completition_timeout);
#else
			uint8_t* pixels=pmsg.data;
			for(unsigned int r=0; r<row_per_msg; ++r)
			{
				memcpy(pixels, frag_base, header.pitch);
				pixels+=header.pitch;
				frag_base+=pitch;
			}

			net_res=mrk_net_udp_send(&gcsocket, &pmsg, sizeof(gc_remote_display_buffer_t), receiver_address, receiver_port, 0);
#endif
			if (net_res!=mrk_net_ok)
			{
				rtlog_printf("GC", rtlog_error|rtlog_breaking_news, "send error: %s", mrk_net_error_string());
				break;
			}

			if ((i==0 || ack_required) && (!autoMonitorable_gcrd.push_mode))
			{
				bool frame_match=false;
				int rp=0;
				for(rp=0; (rp<2) && (!frame_match); ++rp)
				{
#if 1
					unsigned int from_len=0;
					net_res=mrk_net_udp_receive(&gcsocket, &reply, sizeof reply, &from_address, &from_port, &from_len, 2);
					if (net_res!=mrk_net_ok)
						break;
					if ((reply.gev.blockid_l==pmsg.header.gev.blockid_l) && (reply.gev.packet_id==pmsg.header.gev.packet_id))
						frame_match=true;
					if (i==0)
						frame_match=true;
#else
					const mrk_net_opaque_packet_t* dummy;
					net_res=mrk_net_udp_raw_receive(&gcsocket, &dummy, &from_address, &from_port, 2);
					if (net_res!=mrk_net_ok)
						break;
					mrk_net_release_packet(dummy);
					frame_match=true;
#endif
				}

				if ((!frame_match/*net_res!=mrk_net_ok*/) && (!ack_required))
				{
#if 0
					if (connected<=0)
					{
						receiver_address=mrk_net_myip_address_broadcast(0);
						if (con_msg_filter>100)
						{
							rtlog_printf(LID, rtlog_info, "GC Remote not connected");
							con_msg_filter=0;
						}
						else
							++con_msg_filter;
					}
					else
						--connected;
					break;
#endif
				}
				else
			    {
#if 0
					if (!connected)
					{
						rtlog_printf(LID, rtlog_success|rtlog_breaking_news, "GC Remote connected! (%u)", reply.max_capacity);
					}
					if (connected<10)
						++connected;
					if (net_res==mrk_net_ok)
					{
						rcv_windows=reply.max_capacity;
						receiver_address=from_address;
						//receiver_port=from_port;
					}
#endif
			    }
			}
			else
			{
				if (gc_remote_nice)		//TODO: warning, when running on CPU1, this thread push the CPU1 loading at 100%. Why?
					mrk_thread_sleep(1);
			}
		}

		//GcEnvAdaptor::ggRemoteRelease(gc_remote_memory);
		if (gc_memory_release_handler)
			gc_memory_release_handler(gc_remote_memory);

		mrk_mutex_release(&gc_remote_mutex);
		if (connected<=0)
			mrk_thread_sleep_ms(1000);

		if (gc_remote_log)
			rtlog_printf("GCENV", rtlog_trace, "GC Display end %u/%u", header.sent_frames, header.generated_frames);

	}
}


static void gc_remote_initialize()
{
	mrk_event_create(&gc_remote_event, "GCREMOTE");
    mrk_mutex_create(&gc_remote_mutex, "GCREMOTE");
	mrk_thread_create(&gc_remote_thread, "GCREMOTE", gc_remote_updater, 0, mrk_std_priority_low/*mrk_std_priority_below_normal*/, sizeof gc_remote_stack, gc_remote_stack, 0);
}

void gc_remote_set_memory_release_handler(void (*handler)(const void*))
{
	gc_memory_release_handler=handler;
}


int gc_remote_intruder(const uint8_t* src, unsigned int rows, unsigned int cols, unsigned int pitch, uint32_t* new_palette) __attribute__((weak));

struct gc_vcr_frame_t
{
	unsigned int fnum;
	unsigned int dx;
	unsigned int dy;
	unsigned int stride;

	unsigned int validity;
	unsigned int time_tag;
	unsigned int batch_tag;
	unsigned int mission_date;
	unsigned int mission_time;

	unsigned int video_marker;

	unsigned int spare[8];

	int palette_updated;
	unsigned int palette[256];
};

struct gc_vcr_frame_image_t
{
	gc_vcr_frame_t header;
	uint8_t image[gc_cfg::Y_MAX_PHYSICAL_DISPLAY_DIMENSION][gc_cfg::X_MAX_PHYSICAL_DISPLAY_DIMENSION];
};

static DataRecorder<gc_vcr_frame_image_t> rpVideo(1, "VIDEO", 0);

extern void softrec_sata_video(const void* p, unsigned int len) __attribute__((weak));

static void video_recorder(gc_vcr_frame_t* frame)
{
	if (softrec_sata_video)
	{
		if (rpVideo.enabled==1)
		{
			unsigned int size=sizeof(*frame)+(frame->stride*frame->dy);
			softrec_sata_video(frame, size);
		}
		else if (rpVideo.enabled==2)
		{
			rpVideo.save(*reinterpret_cast<gc_vcr_frame_image_t*>(frame));
		}
	}
}

#include "rtg_high_resolution_timer.h"

void gc_remote_update(const uint8_t* src, unsigned int rows, unsigned int cols, unsigned int pitch, uint32_t* new_palette)
{
	static unsigned int fnum;
	  if (gc_remote_intruder)
	  {
		  int reply=gc_remote_intruder(src, rows, cols, pitch, new_palette);

		  switch(reply)
		  {
		  default:
			  break;
		  case -1:
			  if (gc_memory_release_handler)
				gc_memory_release_handler(gc_remote_memory);
			  return;
		  }
	  }

	  if (!gc_remote_initialized)
	  {
		  gc_remote_initialized=true;
		  gc_remote_memory=0; //new uint8_t[(rows+2)*pitch];
		  rtlog_printf("GCENV", rtlog_info, "Activating remote display replicator %ux%u (%u)", rows, cols, pitch);
		  gc_remote_info.header.dx=cols;
		  gc_remote_info.header.dy=rows;
		  gc_remote_info.header.pitch=pitch;

		  gc_remote_initialize();

//		  rpVideo.setEnable(true);
//#ifdef __MINGW32__
//		  rpVideo.enabled=2;
//#endif
	  }

	  ++fnum;

	  //rtlog_printf("GCENV", rtlog_info, "Blit Start: 0x%p  [%u,%u,%u]", src, rows, cols, pitch);
	  mrk_sts_t r=mrk_mutex_acquire(&gc_remote_mutex, 0);
	  if (r==mrk_ok)
	  {
		  //memcpy(gc_remote_memory, src, rows*pitch);
		  gc_remote_memory=const_cast<uint8_t*>(src);

		  //WARNING!!!! Assuming video memory has a spare are reserved for external use!!!!!
		  gc_vcr_frame_t* video_frame=reinterpret_cast<gc_vcr_frame_t*>(gc_remote_memory);
		  video_frame=&(video_frame[-1]);
		  if (new_palette)
			  memset(video_frame, 0, (sizeof *video_frame)-sizeof video_frame->palette);
		  else
			  memset(video_frame, 0, (sizeof *video_frame));

		  video_frame->fnum=fnum;
		  video_frame->dx=cols;
		  video_frame->dy=rows;
		  video_frame->stride=pitch;
		  video_frame->validity=0;
		  video_frame->palette_updated=0;

		  unsigned long tt=rtg_hrt_to_microseconds(rtg_hrt_get());

		  video_frame->time_tag=tt;

		  if (new_palette)
		  {
			  video_frame->palette_updated=256;
			  for(int i=0; i<256; ++i)
			  {
				  gc_remote_palette[i]=new_palette[i];
				  video_frame->palette[i]=new_palette[i];
			  }
		  }
		  video_recorder(video_frame);

		  //GcEnvAdaptor::ggRemoteRelease(src);
		  ++gc_remote_info.header.generated_frames;
		  mrk_mutex_release(&gc_remote_mutex);
		  mrk_event_signal(&gc_remote_event);
	  }
}
