/**
 * Code by Daniel Rinehart <danielr@neophi.com>
 * Created for CSG260 Fall 2006 with Karl Lieberherr
 */
package edu.neu.ccs.satsolver;

import java.util.Iterator;

import edu.neu.ccs.satsolver.util.AppSAT;
import edu.neu.ccs.satsolver.util.Output;
import edu.neu.ccs.satsolver.util.Polynomial;

/**
 * Utility class that provides methods for creating an updating a polynomial
 * which is used to determine biased coin values.
 */
public class SATSolverUtil {
	/**
	 * Private constructor to force utility class usage.
	 */
	private SATSolverUtil() {
	}

	/**
	 * Given an initial state of relation numbers and fractions compute the
	 * maximum bias.
	 * 
	 * @param inputInitial
	 *            Initial input
	 * @return Bias and polynomial
	 * @throws IllegalArgumentException
	 *             If the PairI fractions do not add to 1.0, any PairI relation
	 *             number is outside the range 0 to 255 inclusive, or any PairI
	 *             fraction is outside the range 0.0 to 1.0 inclusive.
	 */
	public static OutputI calculateBias(InputInitialI inputInitial) {
		PolynomialSum polynomialSum = createPolynomialSum(inputInitial.getPairs().iterator());

		if (!Polynomial.equal(1.0, polynomialSum.sum)) {
			throw new IllegalArgumentException(
					"InputInitial's pair fractions do not add to 1.0 +/-" + Polynomial.DELTA
							+ ". Got: " + polynomialSum.sum);
		}

		return new Output(polynomialSum.polynomial);
	}

	/**
	 * Given a previously generated polynomial, compute an updated maximum bias
	 * based on adding and subtracting the given relation numbers and fractions.
	 * 
	 * @param inputUpdate
	 *            Updated input
	 * @return Bias and polynomial
	 * @throws IllegalArgumentException
	 *             If the PairI added fractions do not equals the subtracted
	 *             fractions, any PairI relation number is outside the range 0
	 *             to 255 inclusive, or any PairI fraction is outside the range
	 *             0.0 to 1.0 inclusive.
	 */
	public static OutputI updateBias(InputUpdateI inputUpdate) {
		Polynomial initialPolynomial = createInitialPolynomial(inputUpdate);
		PolynomialSum addPolynomialSum = createPolynomialSum(inputUpdate.getAddedPairs().iterator());
		PolynomialSum subtractPolynomialSum = createPolynomialSum(inputUpdate.getSubtractedPairs()
				.iterator());

		if (!Polynomial.equal(addPolynomialSum.sum, subtractPolynomialSum.sum)) {
			throw new IllegalArgumentException(
					"InputUpdate's added and subtracted pair fractions do not add to the same value +/-"
							+ Polynomial.DELTA + ". Got: " + addPolynomialSum.sum + " and "
							+ subtractPolynomialSum.sum);
		}

		return new Output(initialPolynomial.add(addPolynomialSum.polynomial).subtract(
				subtractPolynomialSum.polynomial));
	}

	/**
	 * For the update operation, extract out the base polynomial to be modified.
	 * 
	 * @param inputUpdate
	 *            Updated input
	 * @return Polynomial
	 */
	private static Polynomial createInitialPolynomial(InputUpdateI inputUpdate) {
		if (inputUpdate.getPolynomialBefore() instanceof Polynomial) {
			return (Polynomial) inputUpdate.getPolynomialBefore();
		}
		PolynomialI polynomial = inputUpdate.getPolynomialBefore();
		return new Polynomial(polynomial.getCoefficient(3), polynomial.getCoefficient(2),
				polynomial.getCoefficient(1), polynomial.getCoefficient(0));
	}

	/**
	 * Given a list of PairI objects compute the polynomial and total fractional
	 * sum of the PairI objects found.
	 * 
	 * @param iterator
	 *            PairI list
	 * @return Polynomial and sum
	 */
	private static PolynomialSum createPolynomialSum(Iterator iterator) {
		Polynomial result = null;
		double sum = 0.0;
		while (iterator.hasNext()) {
			PairI pair = (PairI) iterator.next();
			verifyPair(pair);
			Polynomial next = AppSAT.appSAT(pair.getRelationNumber()).multiply(pair.getFraction());
			sum += pair.getFraction();
			if (result == null) {
				result = next;
			} else {
				result = result.add(next);
			}
		}
		return new PolynomialSum(result, sum);
	}

	/**
	 * Verify that a PairI object has valid values.
	 * 
	 * @param pair
	 *            PairI object to verify
	 */
	private static void verifyPair(PairI pair) {
		if ((pair.getRelationNumber() < 0) || (pair.getRelationNumber() > 255)) {
			throw new IllegalArgumentException(
					"Pair's relation number must be between 0 and 255 inclusive. Got: "
							+ pair.getRelationNumber());
		}
		if ((pair.getFraction() < 0.0) || (pair.getFraction() > 1.0)) {
			throw new IllegalArgumentException(
					"Pair's fraction must be between 0.0 and 1.0 inclusive. Got: "
							+ pair.getFraction());
		}
	}

	/**
	 * Private class used to return both the generated polynomial and sum of the
	 * fractions generated from a list of PairI objects.
	 */
	private static class PolynomialSum {
		/**
		 * Generated polynomial.
		 */
		public Polynomial polynomial;

		/**
		 * Sum of PairI fractions
		 */
		public double sum;

		/**
		 * Create a new result object.
		 * 
		 * @param polynomial
		 *            Generated polynomial
		 * @param sum
		 *            Sum of fractions
		 */
		public PolynomialSum(Polynomial polynomial, double sum) {
			this.polynomial = polynomial;
			this.sum = sum;
		}
	}
}