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


// Genode *native* back-end for:
// BSoundPlayer

// base.lib.a
#include <base/attached_ram_dataspace.h>  // member
//#include <base/signal.h>

// libmedia.so
#include "_ge_consumer_base.h"  // ancestor



class hog::ConsumerNative : public ConsumerBase
{
	// Consumes audio frames produced by FramesProducer,
	// sends them to Audio_out service (e.g. mixer)
	
public:

		ConsumerNative( Genode::Env & env );
		~ConsumerNative();
		
		// ConsumerBase
		bool IsAlmostFull() override;
		void ConsumeBuffer( Frame_data & frame_data ) override;
		void Halt() override;
		virtual uint64_t Progress_ms();


private:  // Code

		void packageFloats( const float * const decoded_interleaved );
		
		//void handle_audio_out_progress();
		//size_t frame_size() const;


private:  // Data

		Genode::Attached_ram_dataspace 
			writeBuf;
		Genode::Constructible<Audio_out::Connection>
			outChannels[NUM_CHANNELS];
		
		//Genode::Signal_context_capability  sigHandler; _d_
		//Genode::Signal_handler<ConsumerNative> sigHandler;//  progress_dispatcher;
		
		// stats
		uint64_t framesSubmitted;
};


// work-around for Hai-OS's #defines (imported by MediaFile.cpp) interfering with "attached_ram_dataspace.h" (imported by this file)...
/**********************/
hog::ConsumerBase * instantiate_output_consumer( Genode::Env & env, Genode::Mutex & /*mutex_ref*/ )
{
	return new hog::ConsumerNative( env );
}
/**********************/


hog::ConsumerNative::ConsumerNative( Genode::Env & env )
:	ConsumerBase( env )
	,writeBuf( env.ram(), env.rm(), AUDIO_OUT_PACKET_SIZE )
	//,sigHandler( env.ep(), *this, &hog::ConsumerNative::handle_audio_out_progress )
	,outChannels()
	,framesSubmitted( 0 )
{
	GeTRACE( "native_out.Ctor" );
	
	// build channels
	for( int i = 0; i < NUM_CHANNELS; i++ )
	{
		static const char *names[] = { "left", "right" };//+static_assert re. NUM_CHANNELS..
		
		try
		{
			outChannels[i].construct( env, names[i], false, false );
			
			rl.log( " created channel " ); rl.log( i );
		}
		catch( ... )
		{
			Genode::error( "cannot create Audio_out #", i, " aka ", names[i] );
			throw;
		}
	}
	
	// subscribe to mixer signals
	//outChannels[0]->progress_sigh( sigHandler );
	
	// start streaming to mixer
	for( int i = 0; i < NUM_CHANNELS; i++)
		outChannels[i]->start();
	
	rl.log( " ctor complete; " );
}


hog::ConsumerNative::~ConsumerNative()
{
	GeTRACE( "native_out.DTor" );
	
	// unsubscribe
	//outChannels[0]->progress_sigh( Genode::Signal_context_capability() );
	
	// stop streaming to mixer
	Halt();
}


void hog::ConsumerNative::Halt()
{
	for( int i = 0; i < NUM_CHANNELS; i++)
	{
		if( outChannels[i].constructed() )
		{
			outChannels[i]->stop();
			//outChannels[i].destruct();
		}
	}
}



bool hog::ConsumerNative::IsAlmostFull()
{
	// RETURNS: (comparison of queued() and <upper_queue_threshold>):
	//
	// - strictly superior:	-> error ! (should not happen, although it does)
	// - equal:				-> return true
	// - strictly inferior:	-> return false (i.e. allow additional queueing up)
	
///FIXME-2: strange, I can't get this message to disappear, even if I enlarge buffer sizes and the queue threshold and decrease the snooze time... I get this situation at the beginning of each mp3, for a second or two, even after configuring to queue up 64 packets... File a ticket ? Or am I using the Audio_out session wrong ?
// (restore the "Dump()" call below for evidence of overflow)
// UPD: that will disappear with "new mixer" ?
	auto count = outChannels[0]->stream()->queued();
	if( count > upper_queue_threshold )
	{
		rl.log( " overflow:" );// *** Impossible Queue Count (overflow?): " );
		rl.log( count );
	//	rl.Dump();
	}
	
	return
		outChannels[0]->stream()->queued()
		>=
		upper_queue_threshold
		;
}


uint64_t hog::ConsumerNative::Progress_ms()
{
	return ((float)framesSubmitted / Audio_out::SAMPLE_RATE) * 1000.;
}


///++ Consume"Frames"
void hog::ConsumerNative::ConsumeBuffer( Frame_data & frame_data )
{
	//const unsigned count_frames = frame_data.read_avail() / (NUM_CHANNELS*sizeof(float));
	GeTRACE( " ConsumeBuffer: need to tx ", frame_data.read_avail(), " bytes (", count_frames, " frames)" );
	
	if( frame_data.read_avail() <= 0 )
	{
		rl.log( " *native sink: empty production, return* " );
		//Genode::log( "** native sink: empty production? failed to decode?" );
		//
		return;
		//
	}
	
///FIXME-2: how come queued() returns 1 (instead of 0) the very first time we're called, before we even start to queue buffers ?
// UPD: that will disappear with "new mixer" ?
	const auto queued = outChannels[0]->stream()->queued();
	if( queued <= 3 )
	{
		rl.log( "\n Low Queue Count (underrun ? or maybe we're just starting up ?): " );
		rl.log( queued );
		/********/
		rl.Dump();
	}
	
	//get rid of this?:
	{
		rl.log( "\n t- pipeline: " ); rl.log( queued ); rl.log( " packets; " );
		rl.log( frame_data.read_avail() ); rl.log( " bytes in fifo; " );
		
		if( IsAlmostFull() )
		{
			//rl.log( " BAIL OUT; " );
			//rl.Dump();
			//Genode::warning( "BAIL OUT, there's already ", queued, " packets queued" );
			//
			return;
			//
		}
	}
	
#if 0
	float tmp[AUDIO_OUT_PACKET_SIZE / sizeof(float)] = { 0 };
	// that's a sizeable buffer at 4096 bytes big, let's use a DS instead:
#else
	float * tmp = writeBuf.local_addr<float>();
#endif
	
	// loop until we exhaust <frame_data> or until we reach the desired buffering level
	//
	while( frame_data.read_avail() >= AUDIO_OUT_PACKET_SIZE
		&& false==IsAlmostFull()
		)
	{
		size_t const n = frame_data.read( tmp, AUDIO_OUT_PACKET_SIZE );
		rl.log( "\n  pop " ); rl.log( n ); rl.log( " bytes; " );
		
		if( n != AUDIO_OUT_PACKET_SIZE )
		{
			rl.log( " ** n != AUDIO_OUT_PACKET_SIZE ** " );
			Genode::warning( "retrieved fewer frames than expected" );
			//
			break;
			//
		}
		
		packageFloats( tmp );
	}
	
	//Genode::log( "  sink.queued: ", outChannels[0]->stream()->queued() );
	
	///FIXME-2: what about the very last few bytes of an mp3 ? Need to send a zero-ed out, "partial" packet
	// and to read exactly what remains, like:
	//
	// frame_data.read( last_buf, frame_data.read_avail() );
}


//++ "pushOneStereoPacketPair()"
void hog::ConsumerNative::packageFloats( const float * const decoded_interleaved )
{
	// We convert *one* buffer of PERIOD frames (interleaved, dual samples),
	// into *two* packets of PERIOD samples (planar, single samples), i.e.:
	//
	// INPUT:
	//	- float array, one left packet + one right packet's worth (two channels interleaved)
	// OUTPUT:
	//	- left packet  (PERIOD * samples)
	//	- right packet (PERIOD * samples)
	
	Audio_out::Packet * left_pk = nullptr;
	try
	{
		rl.log(" alloc;");
		
		left_pk = outChannels[0]->stream()->alloc();
	}
	catch ( ... )
	{
		Genode::error( "audio-out: RESET on Under-Run (?)" );
		rl.log(" *RESET On Underrun* SHOULD WE TRY AGAIN ? (in a for() loop) ?\n");
		
		// seems we're more stable(?) without this:
		//outChannels[0]->stream()->reset();
		
		///ToDo: or maybe, to the contrary, the above could reduce the ~3 seconds "blanks" to be much shorter ? Either up there, or in ConsumeBuffer() ? Well I no longer get audio silence/blanks with the queue threshold increased to 64...
	}
	
	Audio_out::Packet * right_pk = nullptr;
	{
		auto pos = outChannels[LEFT ]->stream()->packet_position( left_pk );
		right_pk = outChannels[RIGHT]->stream()->get( pos );
	}
	
#if 0
		// DEBUG: Square wave generator...
		//
		for( unsigned i = 0; i < Audio_out::PERIOD; i++ )
		{
			float tmp_val = 0.;
			
			if( i < Audio_out::PERIOD/2.0 )
				tmp_val = 0.25;
			else
				tmp_val = -0.25;
			
			/**/float * floats[NUM_CHANNELS] = { left_pk->content(), right_pk->content() };
			floats[LEFT ][i] = tmp_val;
			floats[RIGHT][i] = tmp_val;
		}
#else
		//fillFloats( left_pk->content() );
		//fillFloats( right_pk->content() );
		
		// Stereo sample loop (one pair of packets)
		//
		const unsigned frames_per_packet = Audio_out::PERIOD;
		for( unsigned f = 0; f < frames_per_packet; f++ )
		{
			float * chan_pkt[NUM_CHANNELS] = { left_pk->content(), right_pk->content() };
			
			chan_pkt[LEFT ][f] = decoded_interleaved[ f * NUM_CHANNELS +LEFT ];
			chan_pkt[RIGHT][f] = decoded_interleaved[ f * NUM_CHANNELS +RIGHT ];
		}
		
		framesSubmitted += frames_per_packet;
#endif
	
	rl.log( " submit#" ); rl.log( outChannels[0]->stream()->packet_position((left_pk)) );
	
	// push packets
	outChannels[LEFT ]->submit( left_pk );
	outChannels[RIGHT]->submit( right_pk );
	
	{
		auto queued = outChannels[0]->stream()->queued();
		rl.log( "; t+ pipeline: " ); rl.log( queued ); rl.log( " pkts; " );
	}
}


#if 0
void hog::ConsumerNative::handle_audio_out_progress()
{
	Genode::log( "handle_audio_out_progress" );
	/*
	if constructed..
	auto queued = outChannels[0].stream()->queued();
	log( queued );
	*/
}
#endif



