/*
 * grifo_playback.cpp
 *
 */

#include "ghost_playback.h"
#include "ghost.h"

#include "DataRecorderBlockFormat.h"
#include "DataRecorderStdFormat.h"

#include "grifo_playback_header.h"

#include <stdint.h>
#include <stdarg.h>
#include <string.h>

#include <thread>

enum gpb_markers_t
{
	M_BLOCK=0x7A7A7A7AU
};

struct bhdr_t
{
	char name[8];
	uint32_t skip_pos;
	uint32_t size;
	uint8_t src;
	uint8_t dst;
	uint16_t spare16;
	uint32_t spare;
};

typedef gpb_header::hdr0_mini_t hdr0_mini_t;

struct node0_hdr_xtra_info_t
{
	union {
		uint32_t m[2];
		char marker[0];
	} u;
	unsigned int xcounter;
	unsigned int xsize;
};


//#define HOST_DEBUG

#ifdef HOST_DEBUG
#include <stdio.h>
#define dbg_printf	printf
#else
static void dbg_printf(const char*, ...)
{
}

#endif

GrifoPlaybackStreamReader::~GrifoPlaybackStreamReader()
{
}


const void* GrifoPlaybackStreamReader::getDataPointer(stream_offset_t) const
{
	return 0;
}

	//virtual void logError(const char* fmt, ...);
	//virtual void vLogError(const char* fmt, va_list args);


#define MAX_OBSERVERS	256

struct GrifoPlaybackObserver
{
	const char* marker;
	GrifoPlaybackStream::block_observer_t observer;
	GrifoPlaybackStream::block_observer_cookie_t cockie;
	int cookieType;
	uint32_t opaque[8];
};

struct GrifoPlaybackImplementation
{
	GrifoPlaybackStreamReader* reader;

	volatile char mslice[4];
	volatile char marker[16];

	bhdr_t bhdr;
	GrifoPlaybackStreamReader::stream_offset_t next_block;
	GrifoPlaybackStreamReader::stream_offset_t next_chunk;
	GrifoPlaybackStreamReader::stream_offset_t next_header;

	int state;

	GrifoPlaybackObserver obs[MAX_OBSERVERS];
	GrifoPlaybackObserver hobs[MAX_OBSERVERS];

	GrifoPlaybackObserver promObserver;

	unsigned int shdr_batch;
	gpb_header::soft_mini_header_t shdr;

	static void scanObserverList(GrifoPlaybackObserver* lst, GrifoPlaybackStream::blockHandle& bH, const char* name, unsigned int nsize)
	{
		for(int i=0; i<MAX_OBSERVERS; ++i)
		{
			if (lst[i].observer==0)
				break;
			if (strncmp(lst[i].marker, name, nsize)==0)
			{
				Ghost::logger().print(GhostLogger::debug, "OBS: %s %llx", lst[i].marker, bH.offset);

				if (lst[i].cookieType)
					lst[i].observer(lst[i].opaque, bH);
				else
					lst[i].observer(lst[i].cockie, bH);
				break;
			}
		}
		return;
	}
};

GrifoPlaybackImplementation p_;

void GrifoPlaybackStream::setReaderInterface(GrifoPlaybackStreamReader* rdr)
{
	p_.reader=rdr;
}

void GrifoPlaybackStream::reset()
{
	p_.state=0;
	p_.next_block=0;
}

GrifoPlaybackStream::marker_t GrifoPlaybackStream::nextBlock()
{
	static const marker_t nullblock={0};
	static bool first_block=true;
	marker_t m={0};

	static GrifoPlaybackStream::blockHandle bH;

	uint32_t bmarker[2]={0,0};

lbl_read_block_:

	if (p_.state==0)
	{
		if (p_.next_block)
		{
			auto cpos=p_.reader->currentPosition();
			if (cpos>p_.next_block)
			{
				//return nullblock;
			}
			p_.reader->setCurrentPosition(p_.next_block);
			p_.next_block=0;
		}

		if (p_.reader->read(bmarker, sizeof bmarker)<0)
		{
			return nullblock;
		}

		if ((bmarker[0]==M_BLOCK) && (bmarker[1]==M_BLOCK))
		{
			p_.reader->read(&p_.bhdr, sizeof p_.bhdr);
			memcpy(&m, p_.bhdr.name, sizeof m);

			p_.next_block=p_.reader->currentPosition()+p_.bhdr.size;

			dbg_printf(":::%8.8s %u->%u (%u)\n", p_.bhdr.name, p_.bhdr.src, p_.bhdr.dst, p_.bhdr.size);

			if (strncmp("SOFT", p_.bhdr.name, 4)==0) //explore subblock
			{
				p_.state=1;
				p_.next_chunk=0;
			}
#if 0
			if (strncmp("DISPHEAD", p_.bhdr.name, 8)==0) //explore header
			{
				p_.state=2;
				p_.next_header=0;

				hdr0_mini_t miniHdr;
				p_.reader->read(&miniHdr, sizeof miniHdr);
				auto h_dim=miniHdr.info.header_dim;


			}
#endif
			else
			{
				bH.dst=p_.bhdr.dst;
				bH.src=p_.bhdr.src;
				bH.size=p_.bhdr.size;
				bH.offset=p_.reader->currentPosition();
				bH.soft_block=0;
				strncpy(bH.name, p_.bhdr.name, sizeof p_.bhdr.name);

				for(int i=0; i<MAX_OBSERVERS; ++i)
				{
					if (p_.obs[i].observer==0)
						break;
					if (strncmp(p_.obs[i].marker, p_.bhdr.name, sizeof p_.bhdr.name)==0)
					{
						if (p_.obs[i].cookieType)
							p_.obs[i].observer(p_.obs[i].opaque, bH);
						else
							p_.obs[i].observer(p_.obs[i].cockie, bH);
						//break;
					}
				}

				if (p_.promObserver.observer)
				{
					p_.promObserver.observer(p_.promObserver.cockie, bH);
				}

				return m;
			}
		}
		else if (first_block==true)
		{
			if (((bmarker[0]==0x5A5A5A5A) && (bmarker[1]==0x5A5A5A5A)))
			{
				Ghost::logger().print(GhostLogger::warning, "Soft recorded data?");
				p_.state=2;
				p_.reader->moveCurrentPosition(-(sizeof bmarker));
			}
		}
		first_block=false;
	}

	if (p_.state>0)
	{
		GrifoPlaybackStreamReader::stream_offset_t next_chunk=0;
		if (p_.next_chunk)
		{
			auto cpos=p_.reader->currentPosition();

			//cpos=p_.reader->currentPosition();
			if (cpos>p_.next_chunk)
			{
				Ghost::logger().print(GhostLogger::error, "SOFT chunk overrun @0x%llX (expected at 0x%llX/0x%llx)", cpos, next_chunk, p_.next_block);
				return nullblock;
			}
			p_.reader->setCurrentPosition(p_.next_chunk);

			next_chunk=p_.next_chunk;

			p_.reader->read(bmarker, sizeof bmarker);
			if (!((bmarker[0]==0xA5A5A5A5) && (bmarker[1]==0xA5A5A5A5)))
			{
				Ghost::logger().print(GhostLogger::error, "Missed SOFT end @0x%llX (expected at 0x%llX/0x%llx)", cpos, next_chunk, p_.next_block);
			}

			//p_.reader->setCurrentPosition(p_.next_chunk);
			p_.next_chunk=0;

		}

		DataRecorderFormat::Block block;

		if (p_.reader->read(block.headerEssential.marker, sizeof block.headerEssential.marker)<0)
		{
			if (!p_.reader->eof())
				Ghost::logger().print(GhostLogger::error, "EOF?");
			p_.state=0;
			return nullblock;
		}

		if ((block.headerEssential.marker[0]==0x5A5A5A5A) && (block.headerEssential.marker[1]==0x5A5A5A5A))
		{
			//DataRecorderFormat::Block block;
			//for(int n=0; n<100; ++n)
			//p_.reader->moveCurrentPosition(-8);
			//p_.reader->read(&block.headerEssential.version, (sizeof block)-((sizeof block.headerEssential.marker)+(sizeof block.data.chunk.data)));
			p_.reader->read(&block.headerEssential.version, (sizeof block.headerEssential)-(sizeof block.headerEssential.marker));
			p_.reader->read(&block.header, (sizeof block)-((sizeof block.headerEssential)+(sizeof block.data.chunk.data)));

			//p_.reader->moveCurrentPosition(-4);

			unsigned long to_skip=block.headerEssential.block_size-(0x80);
			p_.next_chunk=p_.reader->currentPosition()+to_skip;

			if (!block.data.blockName.name[0])
				Ghost::logger().print(GhostLogger::warning, "S-Block: unnamed?");
			else
				Ghost::logger().print(GhostLogger::trace, "S-Block: %c%c%c%c...", block.data.blockName.name[0], block.data.blockName.name[1], block.data.blockName.name[2], block.data.blockName.name[3]);

			bH.dst=p_.bhdr.dst;
			bH.src=p_.bhdr.src;
			bH.size=to_skip;
			bH.offset=p_.reader->currentPosition();

			bH.soft_block=1;
			bH.b_counter=block.header.b_counter;
			bH.s_counter=block.header.s_counter;
			bH.b_timetag=block.header.b_timetag;
			bH.buffer_status=block.header.buffer_status;
			bH.buffer_level=block.header.buffer_level;

			strncpy(bH.name, block.data.blockName.name, sizeof block.data.blockName);

			if (p_.promObserver.observer)
			{
				p_.promObserver.observer(p_.promObserver.cockie, bH);
			}

			for(int i=0; i<MAX_OBSERVERS; ++i)
			{
				if (p_.obs[i].observer==0)
					break;
				if (strncmp(p_.obs[i].marker, block.data.blockName.name, sizeof block.data.blockName)==0)
				{
					Ghost::logger().print(GhostLogger::debug, "OBS: %s %llx", p_.obs[i].marker, p_.reader->currentPosition());
					if (p_.obs[i].cookieType)
						p_.obs[i].observer(p_.obs[i].opaque, bH);
					else
						p_.obs[i].observer(p_.obs[i].cockie, bH);
					break;
				}
			}

			memcpy(&m, block.data.blockName.name, sizeof m);
			return m;
		}
		else
		{
			if (p_.state!=2)
				p_.state=0;
			GrifoPlaybackStreamReader::stream_offset_t cpos=p_.reader->currentPosition()-(sizeof bmarker);
			//cpos-=8;
			if (cpos!=p_.next_block)
			{
				auto delta=p_.next_block-cpos;
				if (cpos>p_.next_block) //(cpos!=p_.next_block)
				{
					Ghost::logger().print(GhostLogger::error, "Missed SOFT start marker @0x%llX (expected at 0x%llX/0x%llx, extra bytes=%llx)", cpos, next_chunk, p_.next_block, delta);
				}
				else
				{
					Ghost::logger().print(GhostLogger::debug, "SOFT start marker @0x%llX (expected at 0x%llX/0x%llx): extra trailing=%llx)", cpos, next_chunk, p_.next_block, delta);
				}
			}
			goto lbl_read_block_;
		}
		return nullblock;
	}

	return nullblock;
}

bool GrifoPlaybackStream::readBlockData(void* buffer, unsigned int size)
{
	if (buffer)
		return p_.reader->read(buffer, size)<0 ? false : true;
	p_.reader->moveCurrentPosition(size);
	return true;
}

bool GrifoPlaybackStream::readBlockData(void* buffer, const GrifoPlaybackStream::blockHandle& b, unsigned int size)
{
	if (b.offset)
		p_.reader->setCurrentPosition(b.offset);
	unsigned int rsize=size ? size : b.size;
	return readBlockData(buffer, rsize);
}

GrifoPlaybackStream::blockHandle GrifoPlaybackStream::tryDecodeSoftReference(const void* p)
{
	static GrifoPlaybackStream::blockHandle bH;

	const DataRecorderFormat::Block& block=*(const DataRecorderFormat::Block*)p;

	bH.soft_block=1;
	bH.b_counter=block.header.b_counter;
	bH.s_counter=block.header.s_counter;
	bH.b_timetag=block.header.b_timetag;
	bH.buffer_status=block.header.buffer_status;
	bH.buffer_level=block.header.buffer_level;

	return bH;
}

const void* GrifoPlaybackStream::getDataPointer(const GrifoPlaybackStream::blockHandle& b)
{
	return p_.reader->getDataPointer(b.offset);
}

bool GrifoPlaybackStream::readSignal(void* buffer, const GrifoPlaybackStream::blockHandle& b)
{
	DataRecorderNumericFormat::info_t info;

	p_.reader->setCurrentPosition(b.offset);
	readBlockData(&info, sizeof info);

	if (p_.shdr.hdr.info.header_marker!=0x12345678)
	{
		Ghost::logger().print(GhostLogger::warning, "No header info");
	}

	auto ssize=info.geometry.dx_stride*info.geometry.dy_stride*sizeof(uint32_t);

	if ((p_.shdr.hdr.signal.packet_descr.npri!=info.geometry.dy) || (p_.shdr.hdr.signal.packet_descr.nrbin!=info.geometry.dx))
	{
		Ghost::logger().print(GhostLogger::warning, "Wrong signal geometry: %ux%u", info.geometry.dx, info.geometry.dy);
	}
	else
	{
		Ghost::logger().print(GhostLogger::debug, "Signal: %ux%u=%u", info.geometry.dx, info.geometry.dy, ssize);
	}
	return readBlockData(buffer, ssize);
}

void GrifoPlaybackStream::registerBlockObserver_(const char* marker, block_observer_t observer, block_observer_cookie_t cookie, unsigned int cookie_size)
{
	for(int i=0; i<MAX_OBSERVERS; ++i)
	{
		if (p_.obs[i].observer==0)
		{
			p_.obs[i].observer=observer;
			p_.obs[i].cockie=cookie;
			p_.obs[i].marker=marker;
			p_.obs[i].cookieType=1;
			memcpy(p_.obs[i].opaque, cookie, cookie_size);
			break;
		}
	}
}

void GrifoPlaybackStream::registerHeaderObserver_(const char* marker, block_observer_t observer, block_observer_cookie_t cookie, unsigned int cookie_size)
{
	for(int i=0; i<MAX_OBSERVERS; ++i)
	{
		if (p_.hobs[i].observer==0)
		{
			p_.hobs[i].observer=observer;
			p_.hobs[i].cockie=cookie;
			p_.hobs[i].marker=marker;
			p_.hobs[i].cookieType=1;
			memcpy(p_.hobs[i].opaque, cookie, cookie_size);
			break;
		}
	}
}

void GrifoPlaybackStream::registerPromisucousBlockObserver(block_observer_t observer, block_observer_cookie_t cookie)
{
	p_.promObserver.cockie=cookie;
	p_.promObserver.observer=observer;
}

void GrifoPlaybackStream::registerBlockObserver(const char* marker, block_observer_t observer, block_observer_cookie_t cookie)
{
	for(int i=0; i<MAX_OBSERVERS; ++i)
	{
		if (p_.obs[i].observer==0)
		{
			p_.obs[i].observer=observer;
			p_.obs[i].cockie=cookie;
			p_.obs[i].marker=marker;
			p_.obs[i].cookieType=0;
			break;
		}
	}
}

#if 0
void GrifoPlaybackStream::registerheaderObserver(const char* marker, block_observer_t observer, block_observer_cookie_t cookie)
{
	for(int i=0; i<MAX_OBSERVERS; ++i)
	{
		if (p_.hobs[i].observer==0)
		{
			p_.hobs[i].observer=observer;
			p_.hobs[i].cockie=cookie;
			p_.hobs[i].marker=marker;
			break;
		}
	}
}
#endif


const char* GrifoPlaybackStream::preprocessSoftHeader(const void* buffer, unsigned int size)
{
	//const char* reply=0;
	if (size<sizeof p_.shdr)
	{
		p_.shdr.hdr.info.header_marker=0;
		return "HDR too small";
	}
	p_.shdr=*reinterpret_cast<const decltype(p_.shdr)*>(buffer);
	if (p_.shdr.hdr.info.header_marker!=0x12345678)
	{
		return "HDR invalid";
	}

	if (p_.shdr.hdr.info.header_dim>size)
	{
		return "HDR too small";
	}
	if (p_.shdr.hdr.signal.bacth_counter!=(p_.shdr_batch+1))
	{
		Ghost::logger().print(GhostLogger::warning, "Batch hole: %u -> %u", p_.shdr_batch, p_.shdr.hdr.signal.bacth_counter, p_.shdr.hdr.signal.bacth_counter-p_.shdr_batch);
	}
	p_.shdr_batch=p_.shdr.hdr.signal.bacth_counter;
	return 0;
}

void GrifoPlaybackStream::scanHeaderExt(const blockHandle& handle, unsigned int start_offset)
{
	static blockHandle bH;

	auto r=handle.size-start_offset;

	for(unsigned int i=0; i<r;)
	{
		node0_hdr_xtra_info_t tmp;
		p_.reader->readDatum(&tmp);
		if (tmp.u.m[0]!=0x20545845U)
		{
			p_.reader->setCurrentPosition(handle.offset+handle.size);
			break;
		}
		Ghost::logger().print(GhostLogger::trace, "  XINFO: 0x%4.4X %u %u", tmp.u.m[1], tmp.xsize, i);

		bH.offset=p_.reader->currentPosition();
		bH.size=tmp.xsize;
		strncpy(bH.name, tmp.u.marker, sizeof(tmp.u));
		p_.scanObserverList(p_.hobs, bH, tmp.u.marker, sizeof tmp.u);

		if (p_.promObserver.observer)
		{
			p_.promObserver.observer(p_.promObserver.cockie, bH);
		}

		p_.reader->setCurrentPosition(bH.offset+bH.size);
		//p_.reader->moveCurrentPosition(tmp.xsize);
		i+=tmp.xsize+sizeof tmp;
	}
}

void GrifoPlaybackStream::scanFpgaHeader(void* hdrBuffer, unsigned int /*hdrBufSize*/, const blockHandle& handle)
{
	//static uint32_t tmp[(8*1024)/sizeof(uint32_t)];

	hdr0_mini_t* miniHdr=reinterpret_cast<hdr0_mini_t*>(hdrBuffer);

	p_.reader->setCurrentPosition(handle.offset);
	p_.reader->read(miniHdr, sizeof *miniHdr);


	//p_.reader->readDatum(&miniHdr);

	auto hs=miniHdr->hdr.info.header_dim;
	auto r=handle.size-hs;
	Ghost::logger().print(GhostLogger::trace, "hdr read: %u of %u (%u)", hs, handle.size, r);

	p_.reader->read(&miniHdr[1], (hs-(sizeof(gpb_header::signal_mini_header_t))));

	//p_.reader->moveCurrentPosition(hs-sizeof(gpb_header::header_info_t));

	static blockHandle bH;

	for(unsigned int i=0; i<r;)
	{
		node0_hdr_xtra_info_t tmp;
		p_.reader->readDatum(&tmp);
		if (tmp.u.m[0]!=0x20545845U)
		{
			p_.reader->setCurrentPosition(handle.offset+handle.size);
			break;
		}
		Ghost::logger().print(GhostLogger::trace, "  XINFO: 0x%4.4X %u %u", tmp.u.m[1], tmp.xsize, i);

		bH.offset=p_.reader->currentPosition();
		bH.size=tmp.xsize;
		strncpy(bH.name, tmp.u.marker, sizeof(tmp.u));
		p_.scanObserverList(p_.hobs, bH, tmp.u.marker, sizeof tmp.u);

		if (p_.promObserver.observer)
		{
			p_.promObserver.observer(p_.promObserver.cockie, bH);
		}

		p_.reader->setCurrentPosition(bH.offset+bH.size);
		//p_.reader->moveCurrentPosition(tmp.xsize);
		i+=tmp.xsize+sizeof tmp;
	}
}


void GrifoPlaybackStream::sleep(unsigned int ms)
{
	std::this_thread::sleep_for(std::chrono::milliseconds(ms));
}
