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

/*
Aliases:
	- bridge-genode-world-to-libc
	- replace-and-extend-posix.lib.so
	
Overview:
	This contains the "glue" for calling the libc-style main() from Genode-style Component::construct().
	Beside bridge/glue stuff,
	also look for snooze(), get_system_info() and related functions.
*/


// genode/base.lib.a
#include <base/log.h>  // Genode::log()
#include <base/sleep.h>  // Genode::sleep_forever()
#include <timer_session/connection.h>  // Timer::Connection

// libc.lib.so
#include <libc/args.h>  // Genode's populate_args_and_env()
#include <libc/component.h>  // Libc::Component::construct()
#include <stdio.h>
#include <string.h>

// haiku.lib.so
#include <OS.h>

// raw "extern"s
extern char ** environ;
extern "C" int main( int argc, char ** argv, char ** envp );  // provided by the Haiku application


/**************/
static int debug = 1;



//#pragma mark -


namespace hog {
	class LibRoot_client;
	/// + class Remote, or rather, class Broker_client !
}


class hog::LibRoot_client
{
	// Aliases:
	// - (InTheFuture,WillBe)ClientSideOfKernelServer
	// - LightKernelEnvOneInstancePerTeam
	
public:
		LibRoot_client( Genode::Env & env )
		:	genode_env( env )
			,timer( env )
			,deprecatedTimer( env )
		{
		}

public:  // Data (!)

		Genode::Env & genode_env;
		
		Timer::Connection timer;  // modern API, for calling curr_time()
		Timer::Connection deprecatedTimer;
			//    used for usleep(), which is deprecated API
			///+++ figure out how to use One_Shot_Timeout instead
};


/*********************** (used only here) */
static hog::LibRoot_client
	* haiku_libroot_ = NULL;

/********* (exported to api-etc.cpp) */
#include "kinda_serverside.h"  // just for HAS_BROKER ...
#if defined( HAS_BROKER )
#	include "RemoteBroker.h"
Remote * remote = NULL;
#endif


static void HoG_Init_( Genode::Env & env )
{
	if( haiku_libroot_ || remote )
	{
		Genode::error( "Do not call HoG_Init() twice!" );
		
		return;
	}
	
	haiku_libroot_ = new hog::LibRoot_client( env );
	
	// make the "broker" class member an instance (instead of *pointer*), so long as the "new Remote" is inside a try... catch() :
	try
	{
		remote = new Remote( env );
	}
	catch( ... )
	{
		Genode::log( "Broker not found: will operate with local-only kernel (no inter-app BMessage/port IPC)" );
	}
}



//#pragma mark - semi-public, used by BApplication etc -


Genode::Env & HogEnv()
{
	if( NULL == haiku_libroot_ /* || NULL == remote*/ )
	{
		Genode::error( "NULL env: you forgot to call HoG_Init()! Crashing now" );
	}
	
	return haiku_libroot_->genode_env;
}



//#pragma mark "bridge" stuff


static
int bridge( void * )
{
	// sanity check added after Genode 20.02, though I adapted my code accordingly so this should never happen
	if( nullptr == pthread_self() )
	{
		Genode::error( "pthread_self() returns NULL: this should never happen (any more), spawn_thread() now makes correct use of pthreads" );
		Genode::sleep_forever();
	}
	
	// no need for a "with_libc()" (already done in LibC's component_construct())
///ToDo: uh, we do not use component_construct(), we provide our own variant, so it should be up to that variant ?
	
	// follow the lead of libports/src/lib/posix/construct.cc:
	int argc = 0;
	char ** argv = nullptr;
	char ** envp = nullptr;
	populate_args_and_env(    (Libc::Env&)HogEnv(), argc, argv, envp ); ///
	environ = envp;
	
	//Genode::log( "argc=", argc, " argv[0]=", (const char*)argv[0], " envp[0]=", (const char*)envp[0] );
	
	int status =
		main( argc, argv, envp );
	
	// -- Tear Down --
	
	// sever "Hai_Broker" Session, and close() named pipe
	delete remote; remote = nullptr;
	
	// exit() with the return status, like posix.lib.so does (otherwise, e.g. a  media player continues playback even after the main window is closed)
	exit( status );
	return status;  // shut up compiler
}

static
void main_in_Haiku_thread()
{
	// main() needs to run in a Thread created
	// with spawn_thread(), instead of whatever Genode Thread is
	// calling Libc::Component::construct() by default.
	// Otherwise, a lot of subsequent Haiku calls to find_thread(NULL) would fail.
	
	if( debug )
		Genode::log( "haiku-on-genode: libc-style construct() --------" );
	
	resume_thread(
		spawn_thread( bridge, "main BApplication thread", 10, NULL )
		);
}


void Libc::Component::construct( Libc::Env & env )
{
	//Genode::log( "Libc::Component::construct" );
	
	// entry point when linking against Libc
	
	HoG_Init_( env );
	
	main_in_Haiku_thread();
}


//#pragma mark - Haiku API (OS.h)-


bigtime_t  system_time()
{
	if( debug >= 4 )
		Genode::log(
			__func__, ": ",
			haiku_libroot_->timer
				.curr_time()
					.trunc_to_plain_us()
						.value
			);
	
	return haiku_libroot_
		->timer
			.curr_time()
				.trunc_to_plain_us()
					.value;
}

status_t  snooze( bigtime_t duration )
{
	if( debug >= 4 )
		Genode::log( " ::snooze ", duration, " µs" );
	
	if( duration <= 0 )
		return B_OK;
	
	haiku_libroot_->deprecatedTimer
		.usleep( duration );
	
	return B_OK;
}



status_t  snooze_until(bigtime_t time, int timeBase)
{
	///++++ genode.timer..
	/*
	something like this from libc/main.cc? :
			if (timeout_ms > 0)
				_main_timeout.timeout(timeout_ms);
	*/
	
	if( debug >= 2 )
		Genode::log( "  ::snooze_until ", time, " (base: ", timeBase, ") -> temp impl." );
	
	return snooze(time - system_time());
}


status_t _get_team_info(team_id id, team_info * /*info*/, size_t /*size*/)
{
///later: this is called by RosterAppInfo::IsRunning(), but seems low priority; could implement by querying Broker
	//Genode::warning( __func__, " team: ", id );
	
	return -1;
}



//#pragma mark - for apps/Pulse


status_t  get_system_info( system_info * info)
{
	if( nullptr == info )
		return B_BAD_VALUE;
	
	info->cpu_count =
			HogEnv().cpu().affinity_space().total();
	
	///later-maybe: use this, from virtualbox5/thread.cc ? :
	/*
		Genode::Attached_rom_dataspace const platform(genode_env(), "platform_info");
		Genode::Xml_node const kernel = platform.xml().sub_node("kernel");
		Genode::log( kernel.attribute_value("name", Genode::String<16>("unknown")) ); //return kernel.attribute_value("name", Genode::String<16>("unknown")) == "nova";
	*/
	strcpy( info->kernel_name, "haiku-on-genode v0" );
	strcpy( info->kernel_build_date, "_date: " __DATE__ );
	strcpy( info->kernel_build_time, "_time: " __TIME__ );
	info->kernel_version = 1;
	info->abi = 0;
	
	return B_OK;
}


status_t  get_cpuid(cpuid_info *, uint32 , uint32 )
{
	return B_ERROR;
}

status_t  get_cpu_topology_info(cpu_topology_node_info* /*topologyInfos*/,
						uint32* topologyInfoCount)
{
	if( debug >= 3 )
		Genode::warning( "get_cpu_topology_info: returning hardcoded _2_ cpu count");
	
	*topologyInfoCount =
		HogEnv().cpu().affinity_space().total();
	
	//+ fill out <topologyInfos> with mock info: Intel ..etc
	
	return B_OK;
}


// see haiku/src/system/kernel/system_info.cpp :
status_t  get_cpu_info(uint32 firstCPU, uint32 cpuCount, cpu_info* info)
{
	// 1 emulated CPU performs better in QEMU..?
	// nope, Mandelbrot seems as slow or slower then (45 secs)..
	// so let's hardcode 2 CPUs for grins and giggles
	const int smp_get_num_cpus = 2;///smp_get_num_cpus();
///ToDo: just use HogEnv().cpu().affinity_space().total !
	
	if (firstCPU >= (uint32)smp_get_num_cpus)
		return B_BAD_VALUE;
	if (cpuCount == 0)
		return B_OK;

	uint32 count = Genode::min(cpuCount, smp_get_num_cpus - firstCPU);

	memset(info, 0, sizeof(cpu_info) * count);
	for (uint32 i = 0; i < count; i++) {
		info[i].active_time = 0;///cpu_get_active_time(firstCPU + i);
		info[i].enabled = true;///!gCPU[firstCPU + i].disabled;
	}

	return B_OK;
}



//#pragma mark - for registrar, via apps/Deskbar


// genode base.a
#include <os/reporter.h>

#if 1  //
extern "C" {

///ToDo: this could be moved to a subfolder like "system/kernel/shutdown.cpp" like upstream Haiku does

status_t _kern_shutdown( bool reboot )
{
	Genode::log( "==> _kern_shutdown( reboot=", reboot, " ) !" );
	
	// "Report" service will only work if these requirements are met ;
	// Tx side:
	//		for the sender app to Tx the report, its runtime config must target/route it to the proper report_rom, e.g.
	//			<service name=\"Report\" label=\"system\"> <child name=\"some_report_rom\"/> </service>
	// Rx side:
	//		for the report_rom to rebroadcast (in ROM form) to the recipient (e.g. acpica), the report_rom's runtime config must allow a "sender / report-name" pair, e.g.
	//			<policy label_prefix=\"acpica\" report=\"registrar -> system\"/>
	//
	static// must *keep* the reporter around, for acpica to consider its report (if the connection is severed, acpica won't read us)
		Genode::Reporter reporter { HogEnv(), "system" };//, "system", 4*1024 };
	///later: Shutdown "static" report object is not MT safe, fix-up to properly handle the (unlikely) case of concurrent calls...
	
	///ToDo: that Genode::Reporter seems to *raise an exception*, bringing down the whole registrar, if _kern_suspend_to_ram() has been called before _kern_shutdown()
	// this is reproducible with "jam deskbar.run", now that ps/2 & vesa are embedded within drivers_init,
	// and the exception is:
		// [init -> registrar] Error: Report-session creation failed (label="system", ram_quota=14K, cap_quota=3, buffer_size=4096)
		// [init -> registrar] debugger() called: Registrar::MessageReceived() caught unknown exception
		// [init -> registrar] Error: sleeping forever
	/// Generally speaking, surround the above with a try.. catch.. statement ?
	/// Maybe it's not possible to have *Two* Reporter objects to the same 'system' target in the same report_rom ? If so, move the "static" obj to higher scope
	
	// generate report
	reporter.enabled( true );
	reporter.clear();
	if( reboot )
		Genode::Reporter::Xml_generator xml( reporter, [&] ()
		{
			// This may be handled by pc_platform(_drv) or ps2(_drv) (or even acpica) : we follow the example of SculptOS and route to it for best compatibility (indeed, ps2 reset works on older thinkpads, unlike acpica).
			// REQUIRES: pc_platform(_drv)/ps2(_drv) must get 1) "system" ROM updates, and 2) "acpi" ROM updates, or risk failing silently to reset.
			xml.attribute( "state", "reset" );
		});
	else
		Genode::Reporter::Xml_generator xml( reporter, [&] ()
		{
			// This is handled by acpica.
			// REQUIRES: acpica must get "system" ROM updates.
			xml.attribute( "state", "poweroff" );
		});
	
	return B_OK;
}

}  // extern "C"


extern "C" {

status_t _kern_suspend_to_ram()
{
	Genode::log( "==> _kern_suspend_to_ram()" );
	
	///later: "Suspend" works in qemu, but not on metal... Dig into intel_fb(_drv)/platform/etc, or wait for newer Genode ?
	/*
		Status:
		- incomplete implementation, early experimental support only
		- disabled in _kapm_control_() -- change its return code to 0 to enable the feature, a "Suspend" menu item will show up in Deskbar.
		
		Details:
		This only works in Qemu. On bare metal, the computer does "suspend", but
		fails to "resume" completely. So with Genode 23.05 there's little room left for digging, and only so
		with intel-based laptops : better wait until newer releases "blaze the way".
		Once the feature does work with Vesa etc, will do further work on this, do proper pre-suspend shutdown
		of drivers-init with a report_rom (not with a dynamic_rom!) and post-resume
		reinitialization with a report_rom (also not with a dynamic_rom!) etc.
		
		Alexander B. at Genode Labs:
		> (..)
		> For Intel graphic devices we have a, in principal capable driver, to do 
		> the job, so intel_fb and intel_gpu works on some systems. By restarting 
		> them, they initialize the hardware again into a usable state.
		> In contrast, vesa_fb or boot_fb expects that someone (e.g. Bios/UEFI) 
		> did the job of setting up the hardware, which is not the case after ACPI 
		> resume (as far as I can tell).
		> (..)
		> So for now, I think you should consider this feature experimental, as 
		> you guessed already.
	*/
	
	static// must *keep* the reporter around, for acpica/test-suspend/etc to consider its report (if the connection is severed, they won't read us) ?
		Genode::Reporter reporter { HogEnv(), "system" };
	
	reporter.enabled( true );
	
	reporter.clear();
	Genode::Reporter::Xml_generator prepare( reporter, [&] ()
	{
		// This is handled by "acpica"
		prepare.attribute( "state", "s3_prepare" );
	});
	
	snooze( 3999000 ); ///
	
	reporter.clear();
	Genode::Reporter::Xml_generator suspend( reporter, [&] ()
	{
		// This is handled by the "test_suspend" component : it triggers ACPI S3 suspend via Pd::managing_system()
		suspend.attribute( "state", "suspend" );
	});
	
	snooze( 3999000 ); ///
	
	reporter.clear();
	Genode::Reporter::Xml_generator resume( reporter, [&] ()
	{
		resume.attribute( "state", "s3_resume" );
	});
	
	snooze( 3999000 ); ///
	
	reporter.clear();
	Genode::Reporter::Xml_generator online( reporter, [&] ()
	{
		online.attribute( "state", "online" );
	});
	
	return B_OK;
}

}  // extern "C"
#endif



//#pragma mark - little used stuff -


void beep()
{
}

status_t system_beep( const char * eventName )
{
	return -1;
}

status_t add_system_beep_event( const char* eventName, uint32 flags )
{
	return -1;
}

