implementation module UpdateObject;

/*
from StdMisc import abort;
from StdBool import not, ||, &&;
from StdInt import <;
import StdClass;
*/
import StdEnv;

import ExtString;
import ExtFile;

import DLState;

import target;
import State;
import UnknownModuleOrSymbol;
//1.3
from deltaIOState import FileEnv;
from deltaEventIO import IOState;
import Directory;

//3.1

/*2.0
from deltaIOState import class FileEnv, instance FileEnv (IOState s);
from StdLibMisc import ::Date(..), ::Time(..);
import Directory;
0.2*/

import pdUpdateObject;
//import DebugUtilities;


/*
	GenerateObject
	
	It is checked that the object module exists (as a separate object module or as part of a static library). Only
	object modules *without* extension (i.e. not having an .obj or .lib) are updated.
	
	Improvemtents:
	-	eliminating the Directory-module which requires conversions from strings to its own representation
		of paths which creates unnecessary garbage.
*/
GenerateObject :: !String !*State !*DLClientState !*DLServerState !(IOState s) -> !(!Bool,!String,[ModuleOrSymbolUnknown],[String],!*State,!*DLClientState,!*DLServerState,!(IOState s));
GenerateObject module_path_name state dl_client_state=:{target={target_vers,target_cgen}} dl_server_state=:{application_path} io
	#! (path_file,extension)
		= ExtractPathFileAndExtension module_path_name;
	// an .obj- or .lib-file
	| extension == "obj" || extension == "lib"
		//  check whether file exists	
		#! ((ok,obj_or_lib_file),io)
			= accFiles (pd_StringToPath module_path_name) io;
		| not ok
			= abort ("GenerateObject: internal error during path conversion");

		# ((obj_or_lib_error,_),io)
			= accFiles (getFileInfo obj_or_lib_file) io;
		= (obj_or_lib_error == NoDirError,module_path_name,[],[],state,dl_client_state,dl_server_state,io);
	| extension <> ""
		= abort ("GenObj3 (internal error): extension not permitted <" +++ extension +++ ">");

		// (re)create the file_name with the {.abc,.o}-extensions
		// collect .abc info
		#! abc_path_file
			= path_file +++ ".abc";
		#! ((ok,abc_file),io)
			= accFiles (pd_StringToPath abc_path_file) io;
		| /*F ("GenerateObject: " +++ abc_path_file)*/ not ok
			= abort ("DynamicLink (GenObj):  something went wrong during path conversion 1");

		// collect .o info
		#! o_path_file
			= path_file +++ ".o";
		#! ((ok,o_file),io)
			= accFiles (pd_StringToPath o_path_file) io;
		| /*F ("GenerateObject: " +++ o_path_file)*/ not ok
			= abort ("DynamicLink (GenObj):  something went wrong during path conversion 2");
			
		// get the file info
		# ((abc_dir_error,abc_file_info),io)
			= accFiles (getFileInfo abc_file) io;
		# ((o_dir_error,o_file_info),io)
			= accFiles (getFileInfo o_file) io;
				
		| (abc_dir_error == DoesntExist) && (o_dir_error == DoesntExist)
			// given path was invalid; pop up a dialogue
			= (False,"abc & o do not exists",[],[],state,dl_client_state,dl_server_state,io);
		| (abc_dir_error == DoesntExist) && (o_dir_error <> DoesntExist)
			// only .o exists; assume it to be up-to-date
			= (True,o_path_file,[],[],state,dl_client_state,dl_server_state,io);
			
		// .abc-file exists
		#! ((ok,offered_version,objs,libs,state),io)
			= accFiles (read_abc_info abc_path_file state) io;
		| not ok
			#! message
				= "failed to open '" +++ abc_path_file +++ "'";
			= (False,"1",[],[],AddMessage (LinkerWarning message) state,dl_client_state,dl_server_state,io);
			
		// check .abc-file version with required version
		| target_vers <> offered_version
			#! message
				= "incorrect version of file '" +++ abc_path_file +++ "'";
			= (False,"2",[],[],AddMessage (LinkerWarning message) state,dl_client_state,dl_server_state,io);

		// .abc-file with correct version number
		| (o_dir_error == DoesntExist)
			//  .o does *not* exist; generate a new .o
			= generate_abc_file module_path_name o_path_file objs libs state dl_client_state dl_server_state io;
		
		// both extensions of the file exist. is .o out of date?
		#! abc_datetime
			= abc_file_info.pi_fileInfo.lastModified;
		# o_datatime
			= o_file_info.pi_fileInfo.lastModified;
		#! (older,equal)
			= older_than_or_equal abc_datetime o_datatime;	
		| (not older && not equal)
			// .abc is newer as its corresponding .o
			= generate_abc_file module_path_name o_path_file objs libs state dl_client_state dl_server_state io;

			// .o is up-to-date 
			= (True,o_path_file,objs,libs,state,dl_client_state,dl_server_state,io);
where {
/*
	// winos
	generate_abc_file :: !String !String [ModuleOrSymbolUnknown] [String] !*State !*DLClientState !*DLServerState !(IOState s) -> !(!Bool,!String,[ModuleOrSymbolUnknown],[String],!*State,!*DLClientState,!*DLServerState,!(IOState s));
	generate_abc_file module_path_name o_path_file objs libs state dl_client_state=:{cgpath} dl_server_state=:{application_path} io
		#! cgpath_without_terminating_null
			= cgpath % (0,size cgpath - 2)
		#! cmdline
			= cgpath_without_terminating_null +++ " \"" +++ module_path_name +++ "\"\0";
		= (GenerateObjectFileOld cgpath cmdline,o_path_file,objs,libs,state,dl_client_state,dl_server_state,io);
*/

	read_abc_info :: !{#Char} !*State !*Files -> *((!Bool,!Int,[ModuleOrSymbolUnknown],[{#Char}],!*State),!*Files);
	read_abc_info abc_path_file state files
		// open .abc
		#! (ok,abc_file,files)
			= fopen abc_path_file FReadText files;
		| not ok
			#! message
				= "failed to open '" +++ abc_path_file +++ "'";
			= ((False,0,[],[],AddMessage (LinkerWarning message) state),files);
		
		// collect info
		#! (abc_file,version,objs,libs,state,files)
			= collect_abc_info abc_file 0 [] [] state files;
			
		// close .abc
		#! (_,files)
			= fclose abc_file files;
		= ((True,version,objs,libs,state),files);
	where {
//		(path_prefix,_)
//			= ExtractPathAndFile abc_path_file;
			
		collect_abc_info abc_file version objs libs state files
			#! (line,abc_file)
				= freadline abc_file;
				
			// reached end of info block?
			#! (found,i)
				= starts ".endinfo" line;
			| found
				= (abc_file,version,objs,libs,state,files);
	
			// version?
			#! (found,i_comp)
				= starts ".comp " line;
			| found
				#! (ok,space_index)
					= CharIndex line i_comp ' ';
				#! version_number
					= toInt (line % (i_comp,dec space_index));
				= collect_abc_info abc_file version_number objs libs state files;
			
			// imported object?
			#! (found,i_impobj)
				= starts ".impobj \"" line;
			| found
				#! obj
					= line % (i_impobj,size line - 3);
					= collect_abc_info abc_file version [ModuleUnknown (/*path_prefix +++ toString path_separator +++ */ strip_abc_and_o_extension obj) "":objs] libs state files;
	
			// imported (dynamic) library?
			#! (found,i_implib)
				= starts ".implib \"" line;
			| found
				#! lib
					= line % (i_implib,size line - 3);
				= collect_abc_info abc_file version objs [/*path_prefix +++ toString path_separator +++*/ lib:libs] state files;
	
				= collect_abc_info abc_file version objs libs state files;
	} // read_abc_info
}

//from Directory import ::Data`;

// macOS
//import CallCg;
class older_than_or_equal a	:: !a	!a	->	(!Bool,!Bool);

//1.3
instance older_than_or_equal Date`
where {	
	older_than_or_equal {year`=year1,month`=month1,day`=day1} {year`=year2,month`=month2,day`=day2}
//3.1
/*2.0
instance older_than_or_equal Date
where {	
	older_than_or_equal {year=year1,month=month1,day=day1} {year=year2,month=month2,day=day2}
0.2*/

		| year1 < year2 
				= (True,False);
				| year1 == year2
					| month1 < month2
						= (True,False);
						| month1 == month2
							| day1 < day2
								= (True,False);
								
								// data equal or bigger
								= (False, day1 == day2);
							// month1 > month2
							= (False,False);
					// year1 > year2
					= (False,False);
};

//1.3
instance older_than_or_equal Time`
where {
	older_than_or_equal {hours`=hours1,minutes`=minutes1,seconds`=seconds1} {hours`=hours2,minutes`=minutes2,seconds`=seconds2}
//3.1
/*2.0
instance older_than_or_equal Time
where {
	older_than_or_equal {hours=hours1,minutes=minutes1,seconds=seconds1} {hours=hours2,minutes=minutes2,seconds=seconds2}
0.2*/
		| hours1 < hours2 
			= (True,False);
			| hours1 == hours2
				| minutes1 < minutes2
					= (True,False);
					| minutes1 == minutes2
						| seconds1 < seconds2
							= (True,False);
							// time equal or bigger
							= (False, seconds1 == seconds2);
						// minutes1 > minutes2
						= (False,False);
				// hours1 > hours2
				= (False,False);
};

//1.3
instance older_than_or_equal (!Date`, !Time`)	// DateTime
//3.1
/*2.0
instance older_than_or_equal (!Date, !Time)	// DateTime
0.2*/
where {
	older_than_or_equal (date1,time1) (date2,time2)
		# (older,equal)
			= older_than_or_equal date1 date2
		| older 
			= (True,False);
			| equal
				# (older,equal)
					= older_than_or_equal time1 time2;
				| older
					= (True,False);			
					= (False,equal);
				= (False,False);
};

instance toString DirError
where {
	toString NoDirError = "NoDirError";
	toString DoesntExist = "DoesntExist";
	toString BadName = "BadName";
	toString NotEnoughSpace = "NotEnoughSpace";
	toString AlreadyExists = "AlreadyExists";
	toString NoPermission = "NoPermission";
	toString MoveIntoOffspring = "MoveIntoOffspring";
	toString MoveAcrossDisks = "MoveAcrossDisks";
	toString NotYetRemovable = "NotYetRemovable";
	toString OtherDirError = "OtherDirError";
};

/*

GenObj :: !String !String !*Files -> (!Bool,!Bool,!String,!String,!*Files);
GenObj cgpath path_and_file files
	= GenObj2 cgpath path_and_file files;

/*
	GenObj2
		
	Improvements:
		- if code generator changes path, then it can not be found (no dialogue pops up; cancel unimplemented)
		- version of code generator doesn't yet matter
*/

GenObj2 :: !String !String !*Files -> (!Bool,!Bool,!String,!String,!*Files);
GenObj2 cgpath path_and_file files
	| not (ends path_and_file ".o" || ends path_and_file ".obj" || ends path_and_file ".abc")
		= abort ("DynamicLink (GenObj): file " +++ path_and_file +++ " doesn't end with {abc,o,obj}-extension");
		
		#! (path_file,extension)
			= ExtractPathFileAndExtension path_and_file;
		| extension == "obj"
			= abort ("DynamicLink (GenObj): object not generated by cg " +++ path_and_file);

			// (re)create the file_name with the {.abc,.o}-extensions
			// collect .abc info
			#! abc_path_file
				= path_file +++ ".abc";
			#! ((ok,abc_file),files)
				= pd_StringToPath abc_path_file files;
			| not ok
				= abort ("DynamicLink (GenObj):  something went wrong during path conversion 1");
			
			// collect .o info
			#! o_path_file
				= path_file +++ ".o";
			#! ((ok,o_file),files)
				= pd_StringToPath o_path_file files;
			| not ok
				= abort ("DynamicLink (GenObj):  something went wrong during path conversion 2");
				
			// get the file info
			# ((abc_dir_error,abc_file_info),files)
				= getFileInfo abc_file files;
			# ((o_dir_error,o_file_info),files)
				= getFileInfo o_file files;
					
			| (abc_dir_error == DoesntExist) && (o_dir_error == DoesntExist)
				// given path was invalid; pop up a dialogue
				= abort ("nothing exists" );
			| (abc_dir_error == DoesntExist) && (o_dir_error <> DoesntExist)
				// only .o exists; assume it to be up-to-date
				= (True,False,o_path_file,"",files);	
			| (abc_dir_error <> DoesntExist) && (o_dir_error == DoesntExist)
				// only .abc exists; then generate corresponding .o
				#! (s,ok)
					= CodeGen path_file;
				| ok
					= (True,False,s,"",files);
					
					= abort "problems during .o generation";
		
			// both extensions of the file exist. is .o out of date?
			#! abc_datetime
				= abc_file_info.pi_fileInfo.lastModified;
			#! o_datatime
				= o_file_info.pi_fileInfo.lastModified;
			#! (older,equal)
				= older_than_or_equal abc_datetime o_datatime;
			| not older && not equal
				// .abc is newer as its corresponding .o; generate a new .o
				# (s,ok)
					=  CodeGen path_file;
				| ok
					= (True,False,s,"",files);
					= abort ("not ok" +++ s);
				// .o is up-to-date 
				= (True,False,o_path_file,"",files);
	*/