/**
 * Simple class for approximate inference based on the burglary network.
 */
public class BayesNet {

  /**
   * Inner class for representing a node in the network.
   */
  private class Node {
    
    // The name of the node
    private String name;
    
    // The parent nodes
    private Node[] parents;

    // The probabilities for the CPT
    private double[] probs;
    
    // The current value of the node
    private boolean value;

    /**
     * Initializes the node.
     */
    private Node(String n, Node[] pa, double[] pr) {
      name = n;
      parents = pa;
      probs = pr;
    }

    /**
     * Returns conditional probability of value "true" for the current
     * node based on the values of the parent nodes.
     */
    private double conditionalProbability() {
     
      int index = 0;
      for (int i = 0; i < parents.length; i++) {
        if (parents[i].value == false) {
          index += Math.pow(2, parents.length - i - 1);
        }
      }
      return probs[index];
    }
  }

  // The list of nodes in the Bayes net
  private Node[] nodes;
  
  /**
   * Constructor that sets up the burglary network.
   */
  public BayesNet() {
    
    nodes = new Node[5];

    nodes[0] = new Node("Burglary", new Node[] {}, new double[] {0.001});
    nodes[1] = new Node("Earthquake", new Node[] {}, new double[] {0.002});
    nodes[2] = new Node("Alarm", new Node[] {nodes[0], nodes[1]}, 
                        new double[] {0.95, 0.94, 0.29, 0.001});
    nodes[3] = new Node("JohnCalls", new Node[] {nodes[2]}, 
                        new double[] {0.9, 0.05});
    nodes[4] = new Node("MaryCalls", new Node[] {nodes[2]}, 
                        new double[] {0.7, 0.01});
  }

  /**
   * Prints the current state of the network to standard out.
   */
  public void printState() {

    for (int i = 0; i < nodes.length; i++) {
      if (i > 0) {
        System.out.print(", ");
      }
      System.out.print(nodes[i].name + " = " + nodes[i].value);
    }
    System.out.println();
  }

  /**
   * This method assigns new values to the nodes in the network by
   * sampling from the joint distribution (based on PRIOR-SAMPLE
   * method from text book/slides).
   */
  public void priorSample() {

    // YOUR CODE HERE
  }

  /**
   * Rejection sampling. Returns probability of query variable being
   * true given the values of the evidence variables, estimated based
   * on the given total number of samples (see REJECTION-SAMPLING
   * method from text book/slides).
   *
   * The nodes/variables are specified by their indices in the nodes
   * array. The array evidenceValues has one value for each index in
   * indicesOfEvidenceNodes. See also examples in main().
   */
  public double rejectionSampling(int queryNode, int[] indicesOfEvidenceNodes, 
                                  boolean[] evidenceValues, int N) {

    return 0;  // REPLACE THIS LINE BY YOUR CODE
  }

  /**
   * This method assigns new values to the non-evidence nodes in the
   * network and computes a weight based on the evidence nodes (based
   * on WEIGHTED-SAMPLE method from text book/slides).
   *
   * The evidence is specified as in the case of rejectionSampling().
   */
  public double weightedSample(int[] indicesOfEvidenceNodes,         
                               boolean[] evidenceValues) {

    return 0;  // REPLACE THIS LINE BY YOUR CODE
  }

  /**
   * Likelihood weighting. Returns probability of query variable being
   * true given the values of the evidence variables, estimated based
   * on the given total number of samples (see LIKELIHOOD-WEIGHTING
   * method from text book/slides).
   *
   * The parameters are the same as in the case of rejectionSampling().
   */
  public double likelihoodWeighting(int queryNode, 
                                    int[] indicesOfEvidenceNodes, 
                                    boolean[] evidenceValues, int N) {

    return 0;  // REPLACE THIS LINE BY YOUR CODE
  }

  /**
   * MCMC inference. Returns probability of query variable being true
   * given the values of the evidence variables, estimated based on
   * the given total number of samples (see MCMC-ASK method from text
   * book/slides).
   *
   * The parameters are the same as in the case of rejectionSampling().
   */
  public double MCMCask(int queryNode, int[] indicesOfEvidenceNodes, 
                        boolean[] evidenceValues, int N) {

    return 0;  // REPLACE THIS LINE BY YOUR CODE
  }

  /**
   * The main method, with some example method calls.
   */
  public static void main(String[] ops) {

    // Create network.
    BayesNet b = new BayesNet();

    // Sample five states from joint distribution and print them
    for (int i = 0; i < 5; i++) {
      b.priorSample();
      b.printState();
    }

    // Print out results of some example queries based on rejection sampling.
    // Same should be possible with likelihood weighting and MCMC inference.

    // Probability of alarm sounding given phone calls from both John
    // and Mary
    System.out.println(b.rejectionSampling(2, new int[] {3, 4}, 
                                           new boolean[] {true, true}, 
                                           10000));


    // Probability of alarm going off given a burglary
    System.out.println(b.rejectionSampling(2, new int[] {0}, 
                                           new boolean[] {true}, 
                                           100000));

    // Probability of alarm going off given a simultaneous burglary
    // and earthquake
    System.out.println(b.rejectionSampling(2, new int[] {0, 1}, 
                                           new boolean[] {true, true}, 
                                           10000000));
  }
}
