/*
 * rex_udp_manipulator.h
 *
 *  Created on: 24/ott/2018
 *      Author: chessaa
 */

#ifndef REX_UDP_MANIPULATOR_H_
#define REX_UDP_MANIPULATOR_H_


/*
 * rex_udp_manipulator.h
 *
 * This is an all-in-header implemented capability. This can be reused in other project (e.g Qt)  without having to compile an implementation file
 */


#ifndef RED_UDP_MANIPULATOR_H_
#define RED_UDP_MANIPULATOR_H_

#include "rex_udp_tunnelling.h"

#include <string.h>

class RexUdpHeaderManipulator
{
public:
	typedef unsigned char raw_payload_t;
	typedef unsigned int db_code_t;

	enum {
		mtu=1500-(20+8),  //my basic MTU, conting the ETH, IP and UDP header
		mtu_header_size=(2+sizeof(RexUdpMsgHeader)),  //header plus 2 bytes for Ethernet UDP align to 32bits
		mtu_payload=(mtu-mtu_header_size) & 0xFFFFFFF8,	//real payload MTU, counting round to 32 bits
		mtu_frag_size=mtu_payload+mtu_header_size,
		db_invalid=0}
	;

	static unsigned int headerPrepare(RexUdpMsgHeader& h, RexUdpMsgHeader::msg_class_t c, unsigned src_id, unsigned int dst_id, unsigned int mailbox, unsigned int payload_sizeof=0)
	{
		memset(&h, 0, sizeof h);
		h.marker_protocol=RexUdpMsgHeader::marker_rexnet_protocol;
		h.marker_value=RexUdpMsgHeader::marker_begin;
		h.msg_class=c;
		h.src=src_id;
		h.dst=dst_id;
		h.mailbox=mailbox;
		h.doorbell=db_invalid;
		return headerFragPrepare(h, payload_sizeof);
		//h.flow_control.raw=0;
	}

	static unsigned int calcFrags(unsigned int payload_sizeof)
	{
		if (payload_sizeof<=mtu_payload)
			return 1;
		unsigned int pfrags=(payload_sizeof+(mtu_payload-1))/mtu_payload;
		unsigned hsize=pfrags*mtu_header_size; //sizeof(RexUdpMsgHeader);
		unsigned int ts=hsize+payload_sizeof;
		unsigned int frags=(ts+(mtu_frag_size-1))/mtu_frag_size; //round up
		return frags;
	}
	static unsigned int headerFragPrepare(RexUdpMsgHeader& h, unsigned int payload_sizeof)
	{
		unsigned int frags=calcFrags(payload_sizeof);
		h.total_frags=frags;
		h.current_frags=0;
		h.marker_protocol=RexUdpMsgHeader::marker_rexnet_protocol;
		h.marker_value=RexUdpMsgHeader::marker_begin;
		h.total_message_size=payload_sizeof;
		return frags;
	}

	static unsigned int headerNextFragCounter(RexUdpMsgHeader& h) //return frags left
	{
		if (h.current_frags<(h.total_frags-1))
		{
			++h.current_frags;
			//payload_p+=mtu-sizeof h;
			return h.total_frags-h.current_frags;
		}
		return 0;
	}
};

class RexUdpMessageHelper: public RexUdpHeaderManipulator
{
public:

    RexUdpMsgHeader* hdr;
    unsigned int msg_len;
    unsigned int msg_remain;
    const uint8_t* msg_payload;
    unsigned int mailbox_base;
    unsigned int mailbox_increment;
    unsigned int frags;
    unsigned int frag_size;
    unsigned int db_code;
    unsigned int frags_already_out;
    unsigned int frags_window;
    const uint8_t* current_payload;

    long id;

    RexUdpMessageHelper(): //RexUdpMsgHeader* h=0, unsigned int message_sizeof=0, const void* message_payload=0):
    	hdr(0),
    	msg_len(0),
    	msg_remain(0),
    	msg_payload(0),
    	mailbox_base(0),
    	mailbox_increment(0),
    	frags(0),
    	frag_size(0),
    	db_code(0),
    	frags_already_out(0),
    	frags_window(4),
    	current_payload(0),
    	id(-1)
    {
    	//initializePayload(h, message_sizeof, message_payload);
    }

    void initializeMessage(
    		RexUdpMsgHeader* h,
    		uint8_t src=0, uint8_t dst=0, RexUdpMsgHeader::msg_class_t mc=RexUdpMsgHeader::t_msg,
    		unsigned int mailbox=0, unsigned int db=RexUdpHeaderManipulator::db_invalid,
    		unsigned int message_sizeof=0, const void* message_payload=0)
    {
    	hdr=h;
    	frags=headerPrepare(*h, mc, src, dst, mailbox, message_sizeof);
    	msg_payload=reinterpret_cast<const uint8_t*>(message_payload);
    	msg_len=message_sizeof;
    	msg_remain=message_sizeof;
    	mailbox_base=mailbox;
    	mailbox_increment=(mc==RexUdpMsgHeader::t_memory_write) ? mtu_payload : 0;
    	frag_size=0;
    	frags_already_out=0;
    	db_code=db;
    }

    int fragsLeft()
    {
    	return hdr->total_frags-(hdr->current_frags+1);
    }

    int preparNextFrag(bool no_copy=false)
    {
    	if (frags_already_out>=hdr->total_frags)
    		return 0;

    	hdr->mailbox=mailbox_base;

    	hdr->flow_control.raw=0;

    	unsigned int psize=msg_remain;
		hdr->doorbell=0;

    	if (psize>mtu_payload)
    	{
    		psize=mtu_payload;
    	}
    	else if (db_code)
    	{
    		hdr->doorbell=db_code;
    	}

		hdr->current_frags=frags_already_out;
		++frags_already_out;
    	if ((frags_already_out<hdr->total_frags) && (hdr->current_frags>0))
    	{
        	//if ((hdr->current_frags % frags_window)==0)
        	if ((hdr->current_frags & (frags_window-1))==0)
        		hdr->flow_control.code.ack_frag_required=1;
    	}

    	current_payload=msg_payload;
    	if (!no_copy)
    		memcpy(&(hdr[1]), msg_payload, psize);
    	hdr->payload_size_bytes=psize;
    	msg_payload+=psize;
    	frag_size=psize+sizeof(*hdr);
    	mailbox_base+=mailbox_increment;
    	msg_remain=msg_remain-psize;

		hdr->marker_protocol=RexUdpMsgHeader::marker_rexnet_protocol;

    	if (hdr->current_frags==0)
    		hdr->marker_value=RexUdpMsgHeader::marker_begin;
    	else if (hdr->total_frags>frags_already_out)
    		hdr->marker_value=RexUdpMsgHeader::marker_body;
    	else
    		hdr->marker_value=RexUdpMsgHeader::marker_end;

    	if (hdr->flow_control.code.ack_frag_required)
    		return -frag_size;
    	return frag_size;
    }

    void setTId(long tid)
    {
    	id=tid;
    	hdr->transaction_id=static_cast<uint8_t>(tid);
    }
};

#define REX_UPD_MAX_TRANSACTION	32

template<typename PKT_=void *, typename ADDRT_=unsigned int, typename PORTT_=unsigned short, int MAX_TRANSACTIONS_POWOF2_=REX_UPD_MAX_TRANSACTION> class RexNetUdpTxTransactionManager
{
public:
	enum { max_transactions=MAX_TRANSACTIONS_POWOF2_, max_transaction_mask=MAX_TRANSACTIONS_POWOF2_-1};

	typedef ADDRT_	ip_address_t;
	typedef PORTT_ ip_port_t;

	typedef unsigned int tid_t;
	tid_t tid;

	struct transaction_t
	{
		unsigned int valid;
		tid_t id;
		tid_t pid;

		ip_address_t ip;
		ip_port_t port;
		RexUdpMessageHelper msg;
                PKT_ pkt_;
	};

	transaction_t descriptors[MAX_TRANSACTIONS_POWOF2_];

	RexNetUdpTxTransactionManager()
	{
		tid=0;
		for(int i=0; i<max_transactions; ++i)
		{
			descriptors[i].id=0;
			descriptors[i].valid=0;
		}
	}

	static tid_t xtid(tid_t id)
	{
		return id & max_transaction_mask;
	}
	transaction_t* get(tid_t id)
	{
		return &descriptors[xtid(id)];
	}

	transaction_t* allocate()
	{
		transaction_t *t=get(tid);
		t->pid=t->id;
		t->id=tid;
		//t->msg.setTId(tid);
		++tid;
		tid=tid & 255;
		return t;
	}
	void release(transaction_t* t)
	{
		t->valid=0;
	}

	transaction_t* lookup(unsigned int id)
	{
		//unsigned int id=h.transaction_id;
		transaction_t *t=get(id);
		if (!t->valid)
			return 0;
		if (t->id!=id)
			return 0;
		return t;
	}

	transaction_t* lookup(const RexUdpMsgHeader& h)
	{
		unsigned int id=h.transaction_id;
		transaction_t *t=get(id);
		if (!t->valid)
			return 0;
		if (t->id!=id)
			return 0;
		return t;
	}
};

#if 0
class RexUdpTransaction
{
public:
	unsigned int tid;

	unsigned int dstId;
	unsigned int dstPort;

	int state;
	unsigned int received_frags;
	unsigned long received_bytes;
	void* allocated_buffer;
	void* p;
	void* m;

	RexUdpTransaction():
		tid(0),
		dstId(0),
		dstPort(0),
		state(0),
		received_frags(0),
		received_bytes(0),
		allocated_buffer(0),
		p(0),
		m(0)
	{
	}

	unsigned int id() const { return tid;}

	void reset(int new_state=0)
	{
		p=allocated_buffer;
		received_bytes=0;
		received_frags=0;
		state=new_state;
		m=0;
	}
};

#define REX_UDP_DEFAULT_MAX_TRANSACTION	32

class RexUdpTransactionManagerDummyNotifier //A default dummy notifier
{
public:
	static void notifyError(const char* /*nickname*/, const char* /*msg*/, const RexUdpTransaction& /*ts*/)
	{
	}
	static void notifyPrintf(const char* /*fmt*/, ...)
	{
	}
};

template<unsigned int MAX_TRANS_=REX_UDP_DEFAULT_MAX_TRANSACTION, class NOTIFIER_=RexUdpTransactionManagerDummyNotifier> class RexUdpTransactionManager
{
public:
	enum { max_transactions=MAX_TRANS_};

	typedef NOTIFIER_ NotifierClass;

	unsigned int nextId;

	RexUdpTransactionManager():
		nextId(0)
	{
		for(int i=0; i<max_transactions; ++i)
		{
			transationCx[i].tid=0;
		}
	}

	static unsigned int txIdDecode(unsigned int src, unsigned int dst, unsigned int txid)
	{
		unsigned int tmp=(src<<16)|(dst<<8)|txid;
		return tmp;
	}

	RexUdpTransaction* lookupByCode(const RexUdpMsgHeader& h)
	{
		return lookupByCode(txIdDecode(h.src, h.dst, h.transaction_id));
	}

	RexUdpTransaction* lookupByCode(unsigned int code)
	{
		for(int i=0; i<max_transactions; ++i)
		{
			if (transationCx[i].tid==code)
				return &transationCx[i];
		}
		return 0;
	}

	RexUdpTransaction* lookup(unsigned int id)
	{
		if (id>max_transactions)
			return 0;
		RexUdpTransaction& t=transationCx[id];
		if (t.state==0)
			return 0;
		return &t;
	}

	RexUdpTransaction* alloc(const RexUdpMsgHeader& h)
	{
		unsigned int tid=txIdDecode(h.src, h.dst, h.transaction_id);

		RexUdpTransaction* pending=lookupByCode(tid); //reste and reuse?
		if (!pending)
			pending=alloc();

		if (pending)
		{
			pending->reset();
			pending->tid=tid;
		}
		return pending;
	}

	RexUdpTransaction* alloc()
	{
		RexUdpTransaction& t=transationCx[nextId];
		if (t.state!=0)
		{
			NotifierClass::notifyError(0, "Abort", t); //rtlog_printf(LID, rtlog_trace|rtlog_breaking_news, "%u: TS-abort (%u:%u)", nextId, t.dstId, t.dstPort);
			abortTs(t);
		}
		nextId=(nextId+1) & (max_transactions-1);
		return &t;
	}
	void reset(RexUdpTransaction& t)
	{
		t.reset();
	}
	void abortTs(RexUdpTransaction& t)
	{
		t.reset();
	}

	RexUdpTransaction transationCx[max_transactions];
};

#endif

#if 0
class RexUdpRemoteMemoryHandle
{
public:
	enum {num_buffers=2};

	typedef unsigned long remote_memory_address_t;

	unsigned int curren_buffer;

	struct mem_reference_t
	{
		remote_memory_address_t address;
		RexUdpHeaderManipulator::db_code_t db_code;
	};
	mem_reference_t mem_ref[num_buffers];

	RexUdpRemoteMemoryHandle(remote_memory_address_t b0, RexUdpHeaderManipulator::db_code_t db0=RexUdpHeaderManipulator::db_invalid, remote_memory_address_t b1=0, RexUdpHeaderManipulator::db_code_t db1=RexUdpHeaderManipulator::db_invalid):
		curren_buffer(0)
	{
		mem_ref[0].address=b0;
		mem_ref[0].db_code=db0;
		if (b1==0)
		{
			b1=b0;
		}
		mem_ref[1].address=b1;
		mem_ref[1].db_code=db1;
	}

	const mem_reference_t& currentBuffer() const
	{
		return mem_ref[curren_buffer];
	}

	const mem_reference_t& nextBuffer()
	{
		curren_buffer^=1;
		return currentBuffer();
	}

	const mem_reference_t& previousBuffer() const
	{
		unsigned int pindex=curren_buffer^1;
		return mem_ref[pindex];
	}
};
#endif

#endif /* RED_UDP_MANIPULATOR_H_ */



#endif /* REX_UDP_MANIPULATOR_H_ */
