% =============================================================================
% Automated modeling in process-based qualitative reasoning
%  ** Input model. Contains predicates that handles input from a GARP3
% 				   simulation
% 
% April - July 2007
% 
% Hylke Buisman   - hbuisman@science.uva.nl
% =============================================================================

%==============================================================================
% GENERAL STATE PREDICATES
%==============================================================================

% Get property with PropId from a state's SMD
% TODO: Could be inproved by using keys like 'input system'
% for more readable code
% get_property/3
% get_property(+StateId, +PropertyId, -PropertyValue)
get_property(StateId, PropId, Prop) :-
	engine:state(StateId, Description),
	Description =.. Items,
	nth1(PropId, Items, Prop).
	
	
% Get states that can be reached in one step from input
% get_start_states/1
% get_start_states(-InitStates)
get_start_states(Inits) :-
	findall(S, 
		(
		engine:state_from(S,Froms),
		member(input, Froms)
		), Inits).


% Return the inequalities known in a state
% get_inequalities/2
% get_inequalities(+StateId, -Inequalities)
get_all_inequalities(StateId, Inequalities) :-
	get_property(StateId, 6, par_relations(Ineq)),
	findall(Inq,
		(
		member(Inq,Ineq),
		Inq =.. [H, A1, A2], 
		member(H, [greater, smaller, smaller_or_equal, greater_or_equal, equal]),
		% There should be no min(_,_) or plus(_,_) in the (in)equality
		A1 =.. A1List,
		A2 =.. A2List,
		length(A1List, LA1),
		length(A2List, LA2),
		LA1 < 3,
		LA2 < 3
		),Inequalities).

		
% Return the name of the inputSystem (Scenario) used for the current simulation
% get_input_system_name/1
% get_input_system_name(-ScenarioName)
get_input_system_name(Name) :-
	get_property(1, 2, input_system(Name)).
		
		
% Check for (in)equalities that hold in a given state between two quantities
% Note that this quantity pair is directed. That is, it only returns the (in)equalities
% in that direction. So if [Q1, Q2] is passed it will for example return smaller(Q1, Q2)
% but not greater(Q2, Q1)
% get_quantity_inequalities/3
% get_quantity_inequalities(+StateId, +QuantityPair, -Inequalities)
get_quantity_inequalities(StateId, [A, B], Inequalities) :-
	get_property(StateId, 6, par_relations(Ineq)),
	findall(Inq,
		(
		member(Inq,Ineq),
		Inq =.. [H, A, B], 
		member(H, [greater, smaller, smaller_or_equal, greater_or_equal, equal]),
		% There should be no min(_,_) or plus(_,_) in the (in)equality
		A =.. A1List,
		B =.. A2List,
		length(A1List, LA1),
		length(A2List, LA2),
		LA1 < 3,
		LA2 < 3

		),Inequalities).


% Retrieve scenario description
% get_scenario/1
% get_scenario(-Scenario)
get_scenario(Scenario) :-
	get_property(_, 3, Scenario), !.


% Returns values for all quantities in all states in the state-graph
% get_all_state_values/1
% get_all_state_values(-Values)
get_all_state_values(Values) :-
	findall(state(StateId,Values), 
		(
		get_state_values(StateId, Values)
		), Values
	).

	
% Get magnitudes and derivatives for all quantities in a given state
% get_state_values/2
% get_state_values(+StateId, -Values)
get_state_values(StateID, Values) :-
	get_property(StateID, 5, par_values(Values)).

	
	
%==============================================================================
% QUANTITY RELATED PREDICATES
%==============================================================================

% Retrieve a list of all quantities that were added during the simulation
% get_quantities/1
% get_quantities(-Quantities)
get_all_quantities(Quantities) :-
	setof(quantity(QType, QName, AttachedTo, QSpace),
		Qs^Q^(
			get_property(_, 4, parameters(Qs)),
			member(Q, Qs),
			Q =.. [QType, AttachedTo, QName, _, QSpace]
		),
	Quantities), !.


% Retrieve quantities in a given state
% get_quantities/2
% get_quantities(+StateId, -Quantities)
get_state_quantities(StateId, Quantities) :-
	setof(quantity(QType, QName, AttachedTo, QSpace),
		Qs^Q^(
			get_property(StateId, 4, parameters(Qs)),
			member(Q, Qs),
			Q =.. [QType, AttachedTo, QName, _, QSpace]
		),
	Quantities), !.


% Get a states magnitudes or derivative value for a specific quantity
% ValueType should be either magnitude or derivative
% get_quantity_values/4
% get_quantity_values(+StateId, +ValueType, +QuantityName, -Value)
get_quantity_values(StateID, magnitude, Quantity, Magnitude) :-
	get_state_values(StateID, V),
	member(value(Quantity, _, Magnitude, _Deriv), V).
get_quantity_values(StateID, derivative, Quantity, Derivative) :-
	get_state_values(StateID, V),
	member(value(Quantity, _, _Magnitude, Derivative), V).
	
	
% Return the names of all quantities in the state-graph
% TODO: should give names of all added quantities (not only state 1)
% get_quantity_names/1
% get_quantity_names(-QuantityNames)
get_quantity_names(QNames) :-
	findall(QName,
		Qs^Q^(
			get_property(1, 4, parameters(Qs)),
			member(Q, Qs),
			Q =.. [_, _, QName, _, _]
		),
	QNames), !.
	
	
% Return the entity to which a quantity is connected
% get_quantity_owner/2
% get_quantity_owner(+QuantityName, -EntityName)
get_quantity_owner(Quantity, Owner) :-
	get_state_quantities(1, Q),
	member(quantity(_, Quantity, Owner, _), Q).

	
% Given the name of a quantity return its type
% get_quantity_type/2
% get_quantity_type(+QuantityName, -QuantityType)
get_quantity_type(Quantity, Type) :-
	get_state_quantities(1, Q),
	member(quantity(Type, Quantity, _, _), Q).


% Return the partition (landmarks) of the quantity space belonging to 
% a certain quantity as a list
% get_qs_partition/2
% get_qs_partition(+Quantity, -Partition)
get_qs_partition(Quantity, Partition) :-
	get_state_quantities(_, Qs), !,
	member(quantity(_Type, Quantity, _AttachedTo, QSpace), Qs),
	engine:quantity_space(QSpace, _, Partition).


% Return whether two quantity space partitions are equivalent
% Two quantity spaces Q1 and Q2 are considered equivalent when both
% can be aligned in such a way that each point in Q1 is next to a point
% in Q2, and each interval in Q1 is next to an interval in Q2.
% equivalent_partitions/2
% equivalent_partitions(+Partition1, +Partition2)
equivalent_partitions([], []).
equivalent_partitions([point(_)|T1], [point(_)|T2]) :-
	equivalent_partitions(T1, T2).
equivalent_partitions([V1|T1], [V2|T2]) :-
	V1 =.. [H1|_T1],
	V2 =.. [H2|_T2],
	H1 \= point,
	H2 \= point,
	equivalent_partitions(T1, T2).
	

% Return the sign (-1, 0, +1) of the ValueType (magnitude/derivative)
% of Quantity. If not derivable, unkown is returned.
% sign/4
% sign(+StateId, +ValueType, +Quantity, -Sign)
sign(StateId, magnitude, Quantity, Sign) :-
	get_state_quantities(StateId, Qs),
	member(quantity(_Type, Quantity, _AttachedTo, QSpace), Qs),
	engine:quantity_space(QSpace, _, Partition),
	get_quantity_values(StateId, magnitude, Quantity, Value),
	get_sign(Value, Partition, Sign).
sign(StateId, derivative, Quantity, Sign) :-
	get_quantity_values(StateId, derivative, Quantity, Value),
	get_sign(Value, [min, point(zero), plus], Sign).


% Wrapper for get_sign/4
% get_sign/3
% get_sign(+Value, +Partition, -Result)
get_sign(Value, Partition, Result) :-
	get_sign(Value, Partition, none, Result), !.
	
	
% Sub predicate of sign/4
% get_sign/4
% get_sign(+Value, +Partition, AccResult, -Result)
get_sign(_, [], _Acc, unkown).
get_sign(zero, _, _, 0).
get_sign(A, [H|_T], zero_found, 1) :-
	A = H; point(A) = H.
get_sign(A, [H|T], none, Result) :-
	(A = H; point(A) = H),
	get_sign(A, T, item_found, Result).
get_sign(_A, [point(zero)|_T], item_found, -1).
get_sign(A, [point(zero)|T], none, Result) :-
	get_sign(A, T, zero_found, Result).
get_sign(A, [_|T], Acc, Result) :-
	get_sign(A, T, Acc, Result).
	
	
% Checks whether Q1 is smaller than Q2 in StateId
% is_smaller_than/3
% is_smaller_than(+State, +Q1, +Q2)
is_smaller_than(StateId, Q1, Q2) :- 
	get_quantity_inequalities(StateId, [Q1, Q2], Ineq1),
	member(smaller(Q1, Q2), Ineq1), !.
is_smaller_than(StateId, Q1, Q2) :- 
	get_quantity_inequalities(StateId, [Q2, Q1], Ineq2),
	member(greater(Q2, Q1), Ineq2), !.
	
	
% Checks whether Q1 is greater than Q2 in StateId
% is_greater_than/3
% is_greater_than(+State, +Q1, +Q2)
is_greater_than(StateId, Q1, Q2) :- 
	get_quantity_inequalities(StateId, [Q1, Q2], Ineq1),
	member(greater(Q1, Q2), Ineq1), !.
is_greater_than(StateId, Q1, Q2) :- 
	get_quantity_inequalities(StateId, [Q2, Q1], Ineq2),
	member(smaller(Q2, Q1), Ineq2), !.

	
	
%==============================================================================
% ENTITY PREDICATES
%==============================================================================
	
% Recursively checks if EntityName is op type Type.
% is_type/2
% is_type(+EntityName, -Type)
is_type(EntityName, Type) :-
	get_entity_type(EntityName, Type2),
	is_type(Type2, Type).
is_type(Type1, Type2) :-
	get_scenario(system_elements(Scenario)),
	\+ memberchk(instance(Type1, _), Scenario),
	engine:isa_instance(Type1, Type2).

	
% Checks if EntityName's direct type is Type
% get_entity_type/2
% get_entity_type(+EntityName, -Type)	
get_entity_type(EntityName, Type) :-
	get_scenario(system_elements(Scenario)),
	member(instance(EntityName, Type), Scenario).

	
% Returns all entities present in the state-graph
% TODO: Should do this over all states
% get_present_entities/1
% get_present_entities(-Result)	
get_present_entities(Result) :-
	findall(Owner,
		Qs^Q^(
			get_property(1, 4, parameters(Qs)),
			member(Q, Qs),
			Q =.. [_, Owner, _, _, _]
		),
	Entities), 
	list_to_set(Entities, Result), !.
	
	
% Returns all quantities belonging to a certain entity
% get_entity_quantities/2
% get_entity_quantities(+Entity, -Quantities)		
get_entity_quantities(Entity, Quantities) :-
	findall(QName,
		Qs^Q^(
			get_property(1, 4, parameters(Qs)),
			member(Q, Qs),
			Q =.. [_, Entity, QName, _, _]
		),
	Quantities), !.
	
	
	
%==============================================================================
% TRANSITIONS AND PATHS: Not actually used at the moment
%==============================================================================

% Get transition from state-graph
% get_transitions/1
% get_transitions(-Transitions)
get_transitions(FTrans) :-
	findall(transition(From, Tos), 
		(
		engine:state_to(From,Tos)
		), Transitions
	),
	format_transitions(Transitions, FTrans).
	
	
% Get transition for a specific state
% get_transitions/1
% get_transitions(+FromState, -Transitions)
get_transitions(FromState, FTrans) :-
	findall(transition(FromState, Tos), 
		(
		engine:state_to(FromState,Tos)
		), Transitions
	),
	format_transitions(Transitions, FTrans).
	
	
% Format the transitions
% format_transitions/2
% format_transitions(+TransitionsIn, -FormattedTransitions)
format_transitions([], []).
format_transitions([transition(From, Tos)|T], NFTrans) :-
	format_transitions(T, FTrans),
	format_tos(From, Tos, HTrans),
	append(HTrans, FTrans, NFTrans).
	
	
% Predicate belonging to format_transitions
% format_tos/3
format_tos(_, [], []).
format_tos(From, [to(_Cause, _Conditions, _Results, to_state([To]), _Type)|T], 
			[transition(From, To)|Trans]) :-
	format_tos(From, T, Trans).
format_tos(From, [to(_Cause, _Conditions, _Results, to_state([]), _Type)|T], 
			Trans) :-
	format_tos(From, T, Trans).
	
	
% Check whether this state is a branching point in the graph
% is_branch_point/1
% is_branch_point(+StateId)
is_branch_point(StateId) :-
	get_transitions(StateId, Trans),
	length(Trans, L),
	L > 1.

	
% Wrapper for find_path/2
% path/2
% path(?StartId, ?Path)
path(Start, Path) :-
	find_path([Start], RevPath),
	reverse(RevPath, Path).

	
% Check/generate path from state-graph
% find_path/2
% find_path(?AccPath, ?Path)
find_path([H|T], Path) :-
	get_transitions(H, Trans),
	Trans \= [],
	member(transition(H,Next), Trans),
	\+ member(Next, [H|T]),
	find_path([Next|[H|T]], Path).
find_path([H|T], [H|T]) :- 			% Node has no succesors
	get_transitions(H, Trans),
	Trans == [].
find_path([H|T], [H|T]) :- 
	get_transitions(H, Trans),		% Cycle established
	findall(_, 
		(
			member(transition(H,Next), Trans),
			\+ member(Next, [H|T])
		), List),
	length(List, L),
	L = 0.
	
	
	
%==============================================================================
% SIMPLE AUXILLIARY PREDICATES
%==============================================================================
	
% Converts an atom to a string, capitalizing the first letter,
% and replacing underscores with spaces. Mainly required for
% converting internal names like oil_left to the names used in XPCE,
% in this case 'Oil left'
% pretty_atom_to_string/2
% pretty_atom_to_string(+Atom, -String)
pretty_atom_to_string(In, Out) :-
	atom_chars(In, [C|T]),
	upcase_atom(C, UC),
	list_replaceall([UC|T], '_', ' ', NList),
	atom_chars(Out, NList).
	
	
% Replace all occurrences of Find in ListIn with Replace
% list_replaceall/4
% list_replaceall(+ListIn, +Find, +Replace, -ListOut)
list_replaceall([], _, _, []).
list_replaceall([Find|T], Find, Replace, [Replace|Out]) :-
	list_replaceall(T, Find, Replace, Out), !.
list_replaceall([H|T], Find, Replace, [H|Out]) :-
	list_replaceall(T, Find, Replace, Out), !.
