:- $typeutils.addtypesearchpath("Game", "Assembly-CSharp").
:- $typeutils.addtypesearchpath("Game.Services", "Assembly-CSharp").
:- $typeutils.addtypesearchpath("Game.Systems.Values", "Assembly-CSharp").
:- $typeutils.addtypesearchpath("Game.Session.Board", "Assembly-CSharp").
:- $typeutils.addtypesearchpath("Game.Session.Sim", "Assembly-CSharp").
:- $typeutils.addtypesearchpath("Game.Session.Entities.Components", "Assembly-CSharp").
:- $typeutils.addtypesearchpath("Game.Session.Entities.Config", "Assembly-CSharp").


problems :-
   step_limit(10000000),
   all(P, problem(P), Problems),
   forall(member(P, Problems), writeln(P)).

problem(P) :-
   unit(U),
   unit_problem(U, P).

problem(P) :-
   workplace(W),
   workplace_problem(W, P).

problem(P) :-
   residence(R),
   residence_problem(R, P).

%%%
%%% Residence problems
%%%

residence_problem(R, no_matching_movein(R)) :-
   \+ matches_residential_movein(R, _).

matches_residential_movein(R, M) :-
   RTags is R.unit.tags,
   MoveIns is $'Game'.serv.globals.settings.economy.moveins,
   member(M, MoveIns),
   M.instant =\= null,
   member(Tag, RTags),
   element(Tag, M.instant.tags),
   R.placement.size =:= M.size.

residence_problem(R, P) :-
   all(W, residence_waitlist(R, W), L),
   residence_waitlist_problem(R, L, P).

residence_waitlist(R, W) :-
   element(W, $'Game'.serv.globals.settings.residents.waitlists),
   expression_values_intersect(W.tags, R.unit.tags).

waitlist_name(W, Name) :-
   Name is W.name.
waitlist_names(WList, NameList) :-
   maplist(waitlist_name, WList, NameList).

residence_waitlist_problem(R, [], residence_has_no_waitlist(R)).
residence_waitlist_problem(R, [First, Second | Rest], residence_has_multiple_waitlists(R, Names)) :-
   waitlist_names([First, Second | Rest], Names).

residence_waitlist_problem(R, [W], waitlist_has_no_locdesc(R, W)) :-
   W.locdesc =:= null.
residence_waitlist_problem(R, [W], waitlist_has_undefined_locdesc(R, W, LN)) :-
   localization_problem(W.locdesc, LN).

expression_values_intersect(Exp1, Exp2) :-
   element(E, Exp1),
   element(E, Exp2),
   !.



%%%
%%% Workplace problems
%%%

workplace_problem(Workplace, uncovered_work_time(Workplace, Sample)) :-
   Hours is Workplace.workplace.workhours,
   uncovered_time(Workplace, Hours, Sample).

workplace_problem(Workplace, undefined_worker_template(Workplace:Worker)) :-
   element(Worker, Workplace.workplace.workertemplates),
   \+ template_named(Worker, _).

workplace_problem(W, workplace_without_name(W)) :-
   \+ name_set(W).
workplace_problem(W, workplace_unset_locdesc(W)) :-
   \+ is_set(W.unit.locdesc).

% workplace_problem(W, workplace_unset_loccat(W)) :-
%    \+ is_set(W.unit.loccat).

workplace_problem(W, missing_in_locname(LN)) :-
   localization_problem(W.unit.locname, LN).

workplace_problem(W,missing_in_locpattern(LN)) :-
   localization_problem(W.unit.locpattern, LN).

workplace_problem(W, missing_in_locdesc(LN)) :-
   localization_problem(W.unit.locdesc, LN).

workplace_problem(W, missing_in_loccat(LN)) :-
   localization_problem(W.unit.loccat, LN).

workplace_problem(W, P) :-
   Tags is W.unit.tags,
   MoveIns is $'Game'.serv.globals.settings.economy.moveins,
   moveins_problem(W, Tags, MoveIns, P).

moveins_problem(W, Tags, MoveIns, no_matching_movein(W)) :-
   rentable_commercial_space(W),
   \+ matching_movein(W, Tags, MoveIns, _).

moveins_problem(W, Tags, MoveIns, conflicting_movein_matches(W, L)) :-
   rentable_commercial_space(W),
   all(M,
       matching_movein(W, Tags, MoveIns, M),
       L),
   length(L, N),
   N>1.

rentable_commercial_space(W) :-
   W.office =\= null.
rentable_commercial_space(W) :-
   Serv is W.service,
   Serv \= null,
   ServiceType is Serv.servicetype,
   ((ServiceType =:= $'ScoringType'.'Restaurant') ; (ServiceType =:= $'ScoringType'.'Retail')),
   Tags is W.unit.tags,
   \+ element("consultant", Tags).

matching_movein(W, Tags, MoveIns, Type:Size) :-
   member(Match, MoveIns),
   element(Entry, Match.entries),
   member(Tag, Tags),
   element(Tag, Entry.tags),
   Size is Match.size,
   Match.size =:= W.placement.size,
   Type is Match.type.

%%%
%%% Unit problems
%%%

unit_problem(U, P) :-
   utility_problem(U, P).
unit_problem(U, P) :-
   trash_problem(U, P).
unit_problem(U, unit_has_no_layout(U)) :-
   (workplace(U) ; residence(U)),
   Layouts is U.sprite.layouts,
   once((Layouts = null ; empty(Layouts))).
unit_problem(U, P) :-
   (workplace(U) ; residence(U)),
   Layouts is U.sprite.layouts,
   Layouts \= null,
   nonempty(Layouts),
   member(LName, Layouts),
   LName \= "layout-missing",
   LayoutDefinition is $'Game'.ctx.layouts.'GetLayoutsUnsafeOrNull'(LName, U),
   (LayoutDefinition = null -> P=undefined_layout(U, LName) ; layout_problem(U, LayoutDefinition, LName, P)).

layout_problem(U, LayoutDefinition, LName,
	       workplace_layout_missing_workstations(U, LName)) :-
   workplace(U),
   \+ nonnull_nonempty_expression(LayoutDefinition.workstations).

layout_problem(U, LayoutDefinition, LName,
	       residence_layout_missing_residentanchors(U, LName)) :-
   residence(U),
   \+ nonnull_nonempty_expression(LayoutDefinition.residentanchors).

layout_problem(U, LayoutDefinition, LName,
	       layout_missing_serviceanchors(U, LName)) :-
   nonnull_nonempty_expression(U.unit.needservices),
   \+ nonnull_nonempty_expression(LayoutDefinition.serviceanchors).

layout_problem(U, LayoutDefinition, LName,
	       layout_missing_visitoranchors(U, LName)) :-
   nonnull_nonempty_expression(U.unit.visitors),
   \+ nonnull_nonempty_expression(LayoutDefinition.visitoranchors).

layout_problem(U, LD, LN, undefined_sprite(U, LN, Type, Sprite)) :-
   undefined_sprite(U, LD, Type, Sprite).

undefined_sprite(U, LD, construction, Img) :-
   undefined_sprite_aux(U, LD.construction, Img).
undefined_sprite(U, LD, open, Img) :-
   undefined_sprite_aux(U, LD.open, Img).
undefined_sprite(U, LD, closed, Img) :-
   undefined_sprite_aux(U, LD.closed, Img).
undefined_sprite(U, LD, broken, Img) :-
   undefined_sprite_aux(U, LD.broken, Img).
undefined_sprite(U, LD, movein, Img) :-
   undefined_sprite_aux(U, LD.movein, Img).

undefined_sprite_aux(U, Expression, Img) :-
   call(List is Expression),
   List \= null,
   member(Sprite, List),
   Img is Sprite.img,
   $'Game'.ctx.materials.'GetSprite'(Sprite.atlas, U, Img) =:= null.

%% Utility problems.
utility_problem(U, unit_doesnt_care_about_need(U, NeedType)) :-
   Needs is U.unit.needutilities,
   Needs \= null,
   member(Need, Needs),
   NeedType is Need.type,
   \+ cares_about_utility(U, Need).

utility_problem(U, unit_cares_about_nonexistent_need(U, NeedType)) :-
   element(Mod, U.unit.satisfaction.mods),
   is_class(Mod, $'UtilitiesModifier'),
   NeedType is Mod.type,
   \+ needs_utility(U, NeedType).

cares_about_utility(U, Need) :-
   element(Mod, U.unit.satisfaction.mods),
   is_class(Mod, $'UtilitiesModifier'),
   Mod.type =:= Need.type.

needs_utility(U, NeedType) :-
   Needs is U.unit.needutilities,
   Needs \= null,
   member(Need, Needs),
   Need.type =:= NeedType.

%% Trash problems.
trash_problem(U, unit_generates_trash_but_has_no_collection_time(U)) :-
   TP is U.unit.trashproduced,
   TP \= null,
   nonempty(TP),
   U.unit.trashhour =:= 0.

trash_problem(U, unit_doesnt_care_about_trash(U, TrashItem)) :-
   TP is U.unit.trashproduced,
   TP \= null,
   member(Trash, TP),
   \+ cares_about_trash_produced(U, Trash),
   \+ is_base_template_name(U),
   TrashItem is Trash.item.

is_base_template_name(T) :-
   TemplateName is T.template,
   TemplateName.'EndsWith'("-base").

trash_problem(U, unit_cares_about_nonexistent_trash(U, TrashType)) :-
   element(Mod, U.unit.satisfaction.mods),
   is_class(Mod, $'TrashDeliveryModifier'),
   TrashType is Mod.type,
   \+ produces_trash(U, TrashType).

cares_about_trash_produced(U, Trash) :-
   element(Mod, U.unit.satisfaction.mods),
   is_class(Mod, $'TrashDeliveryModifier'),
   Trash.item =:= Mod.type.

produces_trash(U, Type) :-
   TP is U.unit.trashproduced,
   TP \= null,
   member(Trash, TP),
   Trash.item =:= Type.

nonnull_nonempty_expression(E) :-
   call(X is E),
   nonnull_nonempty(X).
nonnull_nonempty(IEnumerable) :-
   IEnumerable \= null,
   once(member(_, IEnumerable)).
nonempty(IEnumerable) :-
   once(member(_, IEnumerable)).
empty(IEnumerable) :-
   \+ member(_, IEnumerable).
null_or_empty(IEnumerable) :-
   IEnumerable = null, !.
null_or_empty(IEnumerable) :-
   empty(IEnumerable).

%%%
%%% Localization problems
%%%

localization_problem(NameExpression, LN) :-
   call(LN is NameExpression),
   LN \= null,
   \+ $'Game'.serv.loc.'HasKey'(LN).

name_set(W) :-
   is_set(W.unit.locname).
name_set(W) :-
   is_set(W.unit.locpattern).
		 
entity_manager(M) :-
   M is $'Game'.ctx.entityman.

template_dictionary(D) :-
   entity_manager(M),
   D is M.'GetAllTemplatesUnsafe'().

template(T) :-
   nonvar(T),
   % Test T is a template
   is_class(T, $'EntityTemplate').
template(T) :-
   var(T),
   % Enumerate all templates
   template_dictionary(D),
   member(P, D),
   T is P.'Value'.

template_named(Name, T) :-
   entity_manager(M),
   T is M.'FindTemplate'(Name),
   T \= null.

peep(T) :-
   template(T),
   T.peep =\= null.

workplace(T) :-
   template(T),
   T.workplace =\= null.

residence(T) :-
   template(T),
   T.residence =\= null,
   \+ residence_base(T).

residence_base(R) :-
   R.placement.size.x =:= 1.


unit(T) :-
   template(T),
   T.unit =\= null.

workplace_worker(WorkplaceTemplate, WorkerTemplate) :-
   workplace(WorkplaceTemplate),
   L is WorkplaceTemplate.workplace.workertemplates,
   member(Name, L),
   template_named(Name, WorkerTemplate).

peep_schedule(T, S) :-
   S is T.peep.schedule.

% Eval Expression and test that it's non-null.
is_set(Expression) :-
   call(Expression =\= null).

% Element is just member, but it evals its second argument.
element(Element, ListExpression) :-
   call((L is ListExpression)),
   member(Element, L).

time_sample(T) :-
   member(T,
	  [0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5, 5.5, 6, 6.5, 7, 7.5, 8, 8.5, 9, 9.5, 10, 10.5, 11, 11.5,
	   12, 12.5, 13, 13.5, 14, 14.5, 15, 15.5, 16, 16.5, 17, 17.5, 18, 18.5, 19, 19.5, 20, 20.5, 21, 21.5, 22, 22.5, 23, 23.5]).

worker(Workplace, Worker) :-
   element(WName, Workplace.workplace.workertemplates),
   template_named(WName, Worker).

worker_schedule(Worker, Schedule) :-
   Schedule is $'Game'.serv.globals.settings.schedules.'FindSchedule'(Worker.peep.schedule).

worker_work_block(Worker, Block) :-
   worker_schedule(Worker, Schedule),
   element(Block, Schedule.blocks),
   once( (element(Task, Block.tasks),
	  Task.'StartsWith'("go-work")) ).

uncovered_time(Workplace, Interval, Sample) :-
   interval_contains(Interval, Sample),
   \+ covered_time(Workplace, Sample).

covered_time(Workplace, Sample) :-
   worker(Workplace, Worker),
   worker_work_block(Worker, Block),
   interval_contains(Block, Sample).

%%
%% Interval operations
%%

interval_contains(I, Sample) :-
   I.from < I.to,
   time_sample(Sample),
   I.from =< Sample,
   I.to > Sample.
interval_contains(I, Sample) :-
   I.from > I.to,
   time_sample(Sample),
   ( Sample >= I.from; Sample < I.to ).

intervals_intersect(I1, I2) :-
   interval_contains(I1, S),
   interval_contains(I2, S),
   !.

interval_covering(Interval, ListOfIntervals) :-
   forall(interval_contains(Interval, S),
	  (member(I, ListOfIntervals),
	   interval_contains(S, I))).