import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintStream;
import org.apache.commons.math.distribution.PoissonDistributionImpl;
import org.apache.commons.math.util.MathUtils;
import org.apache.commons.*;

/**
 * The Statistics class is used to collect statistics on several different Random Graph
 * questions.
 *
 *
 */
public class Statistics {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		String filename = "BranchingTest";

		// double[] means = {0.5,0.8, 1, 1.2, 1.5, 2};
		// double[] prob = {1, 1.2, 1.4, 1.6, 1.8, 2};
		double[] prob = { 1.01, 1.05, 1.06, 1.07, 1.08, 1.09, 1.1, 1.2, 1.3,
				1.4, 1.5, 2, 3, 5 };
		int[] max = { 20, 30, 50, 20, 20, 12 };
		int numRuns = 1000;
		int numVerts = 2000;
		int[] numVertsArray = { 3000 };
		double[] extinctionProb = { 0.417188 };
		int[] sizes = { 1, 5, 10 };
		// branchingTest(means, max, numRuns, filename);

		filename = "LargestComponentTest";
		for (int i = 0; i < numVertsArray.length; i++) {
			filename = "LargestComponentTest_" + numVertsArray[i]
					+ "_vertices_3";
			largestComponentTest(numVertsArray[i], numRuns, prob, filename);
			System.out.println("" + numVertsArray[i] + " complete");
		}

	}

	public static void branchingTest(double[] means, int[] max, int numRuns,
			String filename) {
		File f = new File(filename);

		PrintStream p;
		try {
			p = new PrintStream(f);
		} catch (FileNotFoundException e) {
			p = System.out;
		}

		for (int j = 0; j < means.length; j++) {
			PoissonDistributionImpl X = new PoissonDistributionImpl(means[j]);
			BranchingProcess bp = new BranchingProcess(X);
			int survivalRate = 0;
			for (int i = 0; i < numRuns; i++) {
				if (bp.run(max[j])[max[j]] > 0) {
					survivalRate++;
				}
			}
			p.println("Mean = " + means[j] + ": " + survivalRate
					+ " survived out of " + numRuns + " = "
					+ ((double) survivalRate) / numRuns);
		}
		p.close();
	}

	public static void largestComponentTest(int numVerts, int numRuns,
			double[] prob, String filename) {
		RandomGraph g;

		File f = new File(filename);

		PrintStream p;
		try {
			p = new PrintStream(f);
		} catch (FileNotFoundException e) {
			p = System.out;
		}
		for (int i = 0; i < prob.length; i++) {
			int totalSize = 0;
			for (int j = 0; j < numRuns; j++) {
				g = new RandomGraph(numVerts, prob[i]);
				totalSize += g.getLargestComponent().getNumVerts();
			}
			p.println("Prob = " + prob[i] + ": Average size of largest = "
					+ ((double) totalSize) / numRuns);
		}
		p.close();
	}

	public static void graphTest(int numVerts, int numRuns, String filename) {
		RandomGraph g;
		int updateCount = (numRuns * 20 / numVerts);

		File f = new File(filename);

		PrintStream p;
		try {
			p = new PrintStream(f);
		} catch (FileNotFoundException e) {
			p = System.out;
		}
		for (int i = 0; i < numRuns; i++) {
			g = new RandomGraph(numVerts, 1.2);
			p.println(g);
			if ((i + 1) % updateCount == 0) {
				System.out.println("Run " + (i + 1));
			}
		}
		p.close();
	}

	public static void diameterTest(int numVerts, int numRuns, double[] prob,
			String filename) {
		RandomGraph g;

		File f = new File(filename);

		PrintStream p;
		try {
			p = new PrintStream(f);
		} catch (FileNotFoundException e) {
			p = System.out;
		}
		Component giant;
		for (int i = 0; i < prob.length; i++) {
			int totalDiameter = 0;
			int diameter;
			double totalRatio = 0;
			for (int j = 0; j < numRuns; j++) {
				g = new RandomGraph(numVerts, prob[i]);
				giant = g.getLargestComponent();
				diameter = giant.randomDistance();
				totalDiameter += diameter;
				totalRatio += diameter / Math.log(giant.getNumVerts());
			}
			p.println("Prob = " + prob[i] + ": Average diameter of giant = "
					+ ((double) totalDiameter) / numRuns + " Average ratio = "
					+ totalRatio / numRuns);
		}
		p.close();
	}

	public static void averageDiameterTest(int numVerts, int numRuns,
			double[] prob, String filename) {
		RandomGraph g;

		File f = new File(filename);

		PrintStream p;
		try {
			p = new PrintStream(f);
		} catch (FileNotFoundException e) {
			p = System.out;
		}
		Component giant;
		for (int i = 0; i < prob.length; i++) {
			double totalAvgDiameter = 0;
			double diameter;
			double totalRatio = 0;
			for (int j = 0; j < numRuns; j++) {
				g = new RandomGraph(numVerts, prob[i]);
				giant = g.getLargestComponent();
				diameter = giant.averageDistance();
				totalAvgDiameter += diameter;
				totalRatio += diameter / Math.log(giant.getNumVerts());
			}
			p.println("Prob = " + prob[i] + ": Average diameter of giant = "
					+ totalAvgDiameter / numRuns + " Average ratio = "
					+ totalRatio / numRuns);
		}
		p.close();
	}

	public static void secondLargestComponentTest(int[] numVerts, int numRuns,
			double[] prob, String filename) {
		RandomGraph g;

		File f = new File(filename);

		PrintStream p;
		try {
			p = new PrintStream(f);
		} catch (FileNotFoundException e) {
			p = System.out;
		}

		double totalSize;
		double bound;
		for (int i = 0; i < numVerts.length; i++) {
			p.println("Num verts: " + numVerts[i]);
			for (int j = 0; j < prob.length; j++) {
				totalSize = 0;
				for (int k = 0; k < numRuns; k++) {
					g = new RandomGraph(numVerts[i], prob[j]);
					totalSize += g.getComponent(2).getNumVerts();
				}
				bound = 1.0 / (prob[j] - 1 - Math.log(prob[j]))
						* Math.log(numVerts[i]);
				p.println("\t prob: " + prob[j] + ", Bound: " + bound
						+ ", Average = " + totalSize / numRuns);
				System.out.println("" + numVerts[i] + ", " + prob[j]
						+ " complete.");
			}
		}
	}

	public static void testCLT(int[] numVerts, int numRuns, double[] prob,
			double[] extinctionProb, String filename) {
		RandomGraph g;

		File f = new File(filename);

		PrintStream p;
		try {
			p = new PrintStream(f);
		} catch (FileNotFoundException e) {
			p = System.out;
		}

		double expected;
		double result;
		int n;
		double sqrtn;
		for (int i = 0; i < numVerts.length; i++) {
			n = numVerts[i];
			sqrtn = Math.sqrt(n);
			for (int j = 0; j < prob.length; j++) {
				expected = n * (1 - extinctionProb[j]);
				p.println("Num Vertices: " + n + "Prob: " + prob[j] + "\n");
				for (int k = 0; k < numRuns; k++) {
					g = new RandomGraph(numVerts[i], prob[j]);
					result = (g.getLargestComponent().getNumVerts() - expected)
							/ sqrtn;
					p.println("" + result);
				}
				System.out.println("" + numVerts[i] + ", " + prob[j]
						+ " complete.");
				p.println();
			}
		}
	}

	public static void testExcessCount(int numVerts[], int numRuns,
			int[] sizes, int excess, double prob[], String filename) {
		RandomGraph g;

		File f = new File(filename);

		PrintStream p;
		try {
			p = new PrintStream(f);
		} catch (FileNotFoundException e) {
			p = System.out;
		}

		long total;
		for (int i = 0; i < numVerts.length; i++) {
			for (int j = 0; j < prob.length; j++) {
				for (int h = 0; h < sizes.length; h++) {
					total = 0;
					for (int k = 0; k < numRuns; k++) {
						g = new RandomGraph(numVerts[i], prob[j]);
						total += g.getNumComponents(sizes[h], excess);
					}
					if (excess == -1) {
						p.print("Num Verts: " + numVerts[i] + ", Prob: "
								+ prob[j] + ", Size: " + sizes[h]
								+ ", Result = " + ((double) total) / numRuns
								+ ", Expected = ");
						p.printf(
								"%.2f\n",
								expectedTreeCount(numVerts[i], sizes[h],
										prob[j]));
					} else if (excess == 0) {
						p.print("Num Verts: " + numVerts[i] + ", Prob: "
								+ prob[j] + ", Size: " + sizes[h]
								+ ", Result = " + ((double) total) / numRuns
								+ ", Expected = ");
						p.printf(
								"%.2f\n",
								expectedUnicyclicCount(numVerts[i], sizes[h],
										prob[j]));
					} else {
						p.println("Num Verts: " + numVerts[i] + ", Size: "
								+ sizes[h] + ", Prob: " + prob[j] + " = "
								+ ((double) total) / numRuns);
					}
				}
			}
		}
	}

	private static double expectedTreeCount(int n, int k, double lambda) {
		return n * Math.pow(k, k - 2) * Math.pow(lambda, k - 1)
				* Math.exp(0 - lambda * k) / MathUtils.factorial(k);
	}

	private static double expectedUnicyclicCount(int n, int k, double lambda) {
		double sum = 0;
		for (int i = 0; i <= k - 3; i++) {
			sum += Math.pow(k, i) / MathUtils.factorial(i);
		}
		return sum * Math.pow(lambda * Math.exp(0 - lambda), k) / (2 * k);
	}
}
