module DynamicLinker;

from StdReal import entier; // RWS marker

import DLState;
import DynamicLink;
from pdRequest import ParseCommandLine;
import Request;
import encode_dynamic;
import decode_dynamic;
import directory_structure;
import ExtFile;
import ArgEnv;
import StdEnv;
import ExtString;
import LinkerMessages;
import DefaultElem;
import ProcessSerialNumber;
import pdExtFile;
import State;

Start :: *World -> *World;
Start world
	| is_first_instance
		#! start_state = default_elemU;
		#! (start_state,world)
			= init_io2 start_state world

		#! (start_state,world)
			= dynamic_linker_message_loop start_state world;
		with {
			dynamic_linker_message_loop start_state=:{quit_server} world
				| quit_server
					= (start_state,world);
				#! (start_state,world)
					= any_clients_left (receive_and_handle_dynamic_linker_message start_state world);
				= dynamic_linker_message_loop start_state world
		}
		= world;
				
		#! command_line = getCommandLine;
		#! s_command_line = size command_line;
		#! new_cmd_line = encode_command_line [ c \\ c <-: command_line];
		#! new_cmd_line = new_cmd_line % (1,dec (size new_cmd_line));
		| PassCommandLine (new_cmd_line +++ "\0")
			= world
			= abort ("error executing " +++ new_cmd_line);
where {
	quote_and_add_arg :: !Int !String !String -> String;
	quote_and_add_arg i arg s
		# s = if (i == 0) s (s +++ " ");
		# s
			= case (arg.[0] <> '\"' && (fst (CharIndex arg 0 ' ')) ) of {
				True
					// string should have been quoted
					-> s +++ "\"" +++ arg +++ "\"";
				_
					-> s +++ arg;
			};
		= s;

	init_io2 :: !*DLServerState !*f -> (!*DLServerState, !*f) | FileEnv f & FileSystem f;
	init_io2 s io
		// no arguments?
		# cmd_line = getCommandLine;

		| size cmd_line <= 1
			= abort "DynamicLinker needs an argument";

		// compatibility mode
		# option = cmd_line.[1];
		| (size cmd_line == 2) && ((option == "/W") || (option  == "/w"))
			# project_name
				= cmd_line.[2];

			// read environments
			# application_path = (ParseCommandLine GetDynamicLinkerPath).[0];

			# (sep_found,sep_index)
				= CharIndexBackwards application_path (size application_path - 1) path_separator;
			| not sep_found
				= abort ("could not read IDEEnvs");
			
			# application_path = application_path % (0,dec sep_index);
				
			# s = { s &
					application_path				= application_path
				,	static_application_as_client	= (option == "/W") || (option  == "/w")
				};
			= InitServerState s io;
			
			#! dynamic_linker_dir
				= fst (ExtractPathAndFile cmd_line.[0]);
			# s = { s &
					dlss_lib_mode			= True
				,	dlss_lib_command_line	= cmd_line
				, 	application_path		= dynamic_linker_dir
				};
			# (s,io)
				= InitServerState s io;
			# (_,io)
				= ds_create_directory DS_SYSTEM_DYNAMICS_DIR dynamic_linker_dir io;
			= (s,io);
	where {
		build_cmdline_in_addclient_format :: !Int !Int {{#Char}} -> {#Char};
		build_cmdline_in_addclient_format i limit cmd_line
			| i == limit
				= "";
				= cmd_line.[i] +++ (if (i == (dec limit)) "" " ") +++ (build_cmdline_in_addclient_format (inc i) limit cmd_line);
	};
}

// windows specific
dummy s io
	= (s,io);
dummy_ignore_arg _ s io
	= (s,io);

receive_and_handle_dynamic_linker_message :: !*DLServerState !*f -> *(*DLServerState,!*f) | FileEnv, FileSystem f;
receive_and_handle_dynamic_linker_message s=:{dlss_lib_mode=True,dlss_lib_command_line} io
	// matches only when there is no other dynamic rts running
	# s = { s & dlss_lib_mode	= False };
	#! (timeout,_,_)
		= ReceiveReqWithTimeOutE True;
	| timeout || not timeout
		# (remove_state,client_id,s,io)
			= (AddClient3 dummy_ignore_arg DefaultProcessSerialNumber [ arg \\ arg <-: dlss_lib_command_line] s io)
		= handle_request_result dummy dummy_ignore_arg (remove_state,client_id,s,io);

receive_and_handle_dynamic_linker_message s=:{static_application_as_client} io
	#! (timeout,client_id,request_name)
		= ReceiveReqWithTimeOutE static_application_as_client;
	| timeout
		= (s,io);

	#! s = { s & static_application_as_client = False };
	#! requests = filter (\(_,name,_) -> (fst (starts name request_name))) RequestList;
	| (length requests) == 1
		// extract arguments and execute request
		#! request = hd requests;
		#! request_args
			= case (fst3 request) of {
				True
					-> tl (ExtractArguments '\n' 0 request_name []);
				False
					#! index = size (snd3 request);
					-> [request_name % (index, size request_name - 1)];
			};
		
		// do request
		#! (remove_state,client_id,s,io)
			= (thd3 (hd requests)) client_id request_args s io;
		= handle_request_result dummy dummy_ignore_arg (remove_state,client_id,s,io);
			
		= (s,io);
where {
	AddAndInitPC1_ client_id args s io
		#! (x,t)
			= AddAndInitPC_ client_id args s io;
		= t;
		
	// If requests have common prefixes, then the first request with the common prefix is used.
	RequestList
		= [
			// eagerly linked applications
			(True,"AddAndInit",AddAndInitPC1_)
			// compute address descriptor table using the descriptor usage set
		,	(False,"Compute2DescAddressTable",LinkPartition)
			// get address of the graph to string function
		,	(False,"GetGraphToStringFunction",HandleGetGraphToStringMessage)
			// closing client
		,	(True,"Close",Close)
			// general
		,	(True,"Quit",Quit)
			// send by second or later instance of dynamic rts to first instance of dynamic rts
		,	(False,"MessageFromSecondOrLaterLinker",MessageFromSecondOrLaterLinker_ (\_ s io -> (s,io)) )
			// send to get extra dynamic rts information
		,	(False,"GetDynamicRTSInfo",HandleGetDynamicRTSInfoMessage)
			// Get Type definitions
		,	(True,"LibInit",LoadApplication)		
			// dumpDynamic is the caller
		,	(False,"DumpDynamic",DumpDynamic)		
			// adding addresses
		,	(False,"GetLabelAddresses",GetLabelAddresses)
			// register lazy dynamic
		,	(False,"RegisterLazyDynamic",HandleRegisterLazyDynamicMessage)
			// dumpDynamic is the caller
		,	(False,"GetDynamicLinkerDir",GetDynamicLinkerDir)
		];
}

	any_clients_left (s=:{quit_server},io)
		// update window
		#! (no_more_clients,s)
			= acc_dl_client_states is_empty s;
		#! (static_application_as_client,s)
			= s!static_application_as_client;
		| (not no_more_clients || static_application_as_client) && (not quit_server)
			= (s,io);
			= ({ s & quit_server = True},io);

	where {
		is_empty []
			= (True,[]);
		is_empty l
			= (False,l);
	};

handle_request_result :: !.(*DLServerState -> .(.a -> *(*DLServerState,.b))) .(*DLClientState -> .(*DLServerState -> .(.b -> *(*DLServerState,.b)))) !*(!.Bool,!ProcessSerialNumber,!*DLServerState,.a) -> *(*DLServerState,.b);
handle_request_result callback_before_remove_dl_client_state callback_after_remove_dl_client_state (remove_state,client_id,s,io)
	// platform independent ...; check for errors
	#! ((messages,ok),s)
		= selacc_app_linker_state client_id get_error_and_messages s;
		
	// update client windows	
	// als window nog niet geopened, dan openen
	#! (s,io)
		= callback_before_remove_dl_client_state s io;

	// remove client if necessary
	#! (s,io)
		= case remove_state of {
			True
				#! (_,removed_dl_client_state,s)
					= RemoveFromDLServerState client_id s;
				#! (s,io)
					= callback_after_remove_dl_client_state removed_dl_client_state s io;
				-> (s,io);
			False
				-> (s,io);
		};
		
	// check for error fatal for client application
	| not ok
		# io
			= abort ("!kk"  +++ (pr_linker_message messages "")) //KillClient2 client_id io;
		= (s,io);
		
		= (s,io);
where {	
	get_error_and_messages state 
		#! (messages,state)
			= GetLinkerMessages state;		
		#! (ok,state)
			= IsErrorOccured state;
		= ((messages,ok),state);

	pr_linker_message [] s
		= s;
	pr_linker_message [LinkerError x:xs] s
		# new_s = "LinkerError:\t " +++ x +++ "\n";
		= pr_linker_message xs (s +++ new_s);
	pr_linker_message [LinkerWarning x:xs] s
		# new_s = "LinkerWarning:\t " +++ x  +++ "\n";
		= pr_linker_message xs (s +++ new_s);
	pr_linker_message [Verbose x:xs] s
		# new_s = "Verbose:\t " +++ x  +++ "\n";
		= pr_linker_message xs (s +++ new_s);
} // HandleRequestResult
