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


// genode: format.lib.a
#include <format/snprintf.h>

// this
#include "RingLog.h"



RingLog::RingLog(
			Genode::Env & env,
			bool enable_periodic_snapshot,
			bool enable_instant_parrot
			)
:	logMutex()
	,logThread( env, *this )
	,logThreadNeedsJoining( false )  // we make sure to init to 'false' to avoid join()ing when we're not supposed to
	,enableSnapshots( enable_periodic_snapshot )
	,instantMode( enable_instant_parrot )
	,buf1 {}
	,buf2 {}
	,buf2isOldest( false )
{
	if( enableSnapshots )
	{
		logThread.start();
		logThreadNeedsJoining = true;  // it's ok to join() in the dtor, that won't crash the app
	}
}

RingLog::~RingLog()
{
	// dump one last time, in case some contents was logged
	// between the last thread run and this dtor
	Dump();
	
	// terminate sub-thread
	logThread.tearDown = true;
	
	if( logThreadNeedsJoining )
	{
		logThread.join();
		logThreadNeedsJoining = false;  // for symmetry's sake
	}
}


void RingLog::log( int num )
{
	char buf[64] = {};
	Format::snprintf( buf, sizeof(buf), "%d", num );
	
	log( buf );
	
	// optional "immediate" mode
	if( instantMode )
		Genode::log( (const char*)buf );
}


void RingLog::log( const char * text )
{
	if( Genode::strlen(text) >= SIZE )
	{
		Genode::error( "RingLog overflow, recompile with SIZE > ", Genode::strlen(text), " instead of ", int(SIZE) );
		
		//Genode::sleep_forever();
		
		return;
	}
	
	Genode::Mutex::Guard lock_guard( logMutex );
	
	// need to make room for log ?
	if( Genode::strlen(newest()) + Genode::strlen(text) >= SIZE )
	{
		// T0 : swap buffers
		buf2isOldest = ! buf2isOldest;
		
		// T+ : clear newly-fronted buffer
		Genode::memset( newest(), '\0', SIZE );
	}
	
	// append log
	Genode::copy_cstring(
		newest() + Genode::strlen(newest()),
		text,
		Genode::strlen(text) + sizeof('\0')
		);
	
	// optional "immediate" mode
	if( instantMode )
		Genode::log( text );
}

void RingLog::Dump()
{
	Genode::Mutex::Guard lock_guard( logMutex );
	
	// special case for empty/nothing-logged-yet case: condensed display
	if( Genode::strlen(oldest()) <= 0 && Genode::strlen(newest()) <= 0 )
	{
		Genode::log( "---- <empty ringlog> ----" );
		//
		return;
		//
	}
	
	Genode::log( "---- [", logThread.conTimer.elapsed_ms(), " ms] ----" );
	Genode::log( (const char*)oldest() );
	Genode::log( (const char*)newest() );
	Genode::log( "----" );
}

const char * RingLog::oldest()
{
	return buf2isOldest
		? buf2
		: buf1
		;
}

char * RingLog::newest()
{
	return buf2isOldest
		? buf1
		: buf2
		;
}



//#pragma mark - Thread -


RingLog::LogThread::LogThread( Genode::Env & env, RingLog & ring )
:	Thread( env, "ring_logger", 1 * 4096 )
	,tearDown( false )
	,conTimer( env )
	,ringLog( ring )
{
}

void RingLog::LogThread::entry()
{
	while( false == tearDown )
	{
		// do not engage in a continuous sleep of e.g. 5 seconds,
		// which at tear-down time would delay tear down by as much,
		// instead decompose the sleep period into slices of 0.1 seconds
		// (todo: would be better to just use Timer signals ?)
		for( int i = 0; i < 10; i++ )
		{
			conTimer.msleep( SLEEP * 1000 / 10 );
			if( tearDown )
				//
				return;
		}
		
		ringLog.Dump();
	}
}


