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;

public class GraphStats {

	public static void main(String[] args) {
		if(args.length == 0){
			System.out.println("Please input one of the following:");
			System.out.println(branchingTestMessage(false));
			System.out.println(giantTestMessage(false));
			System.out.println(diameterTestMessage(false));
			System.out.println(avgdiamTestMessage(false));
			System.out.println(secondLargestTestMessage(false));
			System.out.println(excessTestMessage(false));
		}else if(args[0].equals("branch")){
			if(args.length < 4){
				System.out.println("Input should be:");
				System.out.println(branchingTestMessage(true));
			}else{
				double mean = new Double(args[1]);
				int maxGen = new Integer(args[2]);
				int numRuns = new Integer(args[3]);
				String filename = null;
				if(args.length == 5){
					filename = args[4];
				}
				branchingTest(mean, maxGen, numRuns, filename);
			}
		} else if(args[0].equals("giant")){
			if(args.length < 4){
				System.out.println("Input should be:");
				System.out.println(giantTestMessage(true));
			}else{
				int numVerts = new Integer(args[1]);
				double prob = new Double(args[2]);
				int numRuns = new Integer(args[3]);
				String filename = null;
				if(args.length == 5){
					filename = args[4];
				}
				largestComponentTest(numVerts, prob, numRuns, filename);
			}
		} else if(args[0].equals("diameter")){
			if(args.length < 4){
				System.out.println("Input should be:");
				System.out.println(diameterTestMessage(true));
			}else{
				int numVerts = new Integer(args[1]);
				double prob = new Double(args[2]);
				int numRuns = new Integer(args[3]);
				String filename = null;
				if(args.length == 5){
					filename = args[4];
				}
				diameterTest(numVerts, prob, numRuns, filename);
			}
		} else if(args[0].equals("avgdiam")){
			if(args.length < 4){
				System.out.println("Input should be:");
				System.out.println(avgdiamTestMessage(true));
			}else{
				int numVerts = new Integer(args[1]);
				double prob = new Double(args[2]);
				int numRuns = new Integer(args[3]);
				String filename = null;
				if(args.length == 5){
					filename = args[4];
				}
				averageDiameterTest(numVerts, prob, numRuns, filename);
			}
		} else if(args[0].equals("second")){
			if(args.length < 4){
				System.out.println("Input should be:");
				System.out.println(secondLargestTestMessage(true));
			}else{
				int numVerts = new Integer(args[1]);
				double prob = new Double(args[2]);
				int numRuns = new Integer(args[3]);
				String filename = null;
				if(args.length == 5){
					filename = args[4];
				}
				secondLargestComponentTest(numVerts, prob, numRuns, filename);
			}
		} else if(args[0].equals("excess")){
			if(args.length < 6){
				System.out.println("Input should be:");
				System.out.println(excessTestMessage(true));
			}else{
				int numVerts = new Integer(args[1]);
				double prob = new Double(args[2]);
				int size = new Integer(args[3]);
				int excess = new Integer(args[4]);
				int numRuns = new Integer(args[5]);
				String filename = null;
				if(args.length == 7){
					filename = args[6];
				}
				excessTest(numVerts, prob, size, excess, numRuns, filename);
			}
		} else{
			System.out.println("Please input one of the following:");
			System.out.println(branchingTestMessage(false));
			System.out.println(giantTestMessage(false));
			System.out.println(diameterTestMessage(false));
			System.out.println(avgdiamTestMessage(false));
			System.out.println(secondLargestTestMessage(false));
			System.out.println(excessTestMessage(false));
		}
	}
	
	public static String branchingTestMessage(boolean details){
		String message = "java GraphStats branch mean maxGen numRuns [filename]";
		if(details){
			message += "\n\tmean- parameter for average number of children.\n";
			message += "\tmaxGen- max number of generations to be considered a survived process.\n";
			message += "\tnumRuns - number of runs.\n";
			message += "\tfilename - optional, filename to print results.";
		}
		return message;
	}
	
	
	public static String giantTestMessage(boolean details){
		String message = "java GraphStats giant numVerts prob numRuns [filename]";
		if(details){
			message += "\n\tnumVerts - number of vertices of the random graph.\n";
			message += "\tprob - parameter for randomly placing edges, probability is prob/numVerts.\n";
			message += "\tnumRuns - number of runs.\n";
			message += "\tfilename - optional, filename to print results.";
		}
		return message;
	}
	
	public static String diameterTestMessage(boolean details){
		String message ="java GraphStats diameter numVerts prob numRuns [filename]";
		if(details){
			message += "\n\tnumVerts - number of vertices of the random graph.\n" +
					"\tprob - parameter for randomly placing edges, probability is prob/numVerts.\n" +
					"\tnumRuns - number of runs.\n" +
					"\tfilename - optional, filename to print results.";
		}
		return message;
	}
	
	public static String avgdiamTestMessage(boolean details){
		String message = "java GraphStats avgdiam numVerts prob numRuns [filename]";
		if(details){
			message += "\n\tnumVerts - number of vertices of the random graph.\n" +
					"\tprob - parameter for randomly placing edges, probability is prob/numVerts.\n" +
					"\tnumRuns - number of runs.\n" +
					"\tfilename - optional, filename to print results.";
		}
		return message;
	}
	
	public static String secondLargestTestMessage(boolean details){
		String message = "java GraphStats second numVerts prob numRuns [filename]";
		if(details){
			message += "\n\tnumVerts - number of vertices of the random graph." +
					"\tprob - parameter for randomly placing edges, probability is prob/numVerts." +
					"\tnumRuns - number of runs." +
					"\tfilename - optional, filename to print results.";
		}
		return message;
	}
	
	public static String excessTestMessage(boolean details){
		String message = "java GraphStats excess numVerts prob size excess numRuns [filename]";
		if(details){
			message += "\n\tnumVerts - number of vertices of the random graph." +
					"\tprob - parameter for randomly placing edges, probability is prob/numVerts.\n" +
					"\tsize - the size of the components to be counted.\n" +
					"\texcess - the excess of the components to be counted. Excess is edges - vertices.\n" +
					"\tnumRuns - number of runs.\n" +
					"\tfilename - optional, filename to print results.";
		}
		return message;
	}
	
	public static void branchingTest(double mean, int max, int numRuns,
			String filename) {
		PrintStream p;
		if(filename == null){
			p = System.out;
		} else{
			File f = new File(filename);
			try {
				p = new PrintStream(f);
			} catch (FileNotFoundException e) {
				p = System.out;
			}
		}

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

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

		PrintStream p;
		if(filename == null){
			p = System.out;
		} else{
			File f = new File(filename);
			try {
				p = new PrintStream(f);
			} catch (FileNotFoundException e) {
				p = System.out;
			}
		}
		int totalSize = 0;
		for (int j = 0; j < numRuns; j++) {
			g = new RandomGraph(numVerts, prob);
			totalSize += g.getLargestComponent().getNumVerts();
		}
		p.println("Number of vertices = " + numVerts + ", Prob = " + prob 
					+ ", Average size of largest = " + ((double) totalSize) / numRuns);
		p.close();
	}

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

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

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

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

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

		PrintStream p;
		if(filename == null){
			p = System.out;
		} else{
			File f = new File(filename);
			try {
				p = new PrintStream(f);
			} catch (FileNotFoundException e) {
				p = System.out;
			}
		}

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

	}

	public static void excessTest(int numVerts, double prob,
			int size, int excess, int numRuns, String filename) {
		RandomGraph g;

		PrintStream p;
		if(filename == null){
			p = System.out;
		} else{
			File f = new File(filename);
			try {
				p = new PrintStream(f);
			} catch (FileNotFoundException e) {
				p = System.out;
			}
		}

		long total;
		total = 0;
			for (int k = 0; k < numRuns; k++) {
				g = new RandomGraph(numVerts, prob);
				total += g.getNumComponents(size, excess);
			}
			if (excess == -1) {
				p.print("Num Verts: " + numVerts + ", Prob: "
						+ prob + ", Size: " + size
						+ ", Result = " + ((double) total) / numRuns
						+ ", Expected = ");
						p.printf(
								"%.2f\n",
								expectedTreeCount(numVerts, size,
										prob));
			} else if (excess == 0) {
				p.print("Num Verts: " + numVerts + ", Prob: "
						+ prob + ", Size: " + size
						+ ", Result = " + ((double) total) / numRuns
						+ ", Expected = ");
						p.printf(
								"%.2f\n",
								expectedUnicyclicCount(numVerts, size,
										prob));
			} else {
				p.println("Num Verts: " + numVerts + ", Size: "
						+ size + ", Prob: " + prob + " = "
						+ ((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);
	}
}
