% resolve.pl

% Resolution processing for translation

:- module(resolve,
	[null_discourse_context/1,
	 get_preceding_source_discourse/2,
	 set_preceding_source_discourse/3,
	 get_preceding_resolved_interlingua/2,
	 set_preceding_resolved_interlingua/3,
	 get_preceding_target_utterance/2,
	 set_preceding_target_utterance/3,

	 ellipsis_processing_is_activated/0,
	 switch_on_answer_resolution/0,
	 switch_off_answer_resolution/0,
	 
	 perform_resolution/4,

	 remove_phrase_utterance_marking/2]
    ).

%----------------------------------------------------------------------

:- use_module('$REGULUS/Prolog/regulus_utilities').
:- use_module('$REGULUS/PrologLib/utilities').

:- use_module(library(lists)).
:- use_module(library(terms)).
:- use_module(library(ordsets)).

%----------------------------------------------------------------------

null_discourse_context(Context) :-
	empty_assoc_generic(Context).

get_preceding_source_discourse(SourceDiscourse, Context) :-
	nonvar(Context),
	get_assoc_generic(preceding_source_discourse, Context, SourceDiscourse),
	!.
get_preceding_source_discourse(SourceDiscourse, _Context) :-
	SourceDiscourse = [].

set_preceding_source_discourse(NewSourceDiscourse, ContextIn, ContextOut) :-
	nonvar(ContextIn),
	put_assoc_generic(preceding_source_discourse, ContextIn, NewSourceDiscourse, ContextOut).

get_preceding_resolved_interlingua(Interlingua, Context) :-
	nonvar(Context),
	get_assoc_generic(preceding_resolved_interlingua, Context, Interlingua),
	!.
get_preceding_resolved_interlingua(Interlingua, _Context) :-
	Interlingua = [].

set_preceding_resolved_interlingua(NewInterlingua, ContextIn, ContextOut) :-
	nonvar(ContextIn),
	put_assoc_generic(preceding_resolved_interlingua, ContextIn, NewInterlingua, ContextOut).

get_preceding_target_utterance(Utt, Context) :-
	nonvar(Context),
	get_assoc_generic(preceding_target_utterance, Context, Utt),
	!.
get_preceding_target_utterance(Utt, _Context) :-
	Utt = '*no_preceding_utterance*'.

set_preceding_target_utterance(NewUtt, ContextIn, ContextOut) :-
	nonvar(ContextIn),
	put_assoc_generic(preceding_target_utterance, ContextIn, NewUtt, ContextOut).

%----------------------------------------------------------------------

ellipsis_processing_is_activated :-
	current_predicate(user:ellipsis_class_example/2).

%----------------------------------------------------------------------

:- dynamic answer_resolution_on/0.

switch_on_answer_resolution :-
	answer_resolution_on,
	!.
switch_on_answer_resolution :-
	assertz(answer_resolution_on).

switch_off_answer_resolution :-
	retractall(answer_resolution_on).

%----------------------------------------------------------------------

/*
- Resolution processing
  - CurrentSourceDiscourse x PrecedingResolvedSourceDiscourse -> ResolvedSourceDiscourse
    - Predicate: perform_resolution(CurrentSourceDiscourse, PrecedingResolvedSourceDiscourse, ResolvedSourceDiscourse)
  - So far, only yields non-trivial result if CurrentSourceDiscourse is a phrase
    - otherwise, CurrentSourceDiscourse = ResolvedSourceDiscourse
  - Remove [utterance_type, phrase]
  - Sort
  - Full match against some ellipsis_class_example, giving Id
  - Partial match of some other ellipsis_class_example with same Id against PrecedingResolvedSourceDiscourse
  - Replace matched material in PrecedingResolvedSourceDiscourse with CurrentSourceDiscourse
  - Match if first elt in pair is same, i.e. [Key, X] matches [Key, Y]
*/

perform_resolution(CurrentSourceDiscourse, PrecedingResolvedSourceDiscourse, ResolvedSourceDiscourse, ResolutionProcessing) :-
	var(PrecedingResolvedSourceDiscourse),
	format('~N*** WARNING: preceding resolved source discourse representation in call to perform_resolution/3 was uninstantiated.~n', []),
	ResolvedSourceDiscourse = CurrentSourceDiscourse,
	ResolutionProcessing = trivial,
	!.
perform_resolution(CurrentSourceDiscourse, PrecedingResolvedSourceDiscourse0, ResolvedSourceDiscourse, ResolutionProcessing) :-
	nonvar(PrecedingResolvedSourceDiscourse0),
	(   PrecedingResolvedSourceDiscourse0 = external(PrecedingResolvedSourceDiscourse) ->
	    Mode = answer ;

	    % For debugging
	    answer_resolution_on ->
	    PrecedingResolvedSourceDiscourse0 = PrecedingResolvedSourceDiscourse,
	    Mode = answer ;
	    
	    PrecedingResolvedSourceDiscourse0 = PrecedingResolvedSourceDiscourse,
	    Mode = default
	),
	(   whq_representation(PrecedingResolvedSourceDiscourse) ->
	    Wh = wh ;
	    
	    Wh = non_wh
	),
	all_resolutions(CurrentSourceDiscourse, PrecedingResolvedSourceDiscourse, Wh, Pairs),
	keysort(Pairs, SortedPairs),
	SortedPairs = [_NegLengthReplaced-[ResolvedSourceDiscourse0, ResolutionProcessing] | _Rest],
	(   ( Mode = answer, Wh = wh ) ->
	    change_whq_to_dcl(ResolvedSourceDiscourse0, ResolvedSourceDiscourse1),
	    change_you_to_i(ResolvedSourceDiscourse1, ResolvedSourceDiscourse) ;

	    ( Mode = answer, Wh \== wh ) ->
	    change_ynq_to_dcl(ResolvedSourceDiscourse0, ResolvedSourceDiscourse1),
	    change_you_to_i(ResolvedSourceDiscourse1, ResolvedSourceDiscourse) ;
	    
	    ( Mode \== answer, Wh = wh ) ->
	    change_whq_to_ynq(ResolvedSourceDiscourse0, ResolvedSourceDiscourse) ;
	    
	    ResolvedSourceDiscourse0 = ResolvedSourceDiscourse
	),
	!.
perform_resolution(CurrentSourceDiscourse, _PrecedingResolvedSourceDiscourse, ResolvedSourceDiscourse, ResolutionProcessing) :-
	ResolvedSourceDiscourse = CurrentSourceDiscourse,
	ResolutionProcessing = trivial,
	!.
perform_resolution(CurrentSourceDiscourse, PrecedingResolvedSourceDiscourse, ResolvedSourceDiscourse, ResolutionProcessing) :-
	format('~N*** ERROR: bad call: ~w~n', [perform_resolution(CurrentSourceDiscourse, PrecedingResolvedSourceDiscourse, ResolvedSourceDiscourse, ResolutionProcessing)]),
	fail.

all_resolutions(CurrentSourceDiscourse, PrecedingResolvedSourceDiscourse, Wh, Pairs) :-
	findall(LengthReplaced-[ResolvedSourceDiscourse, ResolutionProcessing],
		perform_resolution1(CurrentSourceDiscourse, PrecedingResolvedSourceDiscourse, Wh,
				    ResolvedSourceDiscourse, ResolutionProcessing, LengthReplaced),
		Pairs),
	!.

perform_resolution1(CurrentSourceDiscourse, PrecedingResolvedSourceDiscourse, Wh,
		    ResolvedSourceDiscourse,
		    ellipsis_substitution(Id, (Matched2 --> Matched1) ), ResolutionScore) :-
	ellipsis_processing_is_activated,
	remove_phrase_utterance_marking_and_uninstantiate_null_roles(CurrentSourceDiscourse, CurrentSourceDiscourse1),
	make_transfer_representation_canonical(CurrentSourceDiscourse1, CurrentSourceDiscourse2),
	make_transfer_representation_canonical(PrecedingResolvedSourceDiscourse, PrecedingResolvedSourceDiscourse1),

	full_match_against_ellipsis_class_example(CurrentSourceDiscourse2, Id, Matched1),

	get_ellipsis_class_and_context(Wh, Id, EllipsisClassMember2, EllipsisClassContext),
	
	partial_match(EllipsisClassMember2, PrecedingResolvedSourceDiscourse1, Matched2, NonMatchedPrecedingResolvedSourceDiscourse),
	%partial_match(EllipsisClassContext, NonMatchedPrecedingResolvedSourceDiscourse, _Matched3, _NonMatched2),
	partial_match(EllipsisClassContext, PrecedingResolvedSourceDiscourse1, _Matched3, _NonMatched2),

	align_roles_if_possible(CurrentSourceDiscourse2, Matched2),

	resolution_score(EllipsisClassMember2, EllipsisClassContext, Matched2, Matched1, ResolutionScore),
	
	append(NonMatchedPrecedingResolvedSourceDiscourse, CurrentSourceDiscourse2, ResolvedSourceDiscourse0),
	(   is_list(ResolvedSourceDiscourse0) ->
	    sort(ResolvedSourceDiscourse0, ResolvedSourceDiscourse)
	;
	    otherwise ->
	    format2error('~N*** Error in resolution: non-list resolved representation: ~w~n', [ResolvedSourceDiscourse0]),
	    fail
	).
perform_resolution1(CurrentSourceDiscourse, PrecedingResolvedSourceDiscourse, Wh,
		    ResolvedSourceDiscourse, 
		    ellipsis_substitution(Id, FromTo), LengthReplaced) :-
	remove_clause_from_representation(PrecedingResolvedSourceDiscourse, PrecedingClause, PrecedingResolvedSourceDiscourseWithoutClause),
	(   PrecedingClause = (Role=PrecedingClauseBody) ->
	    perform_resolution1(CurrentSourceDiscourse, PrecedingClauseBody, Wh,
				ResolvedClause, 
				ellipsis_substitution(Id, FromTo), LengthReplaced),
	    ResolvedSourceDiscourse = [Role=[clause, ResolvedClause] | PrecedingResolvedSourceDiscourseWithoutClause]
	;
	    otherwise ->
	    perform_resolution1(CurrentSourceDiscourse, PrecedingClause, Wh,
				ResolvedClause, 
				ellipsis_substitution(Id, FromTo), LengthReplaced),
	    ResolvedSourceDiscourse = [[clause, ResolvedClause] | PrecedingResolvedSourceDiscourseWithoutClause]
	).
% Add content of elliptical phrase to content of preceding source discourse.
% Since this matches no material, and we prioritise matching as much as possible,
% resolutions performed using this method will always be dispreferred compared
% to resolutions performed using substitution.
perform_resolution1(CurrentSourceDiscourse, PrecedingResolvedSourceDiscourse, _Wh,
		    ResolvedSourceDiscourse,
		    ellipsis_addition(CurrentSourceDiscourse1), NegLengthReplaced) :-
	ellipsis_processing_is_activated,
	remove_phrase_utterance_marking_and_uninstantiate_null_roles(CurrentSourceDiscourse, CurrentSourceDiscourse1),
	       
	NegLengthReplaced = 0,
	
	append(PrecedingResolvedSourceDiscourse, CurrentSourceDiscourse1, ResolvedSourceDiscourse0),
	sort(ResolvedSourceDiscourse0, ResolvedSourceDiscourse).

full_match_against_ellipsis_class_example(SourceDiscourse, Id, Matched) :-
	user:ellipsis_class_example(EllipsisClassMember, Id),
	full_match(EllipsisClassMember, SourceDiscourse, Matched).

get_ellipsis_class_and_context(wh, Id, EllipsisClassMember, EllipsisClassContext) :-
	user:ellipsis_class_example(EllipsisClassMember, wh-Id),
	EllipsisClassContext = [].
get_ellipsis_class_and_context(wh, Id, EllipsisClassMember, EllipsisClassContext) :-
	user:ellipsis_class_example([EllipsisClassMember, EllipsisClassContext], wh_with_context-Id).
get_ellipsis_class_and_context(non_wh, Id, EllipsisClassMember, EllipsisClassContext) :-
	user:ellipsis_class_example(EllipsisClassMember, Id),
	EllipsisClassContext = [].

%----------------------------------------------------------------------

full_match(List1, List2, Matched) :-
	partial_match(List1, List2, Matched, []).

partial_match([], Rest, [], Rest) :-
	!.
partial_match([F | R], [F1 | R1], [F1 | MatchedRest], Rest) :-
	element_match(F, F1),
	partial_match(R, R1, MatchedRest, Rest).
partial_match(List1, [F1 | R1], Matched, [F1 | Rest]) :-
	partial_match(List1, R1, Matched, Rest).

% Ellipsis records were previously stored in ground form.
% Now they are stored in partially uninstantiated form.
%element_match([Type, _Val1], [Type, _Val2]).
element_match([Type, Val], [Type, Val]).
element_match(_Role1=[Type, Val], _Role2=[Type, Val]).

%----------------------------------------------------------------------
% If all the roles in the matched phrase are Role, and Role is neither a var or null, 
% set all the uninstantiated roles in the elliptical phrase to Role.

align_roles_if_possible(Elliptical, Matched) :-
	all_roles_same(Matched, Role),
	Role \== null,
	instantiate_uninstantiated_roles(Elliptical, Role),
	!.
align_roles_if_possible(_Elliptical, _Matched).

all_roles_same([], _Role).
all_roles_same([Role1=_ | Rest], Role) :-
	nonvar(Role1),
	Role = Role1,
	!,
	all_roles_same(Rest, Role).

instantiate_uninstantiated_roles([], _Role).
instantiate_uninstantiated_roles([Role=_ | R], Role) :-
	instantiate_uninstantiated_roles(R, Role).
instantiate_uninstantiated_roles([_F | R], Role) :-
	instantiate_uninstantiated_roles(R, Role).

%----------------------------------------------------------------------

resolution_score(EllipsisClassMember2, EllipsisClassContext, Matched, ReplacedBy, ResolutionScore) :-	
	length(EllipsisClassMember2, MainLengthReplaced),
	length(EllipsisClassContext, ContextLength),
	       
	resolution_preference_score(Matched, PreferenceScore),
	clause_non_clause_score(Matched, ReplacedBy, ClauseNonClauseScore),
	
	ResolutionScore is -1 * ( MainLengthReplaced + ContextLength + PreferenceScore + ClauseNonClauseScore ),
	!.

% Disprefer resolutions that introduce or eliminate clauses
clause_non_clause_score(Matched, ReplacedBy, ClauseNonClauseScore) :-
	(   ( representation_contains_clause(Matched), representation_contains_clause(ReplacedBy) ) ->
	    ClauseNonClauseScore = 0
	;
	    ( \+representation_contains_clause(Matched), \+representation_contains_clause(ReplacedBy) ) ->
	    ClauseNonClauseScore = 0
	;
	    otherwise ->
	    ClauseNonClauseScore = -1
	).

representation_contains_clause(List) :-
	member([clause, _], List),
	!.
representation_contains_clause(List) :-
	member(_Role=[clause, _], List),
	!.

resolution_preference_score(Matched, PreferenceScore) :-
	current_predicate(user:resolution_preference/2),
	findall(Score,
		resolution_preference_result(Matched, Score),
		Scores),
	safe_sum_list(Scores, PreferenceScore),
	!.
resolution_preference_score(_Matched, PreferenceScore) :-
	PreferenceScore = 0.

resolution_preference_result(Matched, Score) :-
	user:resolution_preference(PreferencePattern, Score),
	member(PreferencePattern, Matched).

%----------------------------------------------------------------------

remove_phrase_utterance_marking_and_uninstantiate_null_roles(SourceDiscourseIn, SourceDiscourseOut) :-
	remove_phrase_utterance_marking(SourceDiscourseIn, SourceDiscourseNext),
	uninstantiate_null_roles(SourceDiscourseNext, SourceDiscourseOut).

remove_phrase_utterance_marking(SourceDiscourse, SourceDiscourse1) :-
	length(SourceDiscourse, OriginalLength),
	findall(Length-ShortenedSourceDiscourse,
		(   remove_phrase_utterance_marking1(SourceDiscourse, ShortenedSourceDiscourse),
		    length(ShortenedSourceDiscourse, Length)
		),
		Pairs),
	keysort(Pairs, SortedPairs),
	SortedPairs = [NewLength-SourceDiscourse1 | _Rest],
	NewLength < OriginalLength,
	!.

% In fact, remove possible interjection as well
remove_phrase_utterance_marking1(CurrentSourceDiscourse, CurrentSourceDiscourse2) :-
	get_utterance_marking(phrase, PhraseUtteranceMarking),
	list_to_ord_set(CurrentSourceDiscourse, CurrentSourceDiscourseOS),
	list_to_ord_set(PhraseUtteranceMarking, PhraseUtteranceMarkingOS),
	ord_subset(PhraseUtteranceMarkingOS, CurrentSourceDiscourseOS),
	ord_subtract(CurrentSourceDiscourseOS, PhraseUtteranceMarkingOS, CurrentSourceDiscourse1),
	remove_interjections(CurrentSourceDiscourse1, CurrentSourceDiscourse2),
	!.

%----------------------------------------------------------------------

remove_interjections(In, Out) :-
	get_utterance_marking(interjection_tag, InterjectionTag),
	remove_interjections1(In, InterjectionTag, Out),
	!.
remove_interjections(In, Out) :-
	format2error('~N*** Error: bad call: ~w~n',
		     [remove_interjections(In, Out)]),
	fail.

remove_interjections1([], _Tag, []).
remove_interjections1([F | R], Tag, R1) :-
	(   safe_subsumes_chk([Tag, _], F)
	;
	    safe_subsumes_chk(_Role=[Tag, _], F)
	),
	!,
	remove_interjections1(R, Tag, R1).
remove_interjections1([F | R], Tag, [F | R1]) :-
	!,
	remove_interjections1(R, Tag, R1).

%----------------------------------------------------------------------

remove_clause_from_representation([Role=[clause, Clause] | Rest], Role=Clause, Rest) :-
	!.
remove_clause_from_representation([[clause, Clause] | Rest], Clause, Rest) :-
	!.
remove_clause_from_representation([F | R], Clause, [F | R1]) :-
	remove_clause_from_representation(R, Clause, R1).

%----------------------------------------------------------------------

uninstantiate_null_roles([], []).
uninstantiate_null_roles([F | R], [F1 | R1]) :-
	uninstantiate_null_role(F, F1),
	!,
	uninstantiate_null_roles(R, R1).

uninstantiate_null_role(null=Item, _AnonymousRole=Item) :-
	!.
uninstantiate_null_role(Other, Other).

%----------------------------------------------------------------------

change_whq_to_ynq(ResolvedSourceDiscourse, ResolvedSourceDiscourse1) :-
	change_utterance_type(ResolvedSourceDiscourse, whq, ynq, ResolvedSourceDiscourse1).

change_whq_to_dcl(ResolvedSourceDiscourse, ResolvedSourceDiscourse1) :-
	change_utterance_type(ResolvedSourceDiscourse, whq, dcl, ResolvedSourceDiscourse1).

change_ynq_to_dcl(ResolvedSourceDiscourse, ResolvedSourceDiscourse1) :-
	change_utterance_type(ResolvedSourceDiscourse, ynq, dcl, ResolvedSourceDiscourse1).

change_utterance_type(ResolvedSourceDiscourse, Type, Type1, ResolvedSourceDiscourse1) :-
	get_utterance_marking(Type, TypeUtteranceMarking),
	get_utterance_marking(Type1, Type1UtteranceMarking),
	
	list_to_ord_set(ResolvedSourceDiscourse, ResolvedSourceDiscourseOS),
	list_to_ord_set(TypeUtteranceMarking, TypeUtteranceMarkingOS),
	ord_subtract(ResolvedSourceDiscourseOS, TypeUtteranceMarkingOS, ResolvedSourceDiscourseOSMinusType),

	ord_union(Type1UtteranceMarking, ResolvedSourceDiscourseOSMinusType, ResolvedSourceDiscourse1),
	!.
change_utterance_type(ResolvedSourceDiscourse, Type, Type1, ResolvedSourceDiscourse1) :-
	format2error('~N*** Error: bad call: ~w~n',
		     [change_utterance_type(ResolvedSourceDiscourse, Type, Type1, ResolvedSourceDiscourse1)]),
	fail.

%----------------------------------------------------------------------

change_you_to_i(Representation, Representation1) :-
	replace_element_recursively(Representation, you, i, Representation1).

replace_element_recursively(Representation, Type, Type1, Representation1) :-
	get_utterance_marking(Type, [Marking]),
	get_utterance_marking(Type1, [Marking1]),
	replace_element_recursively1(Representation, Marking, Marking1, Representation1).
replace_element_recursively(Representation, Type, Type1, Representation1) :-
	format('~N*** Error: bad call: ~w~n',
	       [replace_element_recursively(Representation, Type, Type1, Representation1)]),
	fail.

replace_element_recursively1([], _Marking, _Marking1, []).
replace_element_recursively1([F | R], Marking, Marking1, [F1 | R1]) :-
	replace_element_recursively2(F, Marking, Marking1, F1),
	!,
	replace_element_recursively1(R, Marking, Marking1, R1).

replace_element_recursively2(Role=Content, _=Content, _=Content1, Role=Content1) :-
	!.
replace_element_recursively2(Marking, Marking, Marking1, Marking1) :-
	!.
replace_element_recursively2(Role=[clause, Clause], Marking, Marking1, Role=[clause, Clause1]) :-
	replace_element_recursively1(Clause, Marking, Marking1, Clause1),
	!.
replace_element_recursively2([clause, Clause], Marking, Marking1, [clause, Clause1]) :-
	replace_element_recursively1(Clause, Marking, Marking1, Clause1),
	!.
replace_element_recursively2(Other, _Marking, _Marking1, Other).

%----------------------------------------------------------------------

whq_representation(Representation) :-
	get_utterance_marking(whq, WHQUtteranceMarking),
	list_to_ord_set(Representation, RepresentationOS),
	list_to_ord_set(WHQUtteranceMarking, WHQUtteranceMarkingOS),
	ord_subset(WHQUtteranceMarkingOS, RepresentationOS),
	!.

get_utterance_marking(Type, Value) :-
	utterance_marking_config_file_tag_and_default_value(Type, Tag, Default),
	(   user:regulus_config(Tag, Value0) ->
	    substitute_in_term(Value0, '*uninstantiated*', _NewVar, Value) ;

	    Value = Default
	),
	
	!.

% utterance_marking_config_file_tag_and_default_value(Type, Tag, Default)

utterance_marking_config_file_tag_and_default_value(phrase, phrase_utterance_marking, [[utterance_type, phrase]]).
utterance_marking_config_file_tag_and_default_value(whq, whq_utterance_marking, [[utterance_type, whq]]).
utterance_marking_config_file_tag_and_default_value(ynq, ynq_utterance_marking, [[utterance_type, ynq]]).
utterance_marking_config_file_tag_and_default_value(dcl, dcl_utterance_marking, [[utterance_type, dcl]]).
utterance_marking_config_file_tag_and_default_value(you, second_person_pro_element, [[pronoun, you]]).
utterance_marking_config_file_tag_and_default_value(i, first_person_pro_element, [[pronoun, i]]).
% The interjection_tag is just an atom...
utterance_marking_config_file_tag_and_default_value(interjection_tag, interjection_tag, interjection).