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

// libc.lib.so
#include <stdio.h>

// haiku.lib.so
#include <storage/Entry.h>
#include <storage/Path.h>
#include <support/StringList.h>

// this
#include "_Ge_InitPilot.h"

/******/
extern Genode::Env & HogEnv();


/**************/
static int debug = 2;  // set _2_ to list children, set _3_ to dump their config XML



///FIXME-2: listen to init reports:
// use Attached_rom_dataspace... with "polling" in MsgRcv() for now ? and use sigh(andler) later, but in a separate thread of course (separate Genode native signals)
/*
test/init/main.cc:
	Attached_rom_dataspace _init_state { _env, "state" };

Then look for attribute "exited=.." (with a status code) :
	<child name="/apps/Pulse" binary="/apps/Pulse" version="1" exited="0">
*/

///ToDo-2: nano3d etc are NOT displayed in Deskbar : handle display (and quitting) of Genode apps (nano3d, vnc_server, ftpd...), hybrid-style, by creating special menu items from the Dynit report ...?



struct hog::Pilot::Dynit_Child
{
public:  // Code
	/*
	Dynit_Child()
	:	argStrings()
		,overrideConfigXml()
	{
	}
	*/
public:  // Data
	BStringList argStrings;
	BString overrideConfigXml;
	int32 ramQuotaMb;
	//BStringList environs...
	//int launch_iteration;  // aka "version"
};



//#pragma mark - base class


hog::Pilot::Pilot()
:	childrenParams()
{
}

hog::Pilot::~Pilot()
{
	for( auto it = childrenParams.begin(); it != childrenParams.end(); it++ )
		delete it->second;
	;
	childrenParams.clear();
}


bool hog::Pilot::Toggle( const BStringList & arg_list )
{
	if( debug )
		printf( "Pilot.Toggle <%s>\n", arg_list.First().String() );
	
	if( arg_list.CountStrings() <= 0 || arg_list.First().IsEmpty() )
		return false;//B_BAD_DATA;
	//if( access(argv[0], 0777) == failure )
	//	return false;//B_NO_SUCH_FILE;
	
	// T0 : this.state
	auto existing = childrenParams.find( arg_list.First()/**/ );
	if( existing == childrenParams.end() )
	{
		// insert
		childrenParams[ arg_list.First()/**/ ] = new Dynit_Child { arg_list, "", 0 };
	}
	else
	{
		// remove
		delete existing->second;
		childrenParams.erase( existing );
	}
	
	// T+ : sync Init from this.state
	genAndSendConf();
	
	return true;///
}


bool hog::Pilot::Toggle_Xml( BString exe_path, BString xml_config, int32 ram_quota_MB )
{
	if( debug >= 1 )
		printf( "Pilot.Toggle_Xml <%s> %d MB [%s]\n", exe_path.String(), ram_quota_MB, xml_config.String() );
	
	if( exe_path.IsEmpty() )
		return false;
	
	auto existing = childrenParams.find( exe_path );
	if( existing == childrenParams.end() )
	{
		BStringList arg_list;
		arg_list.Add( exe_path );
		
		childrenParams[ exe_path ] = new Dynit_Child { arg_list, xml_config, ram_quota_MB };
	}
	else
	{
		delete existing->second;
		childrenParams.erase( existing );
	}
	
	genAndSendConf();
	
	return true;
}




//#pragma mark - subclass for non-packaged (Be or Ge) apps


hog::InitPilot::InitPilot()
:	Pilot()
//done, setting 128 KB now... Resolved? //FIXME-2: pass a value to fourth arg (instead of letting it default to default val), pass a 'big' value:
	,dynTx( HogEnv(), "config", "dynit_config", 128*1024 )  // using "dynit_config" instead of "config" for clarity, so set "label=dynit_config":
{
#if 0
		use an ::Expanding_reporter !
		..
	Genode::Reporter::Xml_generator xml( dyn, [&] ()
	{
		xml.attribute( "start", "top" );
		etc
	});
	*/
#endif
}



BString hog::InitPilot::genHeader()
{
	BString s;
	s
	<< "<config verbose=\"yes\" prio_levels=\"4\">\n"
// (see: genode.org/documentation/genode-foundations/23.05/system_configuration/The_init_component.html#State_reporting )
///FIXME-2: reverted to "NO" requested, otherwise I get errors: "[dynit] Error: state report exceeds maximum size" .. maybe here too it needs an "expanding reporter" ?
	<< "	<report requested=\"no\"/> <!-- report app exits/crashes to dynit_report_rom -->\n"
	<< "	<!-- These should be understood as 'surroundings-provide', or 'siblings-provide'... -->\n"
	<< "	<parent-provides>\n"
	<< "		<service name=\"PD\"/>\n"
	<< "		<service name=\"RM\"/>\n"   // necessary for lxip.lib.so
	<< "		<service name=\"Nic\"/>\n"  // necessary for lxip.lib.so
	<< "		<service name=\"CPU\"/>\n"
	<< "		<service name=\"LOG\"/>\n"
	<< "		<service name=\"ROM\"/>\n"
	<< "		<service name=\"Timer\"/>\n"
	<< "		<service name=\"Rtc\"/>\n"
	<< "		<service name=\"TRACE\"/>  <!-- for 'top' -->\n"
	<< "		<service name=\"Report\"/>\n"  // for GeDepot to report "installation"
	<< "		<service name=\"Block\"/>\n"   // for part_block (launched by mount_daemon/registrar)
	<< "		<service name=\"File_system\"/>\n"
	<< "		<service name=\"Hai_Broker\"/>\n"
	<< "		<service name=\"Gui\"/>\n"
	<< "		<service name=\"Audio_out\"/>\n"
	<< "		<service name=\"Play\"/>\n"
	<< "	</parent-provides>\n"
	<< "	<default-route>\n"
				// Careful with "blanket" routing, must not mess up basic ROM access
				// otherwise that prevents routing to even "ld.lib.so", the very
				// first needed library, which triggers a NULL-ptr crash in dyn/init's spawn...
					//	[init -> dyn] Error: binary is not an ELF
					//	no RM attachment (READ pf_addr=0x0 pf_ip=0x0 from pager_object: pd='init -> dyn -> top' thread='top') 
				// Anyway here in the "leaf" configuration node, we just forward to (parent) dyn/init,
				// and *it* will take care to route e.g. "libc.lib.so" to normal ROM routing,
				// route e.g. "Mandelbrot" app routing to fs_rom, and route everything else as usual.
	<< "		<any-service> <parent/> </any-service>\n"
	<< "	</default-route>\n"
//	<< "	<default caps=\"300\"/>\n"  // AutoCast needs more than 300...:
	<< "	<default caps=\"500\"/>\n"
		;
	
	return s;
}

void hog::InitPilot::genAndSendConf()
{
	// T-
	dynTx.enabled( true );
	dynTx.clear();
	
	BString s;
	
	// header
	s += genHeader();
	
	// children
	for( auto it = childrenParams.begin(); it != childrenParams.end(); it++ )
	{
		const auto item = it->second;
		
		s += genOneChildConf( item->argStrings, item->overrideConfigXml, item->ramQuotaMb );
		
		if( debug >= 2 )
			printf("   gen child <%s>\n", it->first.String() );
	}
	
	// footer
	s += "</config>\n";
	
	if( debug >= 3 )
		printf( "InitPilot.genConf:\n%s\n", s.String() );
	
	// T+ : Tx to Genode 'init'
	dynTx.report( s, s.Length() );
}



BString hog::InitPilot::genOneChildConf( const BStringList & arg_list, const BString & xml_conf, int ram_quota_megs )
{
	// Handle *one* sub-process' configuration.
	
	///ToDo-2: stripping /boot prefix: does this make sense ? The reasoning is, fs_rom forwards the path as-is to $vfs_server_name (e.g. vfs_ram), so e.g. it requests "/boot/bin/top" to it, WITHOUT STRIPPING the "/boot" prefix... So we need to remove the prefix here (?), this gets us going with the "nano3d" and "top" examples, even WITH the /boot prefix.
	BString stripped_arg_zero;
	stripped_arg_zero = arg_list.StringAt( 0 );
	if( stripped_arg_zero.StartsWith("/boot") )
	{
		stripped_arg_zero.RemoveFirst( "/boot" );
	}
	else
		printf( "*** warning: initpilot.genOneChild(): argv does not start with /boot prefix: <%s>\n", stripped_arg_zero.String() );
	
	const int ver = 1;//xx delete 'version' stuff? We only handle quit-then-restart, not in-situ restarting, so no use for this it seems
	int priority = -1;
	int ram_megs = ram_quota_megs > 0 ? ram_quota_megs : 128;  // default to 128 MB RAM quota
	BString s;
	
	s
	<< "	<start name=\"" << "dyn/" << BPath(stripped_arg_zero).Leaf() << "\" version=\"" << ver << "\"  priority=\"" << priority << "\" >\n"  // -1 or -2 priority helps much for "gears OpenGL", but also with MMD..?
						// eg "dyn/Pulse" instead of "/system/apps/Pulse" for log readability
	<< "		<resource name=\"RAM\" quantum=\"" << ram_megs << "M\"/>\n"
	<< "		<binary name=\"" << stripped_arg_zero << "\"/>\n"
		;
	
	///later: get rid of the second clause? or still needed for running nano3d directly (sans depot_deploy) ?
	if( stripped_arg_zero.StartsWith("/apps/genode") || stripped_arg_zero.StartsWith("/depot") )
	{
		// Genode apps (nano3d etc) should route "Gui" to WM:
		// re-label Gui (to use MOTIF window manager Gui instead of raw nitpicker Gui)
		s
		<< "		<route>\n"
		<< "			<service name=\"Gui\"> <parent label=\"ge_wm_gui\"/> </service>\n"  // dynit parent knows how to route "ge_wm_gui"
			;
		///hack: support PartitionsSetupGPT's use of mkntfs (it should look for Blocks in part_block, not call up to ahci_drv):
		if( stripped_arg_zero.EndsWith("/mkntfs") )
		{
			s << "			<any-service> <child name=\"dyn/part_block\"/> <parent/> </any-service>\n" ;
		}
		else
		{
			s << "			<any-service> <parent/> </any-service>\n" ;
		}
		s
		<< "		</route>\n"
			;
				///ToDo: the label "ge_wm_gui" ends up in the window title bar, in stead of eg "nano3d" title, how come ?
	}
	
	if( xml_conf.Length() )
	{
		s += xml_conf;
		s += "\n";
			// Context: there are Genode apps like e.g.
			// - gpt_write
			// - hog_ftpd
			// that require a config-style param instead of
			// argv/argc-style params, and yet they cannot be launched in the "depot_deploy" init
			// as neither their binary nor their runtime file are hosted in the filesystem, so
			// the runtime/config must be passed inline, and the binary loaded with
			// "start name=foo" instead of "start pkg=foo".
			// For those, one may call this gen() to spawn them in this init (instead of
			// the depot_deploy init).
	}
	else  // default to a "Be-like" <config> node:
	{
		s
		<< "		<config ld_verbose=\"yes\">\n"
		<< "			<libc  socket=\"/socket\" rtc=\"/dev/rtc\" stdin=\"/dev/null\" stdout=\"/dev/log\" stderr=\"/dev/log\">\n"
		<< "				<pthread placement=\"all-cpus\" verbose=\"no\" />\n"
		<< "			</libc>\n"
		<< "			<env key=\"_\" value=\"" << arg_list.First() << "\"/> \n"  // e.g. "/boot/apps/Pulse"
			;
		for( int i = 0 ; i < arg_list.CountStrings(); i++ )
		{
			// e.g. "/boot/apps/Pulse"
			s << "			<arg value=\"" << arg_list.StringAt(i) << "\"/>\n" ;
		}
		
		s
		<< "			<vfs>\n"
		<< "				<dir name=\"dev\">\n"
		<< "					<rtc/>\n"
		<< "					<log/>\n"
		<< "					<null/>\n"
		<< "					<fs label=\"downstream\"/>\n"  // access "/dev/bro2app" (broker-to-app traffic)
	#if 0  // enable this if using vfs_oss.lib.so OSS audio instead of 'native' audio output:
		<< "					<oss name=\"dsp\"/>/n"
	#endif
		<< "				</dir>\n"
		<< "				<dir name=\"boot\">\n"
		<< "					<fs label=\"part_boot\"/>\n"  // access "/boot"
		<< "				</dir>\n"
	//	<< "				<dir name=\"boot2\">\n"
	//	<< "					<fs label=\"part_boot2\"/>\n"  // access "/boot2"
	//	<< "				</dir>\n"
			;
		if( stripped_arg_zero.FindFirst("FtpPositive") >= 0 ) ///ToDo-2: hackish...
		{
			s
			<< "				<dir name=\"socket\">\n"
			<< "					<lwip dhcp=\"yes\"/>  <!-- Get an IP assigned by nic_router -->\n"
			<< "				</dir>\n"
				;
		}
		
		s
		<< "			</vfs>\n"
		<< "		</config>\n"
			;
	}
	
	s
	<< "	</start>\n"
		;
	
	return s;
}




//#pragma mark - subclass for depot-packaged (Genode) apps


#include "_Ge_DeployPilot.h"


hog::DeployPilot::DeployPilot()
:	Pilot()
	,depotDeployTx( HogEnv(), "config", "depotdeploy.config" )  // using "depotdeploy.config" instead of "config" for clarity, so set "label=depotdeploy.config":
{
}


void hog::DeployPilot::genAndSendConf()
{
	// T-
	//printf("  depotdeployt-report-Tx\n");///we may remain blocked on this call if the target component fails to start (for lack of RAM quota etc)
	depotDeployTx.enabled( true );
	depotDeployTx.clear();
	
	BString s;
	
	// header
	s
		<<	"	<config arch=\"x86_64\" prio_levels=\"4\">\n"
		<<	"							<report state=\"yes\"/>\n" ///
		<<	"		<static>\n"
		<<	"			<parent-provides>\n"
		<<	"				<service name=\"ROM\"/>\n"
		<<	"				<service name=\"Report\"/>\n"  // for clipboard etc
		<<	"				<service name=\"CPU\"/>\n"
		<<	"				<service name=\"PD\"/>\n"
		<<	"				<service name=\"RM\"/>\n"  // for mesa_gpu-cpu
		<<	"				<service name=\"LOG\"/>\n"
		<<	"				<service name=\"Timer\"/>\n"
		<<	"				<service name=\"Gui\"/>\n"
		<< "				<service name=\"Rtc\"/>\n"  // for Falkon (https certificates)
		<<	"				<service name=\"Nic\"/>\n"  // for Falkon etc
		<<	"				<service name=\"Audio_out\"/>\n"  // for media etc
		<<	"				<service name=\"Audio_in\"/>\n"
		<<	"				<service name=\"Play\"/>\n"  // for media etc
		<<	"				<service name=\"Record\"/>\n"
		<<	"				<service name=\"File_system\"/>\n"
		<<	"				<service name=\"Capture\"/>\n"  // for VNC server
		<<	"				<service name=\"Event\"/>\n"  // for VNC server
		<<	"			</parent-provides>\n"
		<<	"		</static>\n"
		<<	"		<common_routes>\n"
			// <!-- the exe and libs (except ldso) are *not* in this hand-crafted section here, they -->
			// <!-- appear later in the depot_deploy crafted section, with full paths, retrieved via cached_fs_rom -->
		<<	"			<service name=\"ROM\" label_last=\"ld.lib.so\"> <parent/> </service>\n"
		<<	"			<service name=\"ROM\" label_last=\"init\">      <parent/> </service>\n"
//Wrong routing! This gets routed to cached_fs_rom (??), and makes it crash on "invalid file handle":
//		<<	"			<service name=\"ROM\"    label_last=\"clipboard\"> <parent/> </service>\n"
//		<<	"			<service name=\"Report\" label_last=\"clipboard\"> <parent/> </service>\n"
		<<	"			<service name=\"Report\" label_last=\"shape\">     <parent/> </service>\n"
		<<	"			<service name=\"CPU\">   <parent/> </service>\n"
		<<	"			<service name=\"PD\">    <parent/> </service>\n"
		<<	"			<service name=\"RM\">    <parent/> </service>\n"  // for mesa_gpu-cpu
		<<	"			<service name=\"LOG\">   <parent/> </service>\n"
		<<	"			<service name=\"Timer\"> <parent/> </service>\n"
		<<	"			<service name=\"Gui\">   <parent/> </service>\n"
		<<	"			<service name=\"Rtc\">   <parent/> </service>\n"  // for Falkon (https certificates)
		<<	"			<service name=\"Nic\">   <parent/> </service>\n"  // for Falkon etc
		<<	"			<service name=\"Audio_out\">	<parent/> </service>\n"  // for media etc
		<<	"			<service name=\"Audio_in\">		<parent/> </service>\n"
		<<	"			<service name=\"Play\">			<parent/> </service>\n"  // for media etc
		<<	"			<service name=\"Record\">		<parent/> </service>\n"
///later: ram_fs for Falkon etc: this should not be in "common" routes, should either be user-configurable (à la Sculpt), or have a "sub runtime" of some sort
///later: Capture should not be in "common" routes (thus enabled for Falkon et al), just hardcode it for vnc_server...
		<<	"			<service name=\"File_system\" label=\"config\">		<parent label=\"ram_fs\"/> </service> <!-- For Falkon --> \n"
		<<	"			<service name=\"File_system\" label=\"downloads\">	<parent label=\"ram_fs\"/> </service> <!-- For Falkon --> \n"
		<<	"			<service name=\"File_system\" label=\"rw\"> <parent label=\"ram_fs\"/> </service> <!-- For qt5_TextEdit --> \n"
		<<	"			<service name=\"Capture\">	<parent/> </service>\n"  // for VNC server
		<<	"			<service name=\"Event\">	<parent/> </service>\n"  // for VNC server
		<<	"		</common_routes>\n"
		;
	
	// child #1 (hardcoded)
	//
	// Mesa is required for Qt apps, and aliased either to software pipe (mesa_gpu-softpipe.lib.so) or Intel GPU pipe (mesa_gpu_drv.lib.so). We use the software pipe for now.
	// -> first child (required by qt5 apps, so might as well enable it always)
	// (will only work if downloaded from GeDepot though)
	//
	BString hardcoded_child_mesa;
	if( false==childrenParams.empty() )
	{
		const char mesa_pkg[] = "genodelabs/pkg/mesa_gpu-cpu/2023-04-25";
		
		hardcoded_child_mesa
			<< "	<start name=\"" << "depot:" << "mesa_gpu-cpu" << "\"  pkg=\"" << mesa_pkg << "\"  priority=\"" << 0 << "\"  />\n"
			;
	}
	s += hardcoded_child_mesa;
	
	// children #2..n (dynamic)
	//
	for( auto it = childrenParams.begin(); it != childrenParams.end(); it++ )
	{
		///later: <ramQuotaMb> is currently *not* passed to gen(). The pkg runtime does specify a RAM quota... But it also provides for overriding(?) that quota, and we could take advantage of that by applying the client-provided <ramQuotaMb> ?
		BString child = genOneChildConf( it->second->argStrings, it->second->overrideConfigXml );
		
		s += child;
		
		if( debug >= 2 )
			printf("   gen child <%s>\n", it->first.String() );
	}
	
	// footer
	s << "</config>\n";
	
	if( debug >= 3 )
		printf( "DepotDeploy.genDeployConf:\n%s\n", s.String() );
	
	// T+ : Tx to 'init'
	depotDeployTx.report( s, s.Length() );
}


BString hog::DeployPilot::genOneChildConf( const BStringList & arg_list, const BString & xml_conf )
{
	BString s;
	
	int priority = -2;  // We need at least -1 (-2 is better) : think e.g. CPU-hungry GL-Gears in DeployPilot, versus AC in InitPilot...
	BString stripped_arg0;
	stripped_arg0 = arg_list.StringAt( 0 );  // e.g. "/boot/depot/genodelabs/pkg/nano3d/2023-11-24/runtime"
	
	if( stripped_arg0.StartsWith("/boot/depot/") )
		stripped_arg0.RemoveFirst( "/boot/depot/" );  // e.g. "genodelabs/pkg/nano3d/2023-11-24/runtime"
	else
		printf( "*** warning: deploypilot.genOneChildConf(): argv does not start with /boot/depot prefix: <%s>\n", stripped_arg0.String() );
	
	if( stripped_arg0.EndsWith("/runtime") )
		stripped_arg0.RemoveLast( "/runtime" );  // e.g. "genodelabs/pkg/nano3d/2023-11-24"
	else
		printf("*** warning: deploy: argument is not a 'runtime': <%s>\n", stripped_arg0.String());
	
	BString appname;
	{
		BStringList tokens;
		stripped_arg0.Split( "/", true, tokens );
		
		appname = tokens.StringAt( 2 );  // e.g.  genodelabs/pkg/[ nano3d ]/2023-11-24
	}
	
	s
		<< "	<start name=\"" << "depot:" << appname << "\" pkg=\"" << stripped_arg0 << "\"  priority=\"" << priority << "\" >\n"  // -1 or -2 priority helps much for "gears OpenGL", but not only...
///later: make this <route> conditional, with "if runtime explicitely requires this...".. Of course not on 'xml_conf', which holds the <config> node instead of the <route> node, but with a similar param ?
		<< "		<route>\n"
		<< "			<service name=\"ROM\" label_last=\"mesa_gpu_drv.lib.so\"> <any-child/> </service>\n"
		<< "		</route>\n"
		<< "	</start>\n"
		;
	
	return s;
}


