/*
	Sim2D... the file containing "main" and the simulation doLoop for a 2D psuedo-biological simulation
*/

import java.text.*;
import java.io.*;
import java.util.ArrayList;
import java.util.List;

public class Sim2D {

	static WorldFrame theFrame;
	// the pixel world dimensions
	static int width = 700;
	static int height = 700;

	// the virtual world dimensions
	static final double xDimension = 100;        // x dimension in unspecified units
	static final double yDimension = 100;        // y dimension

	static double deltaT = 0.001;            // time-step
	static double simulationTime = 0;        // keeps track of current time
	static double runTime = 100.0;            // final time to run to
	static int counter = 0;                    // keeps track of current integration step number

	// for file writing
	static boolean remote = false;            // no graphics 'cause running on server
	static boolean gsua = false;            // GAK ADDITION to flag when the GSUA option is running
	static int remoteCounter = 0;                // for counting integration steps between remote reports
	static int remoteReportStep = 10;        // number of integration steps between printed info reports
	static boolean infoWrite = false;            // write info to file flag
	static int infoWriteCounter = 0;
	static int infoWriteStep = 10;            // number of integration steps between info reports to file
	static boolean makeMovie = false;
	static int movieStep = 100;                // number of integration steps between movies
	static int movieCounter = 0;                // for counting integration steps between frames
	static String movieFileName = null;
	static String moviePath = null;
	static DecimalFormat fileIdFormat = new DecimalFormat("#000000000.#;#000000000.#");
	static DecimalFormat timeOutFormat = new DecimalFormat("#0.00#; #0.00#");    // a clean format for simulation time printing

	// GAK Addition for Remote simulation and GSUA variables
	List<Double> inputlist = new ArrayList<Double>();
	public FileOps myFileOps = new FileOps();

	static double muttonMaxSize = 3.0;
	static double muttonMaxStepSize = 1.0;
	static double muttonSpawnProb = 0.1;
	static double gluttonMinSize = 1.0;
	static double gluttonMaxStepSize = 1.0;
	static double gluttonNoFoodShrinkProb = 0.1;
	static double gluttonNoFoodShrinkAmount = 0.1;
	static double gluttonInitialPop = 5.0;

	static double multipleSimulationsPerRun = 1.0;

	static double mcSimulationTotal_AvgGluttonRadius= 0.0;
	static double mcSimulationTotal_AvgRadiusGained= 0.0;
	static double mcSimulationTotal_MuttonPop= 0.0;
	static double mcSimulationTotal_GluttonPop= 0.0;
	static double mcSimulationTotal_MaxMuttonPop= 0.0;
	static double mcSimulationTotal_MinMuttonPop= 0.0;





	// for painting control
	static boolean paintOn = true;

	// for run control
	static boolean running = false;

	public static void main(String args[]) {
		new Sim2D(args);
	}

	public Sim2D(String[] inputParams) {
		if (inputParams.length != 0) {
			parseParams(inputParams);
		}    // figure out what options have been specified
		// make things to start
		for (int i = 0; i < gluttonInitialPop; i++) {
			Glutton.makeRandomGlutton();
		}

		if (!remote) {
			// make the frame to which we draw
			theFrame = new WorldFrame(this, width, height);
			theFrame.showAll();        // draw once at beginning
		}

		// start the time loop
        if (gsua){
			for (int j = 0; j < multipleSimulationsPerRun; j++) {
				doLoop();
				mcSimulationTotal_MuttonPop = mcSimulationTotal_MuttonPop + Mutton.muttonCt;
				mcSimulationTotal_GluttonPop = mcSimulationTotal_GluttonPop + Glutton.gluttonCt;

				mcSimulationTotal_MaxMuttonPop = mcSimulationTotal_MaxMuttonPop + Mutton.maxMuttonCt;
				mcSimulationTotal_MinMuttonPop = mcSimulationTotal_MinMuttonPop + Mutton.minMuttonCt;

				mcSimulationTotal_AvgGluttonRadius = mcSimulationTotal_AvgGluttonRadius + Glutton.averageGluttonRadius;
				mcSimulationTotal_AvgRadiusGained = mcSimulationTotal_AvgRadiusGained + Glutton.averageMuttonVolConsumed;
                resetSimulation();

			} // end of Monte carlo loop

			// Calculate final values to output
			mcSimulationTotal_MuttonPop = mcSimulationTotal_MuttonPop/(double)multipleSimulationsPerRun;
			mcSimulationTotal_GluttonPop = mcSimulationTotal_GluttonPop/(double)multipleSimulationsPerRun;
			mcSimulationTotal_MaxMuttonPop = mcSimulationTotal_MaxMuttonPop/(double)multipleSimulationsPerRun;
			mcSimulationTotal_MinMuttonPop = mcSimulationTotal_MinMuttonPop/(double)multipleSimulationsPerRun;
			mcSimulationTotal_AvgGluttonRadius = mcSimulationTotal_AvgGluttonRadius/ (double)multipleSimulationsPerRun;
			mcSimulationTotal_AvgRadiusGained = mcSimulationTotal_AvgRadiusGained/ (double)multipleSimulationsPerRun;

				checkFinalInfoWrite();  // GAK Addition to add the last line output

		}
		else {
			doLoop();
		}
	}


	public void doLoop() {
		while (simulationTime <= runTime) {

			//GAK Addition to allow automatic simulation to remote = true conditions and read in input file
	/*		if (remote) {
				running = true;
				if (gsua) {
					this.openAndReadRemoteInputFile();
					this.writeRemoteOutputFile();
				}

			} */

			if (!running) {
				try {
					Thread.sleep(200);
				} catch (InterruptedException e) {
					System.out.println("error sleeping while paused");
				}
			} else {
				for (int i = 0; i < Thing.thingCt; i++) {
					Thing.theThings[i].step();
				}

				if (!remote) {
					if (paintOn) {
						theFrame.showAll();
						try {
							Thread.sleep(1);
						} catch (InterruptedException e) {
							System.out.println("error sleeping after paint");
						}
					} else {
						theFrame.showInfoOnly();
					}        // only update Thing painting if paintOn
					if (makeMovie) {
						checkJPEGWrite();
						movieCounter++;
					}
				} else {
					if(!gsua) {
						checkRemoteReport();
					}
					remoteCounter++;
				}

				Glutton.calcAvgGluttonRadius();
				Glutton.calcAvgMuttonVolConsumed();
				Mutton.calcMaxAndMinMuttonPopulation();

				if (infoWrite) {
					if (!gsua) {
						checkInfoWrite();
					}
					infoWriteCounter++;
				}


				counter++;
				simulationTime += deltaT;

				Thing.removeDeadThings();        // get rid of dead things
				Mutton.spawnMuttons();        // make more muttons...maybe

			}
		} // end of simulation time


	}

	public void checkJPEGWrite() {
		if (movieCounter >= movieStep) {
			theFrame.showAll(); // paint before save
			String fileName = moviePath + File.separator + movieFileName + String.valueOf(fileIdFormat.format(counter) + ".jpg");
			Jpegger.jpegFromBufferedImage(WorldPanel.theImage, fileName);
			movieCounter = 0;
		}
	}

	//GAK Addition
	public void openAndReadRemoteInputFile(String inputFileName) {
		this.inputlist = FileOps.remoteInputFileOpenAndRead(inputFileName);

	}



	public static void checkRemoteReport() {
		if (remoteCounter >= remoteReportStep) {
			System.out.println(getInfoString());
			remoteCounter = 0;
		}
	}

	public static void checkInfoWrite() {
		if (infoWriteCounter >= infoWriteStep) {
			FileOps.writeInfo();
			infoWriteCounter = 0;
		}
	}
//GAK Addition to write one file
	public static void checkFinalInfoWrite() {
			FileOps.writeFinalInfo();

	}

	public static String getInfoString() {
		String timeStr = " Sim Time = " + String.valueOf(timeOutFormat.format(Sim2D.simulationTime));
		String mutCtStr = " Muttons: " + String.valueOf(Mutton.muttonCt);
		String glutCtStr = " Gluttons: " + String.valueOf(Glutton.gluttonCt);
		return glutCtStr + " , " + mutCtStr + " , " + timeStr;
	}

	private void parseParams(String[] inputParams) {
		for (int i = 0; i < inputParams.length; i++) {
			if ((inputParams[i].equals("-help")) | (inputParams[i].equals("?"))) {
				System.out.println("The following command line arguments are accepted.....");
				System.out.println("	-help	 prints this help message");
				System.out.println("	-r remote start (no local GUI) with default initial conditions");
				System.out.println("-rs [int] remote reporting step interval");
				System.out.println("	-dt [double] use this value for the fixed time-step");
				System.out.println("	-if [filename]	write info to the named file");
				System.out.println("	-is [int] integration steps between info writing");
				System.exit(0);
			}
			if (inputParams[i].equals("-r")) {
				remote = true;
				running = true;
			}

			if (inputParams[i].equals("-rs")) {
				Double rStep = Double.valueOf(inputParams[i + 1]);
				remoteReportStep = (int) rStep.doubleValue();
			}

			if (inputParams[i].equals("-dt")) {
				Double dt = Double.valueOf(inputParams[i + 1]);
				deltaT = dt.doubleValue();
				Thing.deltaT = deltaT;
			}

			if (inputParams[i].equals("-if")) {
				File infodir = new File(inputParams[i + 1]);
				File altdir = new File(inputParams[i + 1]);
				int j = 1;
				while (altdir.isDirectory()) {
					System.out.println(altdir.getName() + " exists.... keeping file name BUT changing directory name");
					altdir = new File(inputParams[i + 1] + "." + String.valueOf(j));
					j++;
				}
				altdir.mkdir();    // make directory for files
				String nameToUse = infodir.getName();
				FileOps.mkInfoFile(altdir.getAbsolutePath() + File.separator + nameToUse);
				//*******
				infoWrite = true;
			}

			if (inputParams[i].equals("-is")) {
				Double iStep = Double.valueOf(inputParams[i + 1]);
				infoWriteStep = (int) iStep.doubleValue();
			}

			// GAK Addition for GSUA operation
			if (inputParams[i].equals("-gsua")) {
				// This operation and the inputList load up the parameter values from input.dat
				this.inputlist=FileOps.remoteInputFileOpenAndRead(inputParams[i + 1]);  // this loads the input.dat file values
			    this.muttonMaxSize = this.inputlist.get(0);
				this.muttonMaxStepSize = this.inputlist.get(1);
				this.muttonSpawnProb = this.inputlist.get(2);
				this.gluttonMinSize = this.inputlist.get(3);
				this.gluttonMaxStepSize= this.inputlist.get(4);
				this.gluttonNoFoodShrinkProb= this.inputlist.get(5);
				this.gluttonNoFoodShrinkAmount= this.inputlist.get(6);
				this.gluttonInitialPop = this.inputlist.get(7);
				this.multipleSimulationsPerRun = this.inputlist.get(8);


				File infodir = new File(inputParams[i + 2]);
				File altdir = new File(inputParams[i + 2]);
				int j = 1;
			/*	while (altdir.isDirectory()) {
					System.out.println(altdir.getName() + " exists.... keeping file name BUT changing directory name");
					altdir = new File(inputParams[i + 1] + "." + String.valueOf(j));
					j++;
				}*/
				altdir.mkdir();    // make directory for files
				String nameToUse = infodir.getName();
	 			FileOps.mkInfoFile(altdir.getAbsolutePath() + File.separator + nameToUse);


				//*******
				infoWrite = true;
				remote = true;
				gsua = true;
				running = true;

			}
		}

	}

	public void resetSimulation() {
		simulationTime = 0.0;
        Mutton.maxMuttonCt=0;
		Mutton.minMuttonCt=0;
		Glutton.averageMuttonVolConsumed = 0.0;
		Glutton.averageGluttonRadius = 0.0;

		for (int j = 0; j < Mutton.muttonCt; j++) {
			Mutton.theMuttons[j].coord[0]=Math.random()*Thing.maxX;
			Mutton.theMuttons[j].coord[1]=Math.random()*Thing.maxY;
			Mutton.theMuttons[j].radius = Math.random()*Mutton.theMuttons[j].maxRadius;

		}


		for (int i = 0; i < gluttonInitialPop; i++) {
			Glutton.theGluttons[i].coord[0]=Math.random()*Thing.maxX;
			Glutton.theGluttons[i].coord[1]=Math.random()*Thing.maxY;
			Glutton.theGluttons[i].radius = Glutton.theGluttons[i].minRadius;

		}

	}
}
		
		
		
		
		
		
		
		
		
		
		
