implementation module CollectTypes;

import typetable;
import type_io_read;
import StdDynamicTypes;
import StdMaybe;
import DefaultElem;
import StdEnv;

import RWSDebugChoice;

:: *CollectTypesState
	= {
	// Input
		cts_type_tables							:: !*{#TypeTable}

	// Reserved		
	,	cts_collected_types						:: !*{#Bool}									// already collected types
	,	cts_module_base_indices					:: !{#Int}

	// Output
	,	cts_type_dependencies					:: ![(TIO_TypeReference,TIO_TypeReference)]

	// Reserved		
	,	cts_type_dependencies_to_be_collected	:: ![(TIO_TypeReference,TIO_TypeReference)]		// set of type dependencies to be examined

	,	cts_left_i								:: !Int
	,	cts_right_i								:: !Int

	,	cts_left_module_i						:: !Int
	,	cts_right_module_i						:: !Int
	};
	
// interne type equivalenties

default_collect_types_state :: *CollectTypesState;
default_collect_types_state
	= { 
	// Parameters
		cts_type_tables							= {}
		
	,	cts_collected_types						= {}
	,	cts_module_base_indices					= {}
	
	// Reserved
	,	cts_type_dependencies					= []
	
	,	cts_type_dependencies_to_be_collected	= []

	,	cts_left_i								= -1
	,	cts_right_i								= -1

	,	cts_left_module_i						= -1
	,	cts_right_module_i						= -1

	};
	

// precondition:
// - 1st and 2nd argument are type equivalent 
// - types are in nf
class collect_types a :: !a !a !*CollectTypesState -> *CollectTypesState;

init_collect_types :: !Int !Int ![(TIO_TypeReference,TIO_TypeReference)] !*CollectTypesState -> *CollectTypesState;
init_collect_types type_table_left type_table_right types cts
	// determine amount of representant types and create arrays which marks used type definition 
	# (n_types,cts)
		= cts!cts_type_tables.[type_table_left].tt_type_io_state.tis_max_types;
	# (module_base_indices,cts)
		= cts!cts_type_tables.[type_table_left].tt_type_io_state.tis_max_types_per_module;

	# cts
		= { cts &
			cts_collected_types						= createArray n_types False
		,	cts_module_base_indices					= module_base_indices

		,	cts_type_dependencies					= []			
		,	cts_type_dependencies_to_be_collected	= types

		,	cts_left_i								= type_table_left
		,	cts_right_i								= type_table_right
		};
	= cts;
	
collect_types_loop :: !*CollectTypesState -> *CollectTypesState;
collect_types_loop cts=:{cts_type_dependencies_to_be_collected=[(type_reference_left,type_reference_right):rest]}
	# cts
		= { cts &
			cts_type_dependencies_to_be_collected	= rest
		};
	# cts
		= collect_types type_reference_left type_reference_right cts;
	= collect_types_loop cts;
collect_types_loop cts
	= cts;

// only called from other modules; does initializing
instance collect_types TypeTableTypeReference
where {
	collect_types t1=:(TypeTableTypeReference type_table_left type_reference_left) t2=:(TypeTableTypeReference type_table_right type_reference_right) cts
		| isTypeWithoutDefinition type_reference_left //<<- ("collect_types",t1,t2)
			= {cts & cts_type_dependencies = [(type_reference_left,type_reference_right)] }; // optimization
			
		# cts
			= init_collect_types type_table_left type_table_right [(type_reference_left,type_reference_right)] cts
		= collect_types_loop cts;
};

// only for types without definition e.g. List, Array
instance == TIO_TypeReference
where {
	(==) {tio_type_without_definition=Just type_name1} {tio_type_without_definition=Just type_name2}
		= type_name1 == type_name2;
	(==) _ _
		= False;
};

instance collect_types TIO_TypeReference
where {
	collect_types type_ref1=:{tio_tr_module_n=tio_tr_module_n1} type_ref2=:{tio_tr_module_n=tio_tr_module_n2} cts=:{cts_left_module_i,cts_right_module_i,cts_left_i,cts_right_i,cts_type_dependencies}
		| isTypeWithoutDefinition type_ref1
			# type_pair = (type_ref1,type_ref2)
			| isMember type_pair cts_type_dependencies
				= cts;
				# cts
					= { cts &
						cts_type_dependencies	= [type_pair:cts_type_dependencies]
					};
				= cts;
			
		// check whether type reference has already been seen
		# (type_ref_index1,cts)
			= compute_type_ref_index type_ref1 cts;
		# (already_referenced_type,cts)
			= cts!cts_collected_types.[type_ref_index1];
		| already_referenced_type
			= cts;
		
		// mark it as seen and put in list
		# cts
			= { cts & 
				cts_collected_types.[type_ref_index1]	= True						
			,	cts_type_dependencies 					= [(type_ref1,type_ref2):cts.cts_type_dependencies]
			};
			
		// dereference type reference
		# (type1,cts)
			= deref_type_reference (TypeTableTypeReference cts_left_i type_ref1) cts
		# (type2,cts)
			= deref_type_reference (TypeTableTypeReference cts_right_i type_ref2) cts
			
		// set defining modules of new types
		# cts
			= { cts &
				cts_left_module_i				= tio_tr_module_n1
			,	cts_right_module_i				= tio_tr_module_n2
			}		
		# cts
			= collect_types type1 type2 cts;	
			
					// restore old defining modules
		# cts
			= { cts &
				cts_left_module_i				= cts_left_module_i
			,	cts_right_module_i				= cts_right_module_i
			}
		= cts;
};

// copied (and slightly modified) from type_io_equal_types ...
compute_type_ref_index {tio_tr_module_n,tio_tr_type_def_n,tio_type_without_definition=Nothing} cts
	# (module_base_index,cts)
		= cts!cts_module_base_indices.[tio_tr_module_n];
	# index
		= module_base_index + tio_tr_type_def_n;
	= (index,cts);
compute_type_ref_index {tio_tr_module_n,tio_tr_type_def_n,tio_type_without_definition=Just s} cts
	= abort ("compute_type_ref_index " +++ s);

deref_type_reference type=:(TypeTableTypeReference type_table_i {tio_tr_module_n,tio_tr_type_def_n,tio_type_without_definition=Nothing}) cts
	= cts!cts_type_tables.[type_table_i].tt_tio_common_defs.[tio_tr_module_n].tio_com_type_defs.[tio_tr_type_def_n];
// ... copied

instance collect_types (TIO_TypeDef a) | collect_types a
where {
	collect_types {tio_td_rhs=tio_td_rhs1} {tio_td_rhs=tio_td_rhs2} cts
		= collect_types tio_td_rhs1 tio_td_rhs2 cts;
};
	
instance collect_types TIO_TypeRhs
where {
	collect_types (TIO_AlgType tio_defined_symbols1) (TIO_AlgType tio_defined_symbols2) cts
		= collect_types tio_defined_symbols1 tio_defined_symbols2 cts;
	collect_types (TIO_RecordType tio_record_type1) (TIO_RecordType tio_record_type2) cts
		= collect_types tio_record_type1 tio_record_type2 cts;
	collect_types (TIO_SynType tio_syn_type1) (TIO_SynType tio_syn_type2) cts
		= collect_types tio_syn_type1 tio_syn_type2 cts;

	collect_types TIO_UnknownType TIO_UnknownType cts
		= abort "UnknownType";
	collect_types _ _ cts
		= abort "unknown type";
};

instance collect_types TIO_RecordType
where {
	collect_types {tio_rt_fields=tio_rt_fields1} {tio_rt_fields=tio_rt_fields2} cts
		= collect_types tio_rt_fields1 tio_rt_fields2 cts;
};
		
instance collect_types TIO_ConstructorSymbol
where {
	collect_types {tio_cons=tio_cons1} {tio_cons=tio_cons2} cts
		= collect_types tio_cons1 tio_cons2 cts;
};

instance collect_types TIO_DefinedSymbol
where {
	collect_types {tio_ds_index=tio_ds_index1} {tio_ds_index=tio_ds_index2} cts=:{cts_left_i,cts_right_i,cts_left_module_i,cts_right_module_i}
		#! (tio_td_name,cts)
			= cts!cts_type_tables.[cts_left_i].tt_tio_common_defs.[cts_left_module_i].tio_module;
		#! (string_table_i,cts)
			=cts!cts_type_tables.[cts_left_i].tt_type_io_state.tis_string_table;
		#! module_name_l
			= get_name_from_string_table tio_td_name string_table_i;

		#! (tio_td_name,cts)
			= cts!cts_type_tables.[cts_right_i].tt_tio_common_defs.[cts_right_module_i].tio_module;
		#! (string_table_i,cts)
			=cts!cts_type_tables.[cts_right_i].tt_type_io_state.tis_string_table;
		#! module_name_r
			= get_name_from_string_table tio_td_name string_table_i;
	
		# (tio_cons_symb1,cts)
			= cts!cts_type_tables.[cts_left_i].tt_tio_common_defs.[cts_left_module_i].tio_com_cons_defs.[tio_ds_index1];
		# (tio_cons_symb2,cts)
			= cts!cts_type_tables.[cts_right_i].tt_tio_common_defs.[cts_right_module_i].tio_com_cons_defs.[tio_ds_index2];
		= collect_types tio_cons_symb1 tio_cons_symb2 cts;
};

instance collect_types TIO_ConsDef
where {
	collect_types {tio_cons_type=tio_cons_type1}  {tio_cons_type=tio_cons_type2} cts
		= collect_types tio_cons_type1 tio_cons_type2 cts;
};

instance collect_types TIO_SymbolType
where {
	collect_types {tio_st_args=tio_st_args1,tio_st_result=tio_st_result1} {tio_st_args=tio_st_args2,tio_st_result=tio_st_result2} cts
		# cts
			= collect_types tio_st_args1 tio_st_args2 cts;
		= collect_types tio_st_result1 tio_st_result2 cts;
};

instance collect_types TIO_AType
where {
	collect_types {tio_at_type=tio_at_type1} {tio_at_type=tio_at_type2} cts
		= collect_types tio_at_type1 tio_at_type2 cts;
};
			
instance collect_types TIO_Type
where {
	collect_types (TIO_TAS tio_type_symb_ident1 tio_atypes1 _) (TIO_TAS tio_type_symb_ident2 tio_atypes2 _) cts
		# cts
			= collect_types tio_type_symb_ident1 tio_type_symb_ident2 cts;
		= collect_types tio_atypes1 tio_atypes2 cts;
					
	collect_types (tio_atype1a ----> tio_atype1b) (tio_atype2a ----> tio_atype2b) cts
		# cts
			= collect_types tio_atype1a tio_atype2a cts;
		= collect_types tio_atype1b tio_atype2b cts;
			
	collect_types (_ :@@: tio_atypes1) (_ :@@: tio_atypes2) cts
		= collect_types tio_atypes1 tio_atypes2 cts;
	
	collect_types (TIO_TB tio_basic_type1) (TIO_TB tio_basic_type2) cts
		= collect_types tio_basic_type1 tio_basic_type2 cts;
	
	collect_types _ _ cts
		= cts;
};
		
instance collect_types TIO_BasicType
where {
	// type are equivalent, so one match suffices
	collect_types basic_type _ cts
		# basic_type
			= { default_elem &
				tio_type_without_definition	= Just (toString basic_type)
			};
		= collect_types basic_type basic_type cts;
};
	
instance collect_types TIO_TypeSymbIdent
where {
	collect_types {tio_type_name_ref=tio_type_name_ref1} {tio_type_name_ref=tio_type_name_ref2} cts
		= collect_types tio_type_name_ref1 tio_type_name_ref2 cts;
};

instance collect_types TIO_FieldSymbol
where {
	collect_types {tio_fs_index=tio_fs_index1} {tio_fs_index=tio_fs_index2} cts=:{cts_left_i,cts_right_i,cts_left_module_i,cts_right_module_i}
		# (tio_select_def1,cts)
			= cts!cts_type_tables.[cts_left_i].tt_tio_common_defs.[cts_left_module_i].tio_com_selector_defs.[tio_fs_index1];
		# (tio_select_def2,cts)
			= cts!cts_type_tables.[cts_right_i].tt_tio_common_defs.[cts_right_module_i].tio_com_selector_defs.[tio_fs_index2];
		= collect_types tio_select_def1 tio_select_def2 cts;
};

instance collect_types TIO_SelectorDef
where {
	collect_types {tio_sd_type=tio_sd_type1} {tio_sd_type=tio_sd_type2} cts
		= collect_types tio_sd_type1 tio_sd_type2 cts;
};

instance collect_types [a] | collect_types a
where {
	collect_types [] [] cts
		= cts;
	collect_types [type1:types1] [type2:types2] cts
		# cts
			= collect_types type1 type2 cts;
		= collect_types types1 types2 cts;
};
			
instance collect_types {#a} | Array {#} a & collect_types a
where {
	collect_types a1 a2 cts
		| s_a1 <> s_a2
			= cts;
			
		= collect_types_loop 0 s_a1 cts;
	where {
		collect_types_loop i limit cts
			| i == limit
				= cts;
			
			# cts
				= collect_types a1.[i] a2.[i] cts;
			= collect_types_loop (inc i) limit cts;
	
		s_a1
			= size a1;
		s_a2
			= size a2;
	};
};
