/* ------------------------------------------------------------------------------ */

?- use_module(library(lists)).
?- consult('fc.pl').

/* ------------------------------------------------------------------------------ */

buildTrEx(TrExNum, TrExNum, [TrainingExample]) :-
  !, findall(fact(FName,FArgs), fact(TrExNum,FName,FArgs), TrainingExample).
buildTrEx(TrExNum, Current, [TrainingExample|Rest]) :-
  !, findall(fact(FName,FArgs), fact(Current,FName,FArgs), TrainingExample),
  New is Current+1,
  buildTrEx(TrExNum, New, Rest).

buildTargetRule([TrainEx|Rest], TargetRule, RestTrEx) :-
  fact(0, FName, FArgs),
  \+ member(fact(FName, FArgs), TrainEx), !,
  buildTargetRule(Rest, TargetRule, RestTrEx).
buildTargetRule([TrainEx|Rest], rule(Premises, TargetFact), Rest) :-
  fact(0, FName, FArgs),
  TargetFact = fact(FName, FArgs),
  disp(TrainEx), nl,
  delete(TrainEx, TargetFact, Premises),
  write('h: '), dispRule(rule(Premises, TargetFact)), nl.

dispPred(_, []).
dispPred(first, [fact(FName,FArgs)|Rest]) :-
  write(FName), write('('), dispArgs(FArgs), write(')'),
  dispPred(notFirst, Rest).
dispPred(notFirst, [fact(FName,FArgs)|Rest]) :-
  write(' ^ '), write(FName), write('('), dispArgs(FArgs), write(')'),
  dispPred(notFirst, Rest).
dispRule(rule(Premises, TargetFact)) :-
  dispPred(first, Premises),
  write(' -> '), dispPred(first, [TargetFact]).

build :-
  consult('input.txt'),tell('output.txt'),
  write('Trace of the machine learning algorithm applied to the example: '),
  fact(0, FName, FArgs), disp([fact(FName, FArgs)]), nl,
  reset,
  trainingExamples(TrExNum),
  buildTrEx(TrExNum, 1, TrExList),
  buildTargetRule(TrExList, TargetRule, RestTrExList),
  ml(RestTrExList, TargetRule, _FinalRule),
  nl, write('This is the target function'), nl, nl, told.

/* ------------------------------------------------------------------------------ */

/*
 * -- newVariable(BrandNewVariable)
 *      newVariable returns var(N) for increasing values of N for each invocation
 *
*/

:- dynamic counter/1.

counter(0).
reset :-
  retract(counter(_)),
  assert(counter(0)).

newVariable(var(X)) :-
  retract(counter(Y)),
  X is Y+1,
  assert(counter(X)).

/* ------------------------------------------------------------------------------ */

/*
 * Machine Learning
 *
 * -- ml([training_example], tmpRule, finalRule).
 *
*/

ml([], Rule, Rule).

ml([TrainEx|Rest], CurrentRule, FinalRule) :-
  CurrentRule = rule(_Premises, fact(FName, FArgs)), 
  \+ member(fact(FName, FArgs), TrainEx), !,
  nl, write('Negative training example:'),
  nl, write('--------------------------'),
  nl, disp(TrainEx), nl,
  write('h: '), dispRule(CurrentRule), nl, nl,
  ml(Rest, CurrentRule, FinalRule).

ml([TrainEx|Rest], CurrentRule, FinalRule) :-
  CurrentRule = rule(Premises, fact(Fname,Fargs)), 
  NewRule = rule(NewPremises, fact(Fname,Fargs)),
  nl, write('Positive training example:'),
  nl, write('--------------------------'),
  nl, disp(TrainEx), nl,
  generalise(Premises, NewPremises, TrainEx),
  nl, write('h: '), dispRule(NewRule), nl, nl,
  ml(Rest, NewRule, FinalRule).

/* ------------------------------------------------------------------------------ */

/*
 * -- generalise(Premises, NewPremises, TrainingExample)
 *
*/

generalise(Premises, NewPremises, TrainingExample) :-
  gen(Premises, NewPremises, TrainingExample, [], []).

?- dynamic currentPredicate/1.
currentPredicate(0).


/*
 * gen(Prem, NewPrem, TrEx, OldV, NewV)
 *
*/

/* -- Reached end of PREMISES -- */
gen([], [], _TrEx, _OldV, _NewV).

/* -- predicate FName doesn't exist in Training Example -- */
gen([fact(FName,_FArgs1)|Rest1], Rest2, TrEx, OldV, NewV) :-
  \+ member(fact(FName,_FArgs2), TrEx),
  write('  - Get rid of '), write(FName), write(' completely'), nl,
  gen(Rest1, Rest2, TrEx, OldV, NewV).

/* -- predicate FName does exist in Training Example -- */
gen([fact(FName,FArgs1)|Rest1], [fact(FName,FArgs2)|Rest2], TrEx, OldV, NewV) :-
  member(fact(FName, Consts), TrEx),
  length(FArgs1, Length),
  length(Consts, Length),
  retract(currentPredicate(_)), assert(currentPredicate(FName)),
  genArgs(FArgs1, FArgs2, Consts, OldV, NewV, OldV2, NewV2),
  gen(Rest1, Rest2, TrEx, OldV2, NewV2).

/* ------------------------------------------------------------------------------ */

/*
 * genArgs(Args1, Args2, Consts, OldV, NewV, OldV2, NewV2)
 *
*/

/* -- Case 1: Reached end of ARGUMENTS -- */
genArgs([], [], [], OldV, NewV, OldV, NewV).                               

/* -- Case 2: the argument is the same (CONST) as the constant -- */
genArgs([Const|Rest1], [Const|Rest2], [Const|RestC], OldV, NewV, OldV2, NewV2) :-
  /* ignore this case, move on to the next item */
  !, genArgs(Rest1, Rest2, RestC, OldV, NewV, OldV2, NewV2).

/* -- ARGUMENT is a variable [Cases 3, 4] -- */

/* -- Case 3: the argument is a variable that NewV binds to other than (Const) -- */
genArgs([var(A)|Rest1], [NewVar|Rest2], [Const|RestC], OldV, NewV, OldV2, NewV2) :-
  member(bind(var(A), OtherConst), NewV),
  \+ OtherConst = Const, !,
  /* create new variable NEWVAR */
  newVariable(NewVar),
  /* NEWVALUES now binds NEWVAR to CONST */
  UpdatedNewV = [bind(NewVar, Const)| NewV],
  genArgs(Rest1, Rest2, RestC, OldV, UpdatedNewV, OldV2, NewV2).

/* -- Case 4: the argument is a variable -- */
genArgs([var(A)|Rest1], [var(A)|Rest2], [Const|RestC], OldV, NewV, OldV2, NewV2) :-
  /* NEWVALUES now binds VAR(A) to CONST */
  !, UpdatedNewV = [bind(var(A), Const)| NewV],
  genArgs(Rest1, Rest2, RestC, OldV, UpdatedNewV, OldV2, NewV2).

/* -- ARGUMENT is a different constant [Cases 5, 6] -- */

/* -- Case 5: -- */
/*    + some variable (Var) bound to constant (Const) in NEWVALUES -- */
/*    + some variable (Var) bound to argument (Arg1) in OLDVALUES  -- */
genArgs([Arg1|Rest1], [Var|Rest2], [Const|RestC], OldV, NewV, OldV2, NewV2) :-
  /* replace ARG1 by VAR in ARGUMENTS */
  member(bind(Var,Const), NewV),
  member(bind(Var,Arg1), OldV),
  write('  - Realise '), write(Arg1), write(' has already been changed for '),
  currentPredicate(FName),
  write('"'), write(FName), write('" predicate'), nl,
  tab(6), write('replace it with '), write(Var), write(' to remain consistent'),
  write(' with current variable bindings'), nl,
  genArgs(Rest1, Rest2, RestC, OldV, NewV, OldV2, NewV2).

/* -- Case 6: the argument is a different constant -- */
genArgs([Arg1|Rest1], [NewVar|Rest2], [Const|RestC], OldV, NewV, OldV2, NewV2) :-
  /* replace ARG1 by NEWVAR in ARGUMENTS */
  newVariable(NewVar),
  write('  - Replace '), write(Arg1), write(' with variable '), write(NewVar),
  currentPredicate(FName),
  write(' in "'), write(FName), write('" predicate'), nl,
  UpdatedOldV = [bind(NewVar,Arg1)|OldV],
  UpdatedNewV = [bind(NewVar,Const)|NewV],
  genArgs(Rest1, Rest2, RestC, UpdatedOldV, UpdatedNewV, OldV2, NewV2).

/* -- End of Machine Learning -- */

/* ------------------------------------------------------------------------------ */
