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


// base.lib.a
#include <base/heap.h>
#include <base/mutex.h>

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



// must relocate the instantiation due to hai-os headers clashing with Genode headers...
extern hog::ConsumerBase * instantiate_output_consumer( Genode::Env & env, Genode::Mutex & mutex_ref );

///ToDo-2: xx clean this up xx
/******************/
#include "_ge_AvDecoder.cpp"
//#include "_ge_AudioIn.cpp"
/******************/



//#pragma mark -


class Audio_player::Main
{
private:  // Data

		Genode::Mutex feedLock;  // protect concurrent access of Audio_player data by the decoder/feeder thread and the client thread(s)
		
		Genode::Env & env;
		Genode::Heap  alloc;
		
		//Genode::Signal_handler<Main>  progress_dispatcher;
		hog::ConsumerBase * outputConsumer;  // might be either a ConsumerNative or ConsumerOss
		hog::FramesProducer * libavDecoder;


private:  // Data -- accessed by BMediaFile...

#if 0
	class AdvancerThread : public Genode::Thread
	{
	public:
		AdvancerThread( Genode::Env & env, Audio_player::Main & hub );
		
		void entry() override;
		
	public:  // (!)
		Audio_player::Main & playerMain;
	};
	AdvancerThread feederThread;
	bool threadNeedsJoining;
#else
		thread_id feedThread;
#endif
		bool feedTearDown;
		//bool feedPaused = false;
		//bool is_stopped = false; ///need something like this (but not quite this), to handle "decoder is exhausted" situation
	

private:  // Code

		void feedMixer();
		void accumulateDecodedFrames();


public:

		Main( Genode::Env & env, Genode::String<256> path );
		~Main();
		
		bool IsTearingDown() const;
		
		//void handle_progress();
		void Advance();
		
		void start();//
		void stop();//
		
		uint64_t progress();// const;
		uint64_t current_frame();// const;
		uint64_t duration();// const;
		uint64_t count_frames();// const;
};



static
status_t audio_out_entry( void * ptr )
{
	//Genode::log( "Entering audio_out_entry with obj=", ptr, " and name: ", Genode::Thread::myself()->name(), " .........." );
	
	if( nullptr == ptr )
	{
		Genode::error( "NULL audio_out ptr" );
		return -1;
	}
	
	auto obj = static_cast<Audio_player::Main*>( ptr );
	
	// With this extra call we make sure the thread starts with *2* back-to-back calls
	// to Advance(), before we ever begin to snooze():
	//
	obj->Advance();
	
	while( false == obj->IsTearingDown() )
	{
		bigtime_t t_minus = system_time();
		
		obj->Advance();
		
#if 1
		// Note: we sleep *without* holding the <feedLock> mutex, of course:
		snooze( hog::snooze_micros );
	///later: snooze misbehaves, sleeps for 100 ms instead of 10 ms, ten times too slow ! Should we use a Genode timer's msleep() call instead?
#else
	// this doesn't help:
		bigtime_t next = t_minus;
		next += hog::snooze_micros;
		// compensate for time spent in Advance() (including the expensive file reading!):
		bigtime_t delta = next;
		delta -= system_time();
		//Genode::log("snoozing for ", delta, " µs");// typically says e.g. "snoozing for 9800 µs"
		snooze( delta );
#endif
	}
	
	//Genode::log( "-Leaving- audio_out_entry ..........thread name: ", Genode::Thread::myself()->name() );
	
	return 0;
}



//#pragma mark -


Audio_player::Main::Main(
		Genode::Env & _env,
		Genode::String<256> path///ToDo-2: 1024+ bytes
		)
:	feedLock()
	,env( _env )
	,alloc( env.ram(), env.rm() )
	//,progress_dispatcher( env.ep(), *this, &Main::handle_progress )
	,outputConsumer( nullptr )
	,libavDecoder( nullptr )
#if 0
	,feederThread( env, *this )
	,threadNeedsJoining( false )
#else
	,feedThread( 0 )
#endif
	,feedTearDown( false )
{
	GeTRACE( "audio_main.Ctor, path: ", path, "   ... parent thread name: ", Genode::Thread::myself()->name() );
	Genode::log( "audio_main/libav:",
		" used RAM = ", env.pd().used_ram().value,
		" used Caps = ", env.pd().used_caps().value );
	
	outputConsumer = instantiate_output_consumer( env, feedLock );
	outputConsumer->rl.log( "\n create-decoder (will throw if invalid file); " );
	
	libavDecoder = new (&alloc) hog::FramesProducer( path );
	outputConsumer->rl.log( " dump-info; " );
	Genode::log( "file info: -----------" );
	libavDecoder->PrintToStream( path.string() );
	
	outputConsumer->rl.log( " audio_main.Ctor complete; " );
}


Audio_player::Main::~Main()
{
	//Genode::log( "audio_main.DTOR" );
	outputConsumer->rl.log( "\n audio_main.DTOR; " );
	
	// T- (since this calls lock(), so must be done *before* the below lock())
	stop();
	
	// T+ (should only block waiting for thread once the above call has unlocked)
	outputConsumer->rl.log( " tearing *Down* 'feed' thread; " );
#if 0
	if( threadNeedsJoining )
		feederThread.join();
#else
	wait_for_thread( feedThread, NULL );
#endif
	
	// Be extra careful and lock here to prevent accessors being called while we delete ?
	// Though if someone's calling "delete BMediaFile" it wouldn't make sense that they also call accessors
	Genode::Mutex::Guard lock_guard( feedLock );
	
	Genode::destroy( &alloc, libavDecoder );  libavDecoder = nullptr;
	//Genode::log("destroy done");
	
	delete outputConsumer; outputConsumer = nullptr;
	//Genode::log("delete audio-out done");
}


bool Audio_player::Main::Main::IsTearingDown() const
{
	return feedTearDown;
}


void Audio_player::Main::Advance()
{
	//xx no need for this one, get rid of it xx
	if( feedTearDown )
		return;
	
	// goal: we want to go to sleep only after stashing decoded samples to the roof
	// and filling the audio queue to the highest tolerable threshold (i.e. strike
	// a balance between excessive latency and instable/choppy playback)
	
	//Genode::log( " Advance() -- progress: ", progress(), " ms");  // clearer than tracing current_frame() vs. count_frames()
	
	feedMixer();
	
	// 30 iterations is enough to fill our 256 KB fifo... better now with 30 ?
	///ToDo: nope, makes no difference, still getting "impossible queue count"... seems harmless though ?
	///UPD: migrate to new mixer, see if that fixes it...
	for( int i = 0; i < 30; i++ )  // we don't want a (potentially buggy/infinite) loop, but we don't want to call this just once either, hence this arbitrary for():
		accumulateDecodedFrames();
	
	//log( "  bmf.accumulated: ", libavDecoder->DecodedFrames().read_avail() );
	
	// once more for good measure, in case a couple packets were consumed during decoding ? :
	//feedMixer();
}


void Audio_player::Main::feedMixer()
{
	Genode::Mutex::Guard lock_guard( feedLock );
	if( feedTearDown )
		return;
	
	if( libavDecoder->DecodedFrames().read_avail() > 0 )
	{
		outputConsumer->ConsumeBuffer( libavDecoder->DecodedFrames() );
		//outputConsumer->rl.Dump();
	}
}


void Audio_player::Main::accumulateDecodedFrames()
{
///FIXME: (record_play_mixer) uses a concurrent thread beside this one, so we should *not* hold the lock for the whole duration, only lock at the last moment when doing the fifo.write(), otherwise the consumer will be "starved" waiting for the producer, whenever the producer does expensive decoding, resulting in audible 'blanks'
	Genode::Mutex::Guard lock_guard( feedLock );
	if( feedTearDown )
		return;
	
	if( libavDecoder->DecodedFrames().write_avail() <= hog::writeavail_threshold )
		//
		return;  // not enough room to write an entire chunk, we're "full"
		//
	
	//outputConsumer->rl.log( "\n prodbuffer; " );
	
	// T0
	//
	const int n = libavDecoder->ProduceBuffer( hog::decode_bytes_threshold );
	outputConsumer->rl.log( " fifo+=" );
	outputConsumer->rl.log( n );
	outputConsumer->rl.log( "; " );
	if( n <= 0 )
	{
		outputConsumer->rl.log( "\n ** zero production! flush previously produced.. ** " );
		Genode::log( " * zero prod : set Tear Down ! *" );
		
///ToDo-2: once the first stage is solved (all reliability issues), refactor some more, so that we keep decoding to the end, we must not tear down prematurely and discard the tail-end audio samples (which have been decoded by libAV but not yet sent to mixer)
#if 1
		// Seems the decoder blows up sometimes, when called beyond EoF -- as a temporary fix, do a brutal bail out and make sure we do NOT call ProduceBuffer() any more:
		feedTearDown = true;
		// +Halt() ?
		
	//	outputConsumer->rl.Dump();
#endif
	}
}



void Audio_player::Main::start()
{
	GeTRACE( "main-audio.start" );
	
	if( feedThread > 0 )
		Genode::error( "audio.start() was already called ! (will create a redundant thread ?)" );
	
	/*
	outputConsumer.start_timer();
	// "prime the pump" ?:
	//handle_progress();
	*/
#if 0
	Genode::Mutex::Guard lock_guard( feedLock );
	
	feederThread.start();
	threadNeedsJoining = true;
#else
	// we need a pthread, not a "bare" Genode thread, so that we can execute libav's libc code:
	status_t st = -1;
	feedThread = spawn_thread( audio_out_entry, "audio_out_entry", 10, this );
	if( feedThread > 0 )
		st = resume_thread( feedThread );
	if( st != B_OK )
		Genode::error( "audio_out: could not spawn/resume thread" );
#endif
}


void Audio_player::Main::stop()
{
	//Genode::log( "main.stop()" );
	
	feedTearDown = true;
	
	/**********/
	Genode::Mutex::Guard lock_guard( feedLock );
	
	//outputConsumer.stop_timer();  // tell the timer or sigh to no longer call our progress() hook
	
	//Genode::log( "main.out.halt()" );
	outputConsumer->Halt();
}



uint64_t Audio_player::Main::progress()// const
{
	GeTRACE( "  bmf.main.progress" );
	Genode::Mutex::Guard lock_guard( feedLock );
	
	return outputConsumer ? outputConsumer->Progress_ms() : 0;
}

uint64_t Audio_player::Main::duration()// const
{
	GeTRACE( "  bmf.main.duration" );
	Genode::Mutex::Guard lock_guard( feedLock );
	
	return libavDecoder ? libavDecoder->Duration() : 0;
}

uint64_t Audio_player::Main::current_frame()// const
{
	GeTRACE( "  bmf.main.curframe" );
	Genode::Mutex::Guard lock_guard( feedLock );
	
///FIXME-2: who uses this ? This should rely on outputConsumer instead...
	return libavDecoder ? libavDecoder->current_frame() : 0;
};

uint64_t Audio_player::Main::count_frames()// const
{
	GeTRACE( "  bmf.main.count_frames" );
	Genode::Mutex::Guard lock_guard( feedLock );
	
///FIXME-2: who uses this ? This should rely on outputConsumer instead...
	return libavDecoder ? libavDecoder->count_frames() : 0;
};


