/*
 * Copyright 2023-2024, ttcoder
 * All rights reserved. Distributed under the terms of the MIT license.
 */
#ifndef _ge_consumer_base_H
#define _ge_consumer_base_H


// base.lib.a
#include <audio_out_session/connection.h>

// libmedia.so
#include "RingLog.h"  // member


/**************/
//#define GeTRACE Genode::log
#define GeTRACE(x...) ;


namespace hog
{
	// constants
	//
	enum { LEFT, RIGHT, NUM_CHANNELS };
	enum { AUDIO_OUT_PACKET_SIZE = NUM_CHANNELS * Audio_out::PERIOD * sizeof(float) };
		///should rename that constant like "Interleaved_AudioOut_CHUNK_Bytes", since it's the number of bytes
		// we 'pop' from the FIFO to the convert from all-in-one-buffer interleaved, into two-separate-packets
		// i.e. it is equal to "left_packet_byte_size + right_packet_byte_size", NOT to the byte size of a single AudioOut packet !!
		// i.e.  "audio_pkt_size = channel_count * samples_per_channel_packet * bytes_per_sample"
	
#if 1  // legacy-mixer:
	enum { snooze_micros = 25000/*55000*/ };  // ca. 40 Hz
		// pause for that many µs between each "wake-up -> decode -> fill queue" step.
	enum { upper_queue_threshold = 64 /*32*/ /*16*/ };
		// upper limit of how many mixer/audio_drv-side packets to fill before snoozing.
		// max 254 (for ca. 3 seconds worth of audio buffering).
		// min circa 16 (otherwise it's hard to avoid under-runs, i.e. hard to be certain to keep up and queue packets in time)
#else
	//enum { upper_queue_threshold... } ///xx what's the equivalent for new-mixer?
	//enum { snooze_micros = 10000 };  // ca. 100 Hz
#endif
	enum { ringbuf_size_kb = 256 };
		// 256 KB -> enough to package <upper_queue_threshold> packets (64 packets)
	enum { writeavail_threshold = 128*1024 /*16*1024*/ };
		// keep filling the frames ring until there's only that many free bytes left
		// (we don't try to fill to the brim as the AV decoder deals in "chunks", and if the last chunk does not fit in full it's too late to "rewind" the decoder)
		// (otherwise we get errors like: << attempting to write 9216 bytes but only 5120 free >>
		//UPD: threshold decreased way down, to only half (128 of 256 KB), seems to work better now, maybe we were getting 'stuck' on lack of room for decoding ?
	enum { decode_bytes_threshold = writeavail_threshold / 8 };
		// call libav's decode() until we get (a little more than) 16384 bytes
	
	// types
	//
	class FramesProducer;
	class ConsumerBase;
	class ConsumerNative;
	class ConsumerOss;
	
	template <size_t> class SimpleRingBuffer;
	typedef SimpleRingBuffer< ringbuf_size_kb/*256*/ * 1024 > Frame_data;
	
	
	/****** Latency config ******
	To increase performance/reliability:
		- increase queue threshold (higher latency)
		- decrease snooze (faster responsivness by more aggressive polling)
	To stress-test:
		- decrease queue threshold (lower latency)
		- increase snooze
	
	Changing the size of the decoded-frames FIFO plays a lesser role than the other settings.
	
	Latency tests (on T410, with snooze(119000)):
		- 24 packets deep: we frequently "miss a beat", with a "blank" of ca. 2 second (why so long ? full 255 packets cycle maybe ?)
		- 32 packets deep: same as 24 deep
		- 64 packets deep: looks good, no need to go up to 128 it seems
	
	The "blanks" get logged as "Low Queue Count"...
	Bug in queued() implementation ? Maybe when empty it tends to not return "0" but rather "255" ?
	Let's add a "Impossible Queue Count" logger.
	*/
};



template <size_t CAPACITY>
class hog::SimpleRingBuffer
{
	// AKA "trivial ring buffer" AKA "not really a *ring* buffer at all, using memmove() instead"
	
public:
		SimpleRingBuffer()
		:	currentSize( 0 )
		{
		}
		
		size_t write_avail() const
		{
			return CAPACITY - currentSize;
		}
		
		size_t write( const void * src, size_t len )
		{
			if( len > write_avail() )
			{
				///ToDo: instead of rejecting new frames I should discard some oldest frames, just enough to make room for newest frames, so as to minimize the damage...
				
				Genode::warning( "frames overflow -- ringbuffer: attempting to write ", len, " bytes but only ", write_avail(), " free" );
				//
				return 0;
				//
			}
			
			// T0
			memcpy(
				bufData + currentSize,
				src,
				len
				);
			
			// T+
			currentSize += len;
			
			return len;
		}
		
		size_t read( void * dst, size_t len )
		{
			if( len > currentSize )
			{
				Genode::error( "ringbuffer: attempting to read ", len, " bytes, but only ", currentSize, " available" );
				//
				return 0;
				//
			}
			
			// T-
			memcpy( dst, bufData, len );
			
			// T0
			memmove( bufData, bufData+len, currentSize-len );
			currentSize -= len;
			
			// T+
			memset( bufData+currentSize, '\0', len );
			
			return len;
		}
		
		size_t read_avail() const
		{
			return currentSize;
		}


private:  // Data

		uint8 bufData[ CAPACITY ];
		size_t currentSize;
};



class hog::ConsumerBase
{
public:
		ConsumerBase( Genode::Env & env )
		:	rl( env, false/**/, false )
		{
		}
		virtual ~ConsumerBase()
		{
		}
		
			//+ "IsBelowThreshold()"...
		virtual bool IsAlmostFull() = 0;
		virtual void ConsumeBuffer( Frame_data & frame_data ) = 0;
		virtual void Halt() = 0;
		virtual uint64_t Progress_ms() = 0;

public:  // Data (!)
		RingLog rl;
};


#endif // _ge_consumer_base_H

