% p(Var,Obs,Dist) is true if Dist represntes the
% probability distribution of P(Var|Obs)

p(Var,Obs,Dist) :-
   member(Var=Val,Obs),!,
   values(Var,ValList),
   make_obs_dist(ValList,Val,Dist).

p(Var,Obs,Dist) :-
   partition_obs(Var,Obs,Desc,NonDesc,Children),
   condition_nondesc(Var,NonDesc,Dist),
   
% condition_nondesc(Var,Obs,Dist) means Dist =
% P(Var|Obs) & we know there are no descendents in Obs.
condition_nondesc(Var,Obs,Dist) :-
   parents(Var,Pars),
   get_dists(Pars,[],RPars,Obs,[],PDists),
   values(Var,Values),
   make_zero_dist(Values,ZeroD),
   multipy_dists(RPars,PDists,Var,[],1,ZeroD,Dist).

% make_obs_dist(Vals,Val,Dist) created distribution
% Dist corresponding to the observation of value Val,
% where Vals is the list of all values.
make_obs_dist([],_,[]).
make_obs_dist([Val|R],Val,[1|D]) :-
   !,
   make_obs_dist(R,Val,D).
make_dist([_|R],Val,[0|D]) :-
   make_dist(R,Val,D).


% partition_obs(Var,Obs,Desc,NonDesc,Children),
partition_obs(_,[],[],[],[]).
partition_obs(Var,[Obsv=Val|RObs],[Obsv=Val|D],ND,Ch1) :-
    descendent(Var,Obsv,Child),
    !,
    partition_obs(Var,RObs,D,ND,Ch0),
    insert(Child,Ch0,Ch1).
partition_obs(Var,[Obsv=Val|RObs],D,[Obsv=Val|ND],Ch) :-
    partition_obs(Var,RObs,D,ND,Ch).

descendent(Var,Obsv,Obsv) :-
   parents(Obsv,PObs),
   member(Var,PObs).

descendent(Var,Obsv,Par) :-
   parents(Obsv,PObs),
   member(PO,PObs),
   descendent(Var,PO,Par).

% Get the distributions for all parents -
% this is only correct if the graph is singly connected.
% This also reverses some lists so that we query the
% probability of variables in the correct order.
% get_dists(Pars,PAcc,RPars,Obs,DAcc,Dists)
% Par is the list of parents left to find distribution for
% PAcc is the list of parents whose distributions have been found
% PRev is the list all al parents in reverse order
% DAcc is the distributions for the elements of Pacc
% Dists is the list of distributions corresponding to PRev
get_dists([],RP,RP,_,PD,PD).
get_dists([V|R],PAcc,PRev,Obs,DistAcc,Dist) :-
   p(V,Obs,VD),
   get_dists(R,[V|PAcc],PRev,Obs,[VD|DistAcc],Dist).

% make_zero_dist(Values,ZeroD) makes a Zero
% distribution the same length as the list Values
make_zero_dist([],[]).
make_zero_dist([_|L],[0|R]) :-
   make_zero_dist(L,R).


% multiply_dists(Vars,Dists,Par,Context,Weight,Dist0,Dist1).
% updates the distribution for variable Par from Dist0 to Dist1
% Where Context is an assignment of values to children of Par,
% and Vars is the children not assigned a value in Context,
% Dists is the list of distributions of elements of Vars.
% Weight is the (possibly unnormalized) probabilty of the context.

multiply_dists([],[],Par,Context,Weight,Dist0,Dist1) :-
   pr(Par,Context,Dist),
   weight_dist(Weight,Dist,Dist0,Dist1).
multiply_dists([CVar|RVars],[Dist|RDists],Par,Context,Weight,Dist0,Dist1) :-
   values(CVar,CVals),
   expand_context(CVals,Dist,CVar,RVars,RDists,Par,Context,Weight,Dist0,Dist1).

% expand_context(CVals,Dist,CVar,RVars,RDists,Par,Context,Weight,Dist0,Dist1).
% CVals is a list of values for variable CVar that is a child of Par,
% and Dist is a list corresponding to the probabilities of the values in CVals.
% RVars is the remaining variables to be assigned a value, and
% RDists is the list of their distributions.
expand_context([],[],_,_,_,_,_,_,W,W).
expand_context([Val1|RVals],[P1|RPs],CVar,RVars,RDists,Par,Context,W,Dist0,Dist2) :-
   We1 is W * P1,
   multiply_dists(RVars,RDists,Par,[CVar=Val1|Context],We1,Dist0,Dist1),
   expand_context(RVals,RPs,CVar,RVars,RDists,Par,Context,W,Dist1,Dist2).

   


% weight_dist(Weight,Dist,AccDist,WDist)  WDist is
% list Dist mutiplied by scalar Weight added onto the
% accumulated distribution.
weight_dist(_,[],[],[]).
weight_dist(Weight,[N|R],[A|RA],[P|WR]) :-
   P is N*Weight+A,
   weight_dist(Weight,R,RA,WR).

%%%%%%%%%%%%%%%%%%%%%%%%
% STANDARD DEFINITIONS %
%%%%%%%%%%%%%%%%%%%%%%%%
% member(X,L) is true if X is a member of list L
member(X,[X|_]).
member(X,[_|L]) :-
   member(X,L).


insert(A,L,L) :-
   member(A,L),!.
insert(A,L,[A|L]).
