/*
  This file is part of the BJP package.

  BJP package is free software: you can redistribute it and/or modify it
  under the terms of the GNU Lesser General Public License as published by the
  Free Software Foundation, either version 3 of the License, or (at your
  option) any later version.

  BJP package is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
  License for more details.

  You should have received a copy of the GNU Lesser General Public License
  along with BJP package.  If not, see <http://www.gnu.org/licenses/>.
 */
package learn.struct.scoring.bjp;


import java.util.ArrayList;
import java.util.List;

import learn.struct.it.IndependenceTriplet;
import learn.struct.scoring.GraphScorable;
import learn.struct.scoring.UndirectedGraphScore;
import learn.struct.it.BayesianTest;
import rep.UndirectedGraph;
import util.math.JGraphtUtils;
import dataset.Dataset;
import dataset.adtree.ADNode;


/**
 * Implements the IBMAP scoring function for
 * Markov networks structure discovery, proposed by (Schluter et al, 2014).
 * 
 * <!-- technical-plaintext-start --> Schlüter, Federico, Facundo Bromberg, and Alejandro Edera. 
 * "Blankets Joint Posterior for Markov network structure learning" 
 * arXiv preprint arXiv:XXXX.XXXX (2016).  
 * <!-- * technical-plaintext-end -->
 * <p/>
 */
public class BJP implements GraphScorable {

	private boolean onlyIndependences = false;
	private BayesianTest test; 

	public BJP() {
		this(false);
		test = new BayesianTest();

	}
	
	public BJP(boolean onlyIndependences) {
		this.onlyIndependences = onlyIndependences;
		test = new BayesianTest();
	}  

	/* (non-Javadoc)
	 * @see mn.learn.ib.algs.ibmap.score.Scorable#computeScore(mn.rep.UndirectedGraph, data.Dataset)
	 */
	public UndirectedGraphScore computeScore(UndirectedGraph g,  Dataset dataset) {
		UndirectedGraphScore gScore = new UndirectedGraphScore(g);
		ArrayList<IndependenceTriplet> closure = getClosureOf(g);
		int n = g.getNumberOfNodes();
		double[][] markovBlanketClosureScores = new double[n][n];
		double score = 0;
		long testCounts = 0;
		long weightedTestCounts = 0;

		for (IndependenceTriplet triplet : closure) {
			int x = triplet.getX().get(0);
			int y = triplet.getY().get(0);
			test.independent(dataset, triplet);
			++testCounts;
			weightedTestCounts+=triplet.getX().size()+triplet.getY().size()+triplet.getZ().size();

			double[] logProbs = test.getLogProbs();
			if (g.existEdge(x, y)) {
				//log probability of dependence model
				score+=logProbs[1];
				markovBlanketClosureScores[x][y] = logProbs[1];
			} else {
				//log probability of independence model
				score+=logProbs[0];
				markovBlanketClosureScores[x][y] = logProbs[0];
			}
		}
		gScore.setMarkovBlanketClosureScores(markovBlanketClosureScores);
		gScore.setTotalScore(score);
		gScore.setTestCounts(testCounts);
		gScore.setWeightedTestCounts(weightedTestCounts);

		return gScore;
	}


	@Override
	public UndirectedGraphScore computeScore(UndirectedGraph g,
			Dataset dataset, boolean useADTree, ADNode aDTree) {
		this.test.setUseADTree(useADTree);
		this.test.setADTree(aDTree);
		return computeScore(g, dataset); //cache is implemented in the Bayesian test class
	}
	
	/* (non-Javadoc)
	 * @see mn.learn.ib.algs.ibmap.score.Scorable#computeScore(mn.rep.UndirectedGraph, mn.learn.ib.algs.ibmap.UndirectedGraphScore, data.Dataset)
	 */
	public UndirectedGraphScore computeScore(UndirectedGraph g, UndirectedGraphScore incremental, Dataset dataset) {
		return computeScore(g, dataset); // THERE IS NOT AN INCREMENTAL VERSION DESIGNED YET
	}
	
	/**
	 * This method return the Markov blanket closure of graph g
	 * 
	 * @param g
	 * @return
	 */
	private ArrayList<IndependenceTriplet> getClosureOf(UndirectedGraph g) {
		
		String[] sortedNodes = JGraphtUtils.sortNodesByDegreeInAscendingOrder(g);
		
		ArrayList<IndependenceTriplet> closure = new ArrayList<IndependenceTriplet>();
		for (int x = 0; x < g.getNumberOfNodes() -1 ; ++x) {
			for (int y = x+1; y < g.getNumberOfNodes(); ++y) {
				if (x != y) {
					List<Integer> blanketOfX = g.getBlanketOf(new Integer(sortedNodes[x]));
					blanketOfX.remove(new Integer(sortedNodes[y]));
					IndependenceTriplet triplet = new IndependenceTriplet(new Integer(sortedNodes[x]), new Integer(sortedNodes[y]), blanketOfX);

					/*
					 * when onlyIndependences is true, the closure only contains
					 * independence assertions when onlyIndependences is false, the
					 * closure contains independence and dependence assertions
					 */
					if (!onlyIndependences
							|| (onlyIndependences && !g.existEdge(new Integer(sortedNodes[x]), new Integer(sortedNodes[y])))) {
						closure.add(triplet);
					}
				}
			}
		}
		return closure;
	}
	
	public void setTest(BayesianTest test) {
		this.test = test;
	}

	
	
	@Override
	public String getScoringFunctionName() {
		return "bjp";
	}


}
