/* 
 * The Computer Programmer's Lament:
 * Program complexity grows until it exceeds the capability of the programmer who must maintain it.
 */

package morlok8k.MinecraftLandGenerator;

import java.awt.EventQueue;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;

import morlok8k.MinecraftLandGenerator.GUI.MLG_GUI;

/**
 * 
 * @author Corrodias, Morlok8k, pr0f1x
 * 
 */
public class Main {

	//////////////////////////////////////////////////////////
	// REMINDER: Because I always forget/mix up languages:	//
	// "static" in java means "global" to this class		//
	// "final" means "constant"								//
	// public/private shows/hides between classes			//
	//////////////////////////////////////////////////////////

	/**
	 * @param args
	 *            the command line arguments
	 */
	public static void main(String[] args) {
		var.startTime = System.currentTimeMillis();

		var.originalArgs = args;	// we may potentially remove some args later, but we keep a record of the original for the log file.

		// This is really just here for debugging...
		// I plan on adding more asserts later, but for now, this will do.
		// to enable this, run:
		// java -enableassertions -jar MinecraftLandGenerator.jar
		assert var.assertsEnabled = true;  // Intentional side-effect!!!  (This may cause a Warning, which is safe to ignore: "Possible accidental assignment in place of a comparison. A condition expression should not be reduced to an assignment")
		if (var.assertsEnabled) {
			Out.outD("assertsEnabled: " + var.assertsEnabled);
			var.verbose = true;
			Out.outD("Verbose mode forced!");
			var.testing = true;
			Out.outD("Debug mode forced!");
			var.dontWait = true;
			Out.outD("-nowait mode forced!");
			Out.outD("");
		}

		boolean GUI = false;			// GUI needs to be true to run in graphical mode
		boolean NOGUI = false;			// NOGUI is a flag that finds reasons to not use a graphical mode.

		if (args.length != 0) {			// if args are present, then we assume we want NOGUI
			NOGUI = true;				// if no args are present, we will attempt GUI
		}

		String[] argsNOGUI = new String[args.length];
		argsNOGUI = args;
		argsNOGUI = StringArrayParse.Parse(argsNOGUI, "nogui");		//parse out "nogui"
		if (!(args.equals(argsNOGUI))) {								//do the freshly parsed args match the original?
			args = argsNOGUI;											//use the freshly parsed args for everything else now...
			NOGUI = true;
		}

		//MLG_GUI Choosing code...
		if ((!NOGUI) && (!java.awt.GraphicsEnvironment.isHeadless())) {
			GUI = true;
			if (var.testing) {
				Out.outD("MLG_GUI: This is a graphical enviroment.");
			}

			//////
			GUI = false;				// forcing GUI to be false for now, because I don't have the MLG_GUI code ready yet!
			//////

		} else {
			GUI = false;				// No GUI for us today...
			if (var.testing) {
				Out.outD("MLG_GUI: Command Line Only!");
			}
		}

		if (GUI) {	//GUI
			// Launch MLG_GUI

			EventQueue.invokeLater(new Runnable() {

				@SuppressWarnings("static-access")
				@Override
				public void run() {

					try {
						final MLG_GUI window = new MLG_GUI();
						window.frmMLG_GUI.setVisible(true);
					} catch (final Exception e) {
						e.printStackTrace();
					}
				}
			});

		} else {	//No GUI
			// Finally, Lets Start MLG!

			var.UsingGUI = false;
			var.args = args;
			Main.runCLI();
		}

	}

	/**
	 * Start MinecraftLandGenerator (Command Line Interface)
	 * 
	 * @author Corrodias, Morlok8k
	 * @param args
	 * 
	 */
	private static void runCLI() {

		// Basic Program Initialization
		Startup.initialStart();
		boolean quit = false;
		quit = Startup.programArguments();
		if (quit) { return; }
		quit = Startup.confFile();
		if (quit) { return; }

		WorldVerify.verifyWorld();

		{
			final File backupLevel =
					new File(var.worldPath + var.fileSeparator + "level_backup.dat");
			if (backupLevel.exists()) {
				//err("There is a level_backup.dat file left over from a previous attempt that failed. You should go determine whether to keep the current level.dat"
				//		+ " or restore the backup.");
				//err("You most likely will want to restore the backup!");
				//Time.waitTenSec(false);

				Out.err("There is a level_backup.dat file left over from a previous attempt that failed.");
				Out.out("Resuming...");

				//use resume data
				final File serverLevel = new File(var.worldPath + var.fileSeparator + "level.dat");
				try {
					Misc.copyFile(backupLevel, serverLevel);
				} catch (final IOException e) {
					e.printStackTrace();
				}
				backupLevel.delete();

				//return;

				FileRead.readArrayListCoordLog(var.worldPath + var.fileSeparator + var.logFile);		// we read the .log just for any resume data, if any.

				System.gc();		//run the garbage collector - hopefully free up some memory!

				var.xRange = var.resumeX;
				var.zRange = var.resumeZ;

			}
		}

		// =====================================================================
		//                              PROCESSING
		// =====================================================================

		Out.out("Processing world \"" + var.worldPath + "\", in " + var.increment
				+ " block increments, with: " + var.javaLine);
		// out( MLG + "Processing \"" + worldName + "\"...");

		Out.out("");

		// prepare our two ProcessBuilders
		// minecraft = new ProcessBuilder(javaLine, "-Xms1024m", "-Xmx1024m", "-jar", jarFile, "nogui");
		var.minecraft = new ProcessBuilder(var.javaLine.split("\\s")); // is this always going to work? i don't know.	(most likely yes)
		var.minecraft.directory(new File(var.serverPath));
		var.minecraft.redirectErrorStream(true);

		try {
			Out.out("Launching server once to make sure there is a world.");

			final long generationStartTimeTracking = System.currentTimeMillis();		//Start of time remaining calculations.

			final boolean serverLaunch = Server.runMinecraft();

			if (!(serverLaunch)) {
				System.exit(1);				// we got a warning or severe error
			}

			if ((var.xRange == 0) & (var.zRange == 0)) {  //If the server is launched with an X and a Z of zero, then we just shutdown MLG after the initial launch.
				return;
			}

			FileWrite.AppendTxtFile(var.worldPath + var.fileSeparator + var.logFile,
					"# " + var.PROG_NAME + " " + var.VERSION + " - " + SelfAware.JVMinfo()
							+ var.newLine + "# " + var.MC_Server_Version + var.newLine
							+ "# Started: " + var.dateFormat.format(generationStartTimeTracking)
							+ var.newLine + "##Size: X" + var.xRange + "Z" + var.zRange
							+ var.newLine);

			Out.out("");

			final File serverLevel = new File(var.worldPath + var.fileSeparator + "level.dat");
			final File backupLevel =
					new File(var.worldPath + var.fileSeparator + "level_backup.dat");

			Out.out("Backing up level.dat to level_backup.dat.");
			Misc.copyFile(serverLevel, backupLevel);
			Out.out("");

			final Coordinates spawn = SpawnPoint.getSpawn(serverLevel);
			Out.out("Spawn point detected: [X,Y,Z] " + spawn);

			FileWrite.AppendTxtFile(var.worldPath + var.fileSeparator + var.logFile, "# Seed: "
					+ var.randomSeed + var.newLine + "# Spawn: " + spawn.toString() + var.newLine);

			boolean overridden = false;
			if (var.xOffset == null) {
				var.xOffset = spawn.getX();
			} else {
				overridden = true;
			}
			if (var.zOffset == null) {
				var.zOffset = spawn.getZ();
			} else {
				overridden = true;
			}

			if (var.useChunks) {		// use Chunks or Regions
				var.xRange = (int) (Math.ceil(((double) var.xRange) / ((double) 16))) * 16;			//say xRange was entered as 1000.  this changes it to be 1008, a multiple of 16. (the size of a chunk)
				var.zRange = (int) (Math.ceil(((double) var.zRange) / ((double) 16))) * 16;			//say zRange was entered as 2000.  there is no change, as it already is a multiple of 16.
				var.xOffset = (int) (Math.ceil(((double) var.xOffset) / ((double) 16))) * 16;
				var.zOffset = (int) (Math.ceil(((double) var.zOffset) / ((double) 16))) * 16;
			} else {
				var.xRange = (int) (Math.ceil(((double) var.xRange) / ((double) 512))) * 512;			//say xRange was entered as 1000.  this changes it to be 1024, a multiple of 512. (the size of a region)
				var.zRange = (int) (Math.ceil(((double) var.zRange) / ((double) 512))) * 512;			//say zRange was entered as 2048.  there is no change, as it already is a multiple of 512.
				var.xOffset = (int) (Math.ceil(((double) var.xOffset) / ((double) 512))) * 512;
				var.zOffset = (int) (Math.ceil(((double) var.zOffset) / ((double) 512))) * 512;
			}

			if (overridden) {
				Out.out("Centering land generation on [" + var.xOffset + ", " + var.zOffset
						+ "] due to switches.");
			}

			Out.out("");

			double xLoops, zLoops;
			int curXloops = 0;
			int curZloops = 0;
			int xRangeAdj = 0;
			int zRangeAdj = 0;

			// have main loop make an arraylist of spawnpoints
			// read from a file if MLG has run before on this world.  save to arraylist
			// remove existing points from new list.
			// run mlg on remaining list of spawn points.

			// X
			xLoops = ((double) var.xRange / (double) var.increment);		//How many loops do we need?
			xLoops = Math.ceil(xLoops);								//round up to find out!
			xRangeAdj = (int) (xLoops * var.increment);
			xLoops = xLoops + 1;

			// Z
			zLoops = ((double) var.zRange / (double) var.increment);		//How many loops do we need?
			zLoops = Math.ceil(zLoops);								//round up to find out!
			zRangeAdj = (int) (zLoops * var.increment);
			zLoops = zLoops + 1;

			Out.out("Calculating Spawn Points...");

			// Perfect Squares Code:

			int totalIterations = (int) (xLoops * zLoops);
			int currentIteration = 0;

			long differenceTime = System.currentTimeMillis();

			Long timeTracking = 0L;

			final ArrayList<Coordinates> launchList = new ArrayList<Coordinates>(totalIterations);

			for (int currentX = 0; currentX <= (xRangeAdj / 2); currentX += var.increment) {
				curXloops++;
				boolean eastEdgeReached = false;

				if (curXloops == 1) {
					currentX = (((0 - var.xRange) / 2) + (var.incrementFull / 2));  	// West Edge of map

				} else if (currentX >= ((xRangeAdj / 2) - (var.increment / 2))) {
					currentX = ((var.xRange / 2) - (var.incrementFull / 2));			// East Edge of map
					eastEdgeReached = true;
				}

				for (int currentZ = 0; currentZ <= (zRangeAdj / 2); currentZ += var.increment) {
					currentIteration++;

					curZloops++;
					boolean southEdgeReached = false;
					if (curZloops == 1) {
						currentZ = (((0 - var.zRange) / 2) + (var.incrementFull / 2));	// North Edge of map
					} else if (currentZ >= ((zRangeAdj / 2) - (var.increment / 2))) {
						currentZ = ((var.zRange / 2) - (var.incrementFull / 2));		// South Edge of map
						southEdgeReached = true;
					}

					{
						// add Coordinates to arraylist here
						final Coordinates tempCoords =
								new Coordinates(currentX + var.xOffset, 64, currentZ + var.zOffset);
						launchList.add(tempCoords);

						if (var.testing) {
							System.out.println(tempCoords);
						}
					}

					if (curZloops == 1) {			// We are at the North edge.  We have special code for the North edge, so we need to change currentZ to be normal again.
						currentZ =
								(int) ((Math.ceil((((0 - zRangeAdj) / 2) / var.increment))) * var.increment);
					}
					if (southEdgeReached) {
						currentZ = zRangeAdj;		// We reached the South edge, so we make sure that we exit the "for loop", bypassing the "1152 bug"
					}

				}
				curZloops = 0;
				if (curXloops == 1) {			// We are at the West edge.  We have special code for the West edge, so we need to change currentX to be normal again.
					currentX =
							(int) ((Math.ceil((((0 - xRangeAdj) / 2) / var.increment))) * var.increment);
				}
				if (eastEdgeReached) {
					currentX = xRangeAdj;		// We reached the East edge, so we make sure that we exit the "for loop", bypassing the "1152 bug"
				}

			}

			//get existing list, and remove this list from launchList
			final ArrayList<Coordinates> removeList =
					FileRead.readArrayListCoordLog(var.worldPath + var.fileSeparator + var.logFile);

			if (!(removeList.isEmpty())) {
				Arraylist.arrayListRemove(launchList, removeList);
			}

			removeList.clear();		// we are done with this now.

			System.gc();		//run the garbage collector - hopefully free up some memory!

			currentIteration = 0;
			totalIterations = launchList.size();
			Coordinates xyz = null;
			final Iterator<Coordinates> coordArrayIterator = launchList.iterator();
			while (coordArrayIterator.hasNext()) {
				currentIteration++;
				xyz = coordArrayIterator.next();

				//////// Start server launch code

				String percentDone =
						Double.toString((((double) currentIteration - 1) / totalIterations) * 100);
				final int percentIndex =
						((percentDone.indexOf(".") + 3) > percentDone.length()) ? percentDone
								.length() : (percentDone.indexOf(".") + 3);		//fix index on numbers like 12.3
				percentDone =
						percentDone.substring(0,
								(percentDone.indexOf(".") == -1 ? percentDone.length()
										: percentIndex));			//Trim output, unless whole number

				Out.out("Setting spawn to [X,Y,Z]: " + xyz + " (" + currentIteration + " of "
						+ totalIterations + ") " + percentDone + "% Done"); // Time Remaining estimate

				timeTracking = System.currentTimeMillis();

				//NEW CODE:
				differenceTime =
						(timeTracking - generationStartTimeTracking) / (currentIteration + 1);		// Updated.  we now count all runs, instead of the last 4.
				differenceTime *= 1 + (totalIterations - currentIteration);									// this should provide a more accurate result.
				Out.out("Estimated time remaining: " + Time.displayTime(differenceTime));						// I've noticed it gets pretty accurate after about 8 launches!

				// Set the spawn point
				SpawnPoint.setSpawn(serverLevel, xyz);

				// Launch the server
				boolean serverSuccess = false;

				serverSuccess = Server.runMinecraft();
				Out.out("");

				//////// End server launch code

				if (serverSuccess) {
					// Write the current Coordinates to log file!
					FileWrite.AppendTxtFile(var.worldPath + var.fileSeparator + var.logFile,
							xyz.toString() + var.newLine);
				} else {
					System.exit(1);				// we got a warning or severe error
				}

			}

			if (currentIteration == 0) {
				Out.out("Nothing to generate!");
			} else {
				Out.out("Finished generating chunks.");
			}

			Misc.copyFile(backupLevel, serverLevel);
			backupLevel.delete();
			Out.out("Restored original level.dat.");

			Out.out("Generation complete in: "
					+ Time.displayTime(var.startTime, System.currentTimeMillis()));
			Time.waitTenSec(false);

			//TODO: add if's

			if (var.webLaunch) {		//if webLaunch is already false, don't check for these things
				if (java.awt.GraphicsEnvironment.isHeadless()) {
					var.webLaunch = false;					//headless enviroment - cant bring up webpage!
				}
				final File web1 = new File("web");
				final File web2 = new File("web.txt");		//user has put in the magical file to not launch the webpage
				final File web3 = new File("web.txt.txt");
				if (web2.exists() || (web1.exists() || web3.exists())) {  //check for "web.txt", if not found, check for "web", and if still not found, check for "web.txt.txt"
					var.webLaunch = false;
				}
			}

			if (var.webLaunch && java.awt.Desktop.isDesktopSupported()) {
				final URI splashPage = URI.create("http://adf.ly/520855/splashbanner");
				try {
					java.awt.Desktop.getDesktop().browse(splashPage);
				} catch (final IOException e) {
					Out.err("Error displaying webpage... " + e.getLocalizedMessage());
				}
			} else {
				Out.out("Please Visit: http://adf.ly/520855/mlg");
				Out.out("Or: " + var.WEBSITE);
				Out.out("Thanks!");
			}

		} catch (final IOException ex) {
			Logger.getLogger(Main.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
		}
	}
}
