#include "HGProbabilityCalc.h"

// LICENSE: This code is freeware.  You may use this code for any purpose you
// like so long as this license is attached.  If this code is used in a
// commercial application, you must send me an email so I feel uber-geeky.  No
// other compensation is required.  Email me at jechols@nerdbucket.com

// Sets up our basic urn situation: number of marbles, number we'll pick, and
// how many of the marbles are white.
//
// See http://en.wikipedia.org/wiki/Hypergeometric_distribution for more
// information about hg dist.
HGProbabilityCalc::HGProbabilityCalc(int marbles, int picks, int white):
  m_total_marbles(marbles),
  m_picks(picks),
  m_white_marbles(white)
{
  calculate_permutations();
}

// Computes the number of ways to draw all our picks.  Useful for information
// as well as the final probability calc.
void HGProbabilityCalc::calculate_permutations() {
  m_pick_permutations = JEStatistics::combin(m_total_marbles, m_picks);
}

// Computes the odds of a certain number of white marbles being picked
// out of our already-built urn.
mpf_class HGProbabilityCalc::odds_against(int matched) {
  // Ways to arrange white marble pulls to win
  mpz_class white_wins = JEStatistics::combin(m_white_marbles, matched);
  // Ways to pull the black marbles to win
  mpz_class black_wins = JEStatistics::combin(m_total_marbles - m_white_marbles, m_picks - matched);

  // Probability is the multiplication of white * black permutations,
  // divided by total permutations.  To win you must have one of the white
  // marble combos and one of the black marble combos.  So the number of
  // permutations that give you both are just the multiplication of the
  // white and black permutations.  It's almost easy to understand!

  // Get total permutations for getting this number of matches
  // If we have a nil value, we know something went "wrong" and
  // probability is 0
  mpf_class probability = 0.0;

  // If either calculation was bad, we know probability for this case is
  // nonexistent
  if (white_wins > 0 && black_wins > 0) {
    // Two-step process to ensure our calculation is done a float.
    // Using our cache for pick permutations appears unnecessary,
    // but for many statistical problems this class solves, you'll call
    // odds_against more than once.  Think of state lotteries or keno where
    // you may want odds for all possible matches.
    probability = (white_wins * black_wins);
    probability /= m_pick_permutations;
  }

  return probability;
}

// Computes the odds of a all possible numbers of white marbles being picked
// out of our already-built urn.  Uses the callback function to let the caller
// deal with the results of each iteration - like yield in Ruby.
void HGProbabilityCalc::get_all_odds(ProbabilityOddsCallback& poc) {
  // Normally we iterate from white marbles down to 0, but if this is a poker
  // situation there may be more white marbles than picks, which would make
  // for some illogical computations.  Pick whichever is lower.
  const int lower = m_picks > m_white_marbles ? m_white_marbles : m_picks;
  for (int matches = lower; matches >= 0; --matches)
  {
    poc(matches, odds_against(matches));
  }
}
