/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */

/*
	Part of the Processing project - http://processing.org

	Copyright (c) 2012-15 The Processing Foundation
	Copyright (c) 2008-12 Ben Fry and Casey Reas

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software Foundation,
	Inc., 59 Temple Place, Suite 330, Boston, MA	02111-1307	USA
 */

package processing.mode.java;

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;

import processing.app.Base;
import processing.app.Platform;
import processing.app.Preferences;
import processing.app.RunnerListener;
import processing.app.Sketch;
import processing.app.SketchException;
import processing.app.Util;
import processing.app.contrib.ModeContribution;
import processing.core.PApplet;
import processing.mode.java.runner.Runner;


/**
 * Class to handle running Processing from the command line.
 *
 * @author fry
 */
public class Commander implements RunnerListener {
	static final String helpArg = "--help";
//	static final String preprocArg = "--preprocess";
	static final String buildArg = "--build";
	static final String runArg = "--run";
	static final String presentArg = "--present";
	static final String sketchArg = "--sketch=";
	static final String forceArg = "--force";
	static final String outputArg = "--output=";
	static final String exportApplicationArg = "--export";
	static final String noJavaArg = "--no-java";
	static final String platformArg = "--platform=";
	static final String bitsArg = "--bits=";
//	static final String preferencesArg = "--preferences=";

	static final int HELP = -1;
	static final int PREPROCESS = 0;
	static final int BUILD = 1;
	static final int RUN = 2;
	static final int PRESENT = 3;
//	static final int EXPORT_APPLET = 4;
	static final int EXPORT = 4;

	Sketch sketch;

	PrintStream systemOut;
	PrintStream systemErr;


	static public void main(String[] args) {
		// Do this early so that error messages go to the console
		Base.setCommandLine();
		// init the platform so that prefs and other native code is ready to go
		Platform.init();
		// make sure a full JDK is installed
		//Base.initRequirements();

		// launch command line handler
		new Commander(args);
	}


	public Commander(String[] args) {
		String sketchPath = null;
		File sketchFolder = null;
		String pdePath = null;	// path to the .pde file
		String outputPath = null;
		File outputFolder = null;
		boolean outputSet = false;	// set an output folder
		boolean force = false;	// replace that no good output folder
//		String preferencesPath = null;
		int platform = PApplet.platform; // default to this platform
//		int platformBits = Base.getNativeBits();
		int task = HELP;
		boolean embedJava = true;

		try {
			if (Platform.isWindows()) {
				// On Windows, it needs to use the default system encoding.
				// https://github.com/processing/processing/issues/1633
				systemOut = new PrintStream(System.out, true);
				systemErr = new PrintStream(System.err, true);
			} else {
				// On OS X, the output goes as MacRoman or something else useless.
				// http://code.google.com/p/processing/issues/detail?id=1418
				// (Not sure about Linux, but this has worked since 2.0)
				systemOut = new PrintStream(System.out, true, "UTF-8");
				systemErr = new PrintStream(System.err, true, "UTF-8");
			}
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			System.exit(1);
		}

		int argOffset = 0;
		for (String arg : args) {
			argOffset++;

			if (arg.length() == 0) {
				// ignore it, just the crappy shell script

			} else if (arg.equals(helpArg)) {
				// mode already set to HELP

//			} else if (arg.equals(preprocArg)) {
//				task = PREPROCESS;

			} else if (arg.equals(buildArg)) {
				task = BUILD;
				break;

			} else if (arg.equals(runArg)) {
				task = RUN;
				break;

			} else if (arg.equals(presentArg)) {
				task = PRESENT;
				break;

			} else if (arg.equals(exportApplicationArg)) {
				task = EXPORT;
				break;

			} else if (arg.equals(noJavaArg)) {
				embedJava = false;

			} else if (arg.startsWith(platformArg)) {
//				complainAndQuit("The --platform option has been removed from Processing 2.1.", false);
				String platformStr = arg.substring(platformArg.length());
				platform = Platform.getIndex(platformStr);
				if (platform == -1) {
					complainAndQuit(platformStr + " should instead be " +
													"'windows', 'macosx', or 'linux'.", true);
				}

			} else if (arg.startsWith(bitsArg)) {
				complainAndQuit("The --bits option has been removed.", false);
//				String bitsStr = arg.substring(bitsArg.length());
//				if (bitsStr.equals("32")) {
//					platformBits = 32;
//				} else if (bitsStr.equals("64")) {
//					platformBits = 64;
//				} else {
//					complainAndQuit("Bits should be either 32 or 64, not " + bitsStr, true);
//				}

			} else if (arg.startsWith(sketchArg)) {
				sketchPath = arg.substring(sketchArg.length());
				sketchFolder = new File(sketchPath);
				if (!sketchFolder.exists()) {
					complainAndQuit(sketchFolder + " does not exist.", false);
				}
				File pdeFile = new File(sketchFolder, sketchFolder.getName() + ".pde");
				if (!pdeFile.exists()) {
					complainAndQuit("Not a valid sketch folder. " + pdeFile + " does not exist.", true);
				}
				pdePath = pdeFile.getAbsolutePath();

//			} else if (arg.startsWith(preferencesArg)) {
//				preferencesPath = arg.substring(preferencesArg.length());

			} else if (arg.startsWith(outputArg)) {
				outputSet = true;
				outputPath = arg.substring(outputArg.length());

			} else if (arg.equals(forceArg)) {
				force = true;

			} else {
				complainAndQuit("I don't know anything about " + arg + ".", true);
			}
		}
		String[] sketchArgs = PApplet.subset(args, argOffset);

//		if ((outputPath == null) &&
//				(task == PREPROCESS || task == BUILD ||
//				 task == RUN || task == PRESENT)) {
//			complainAndQuit("An output path must be specified when using " +
//											preprocArg + ", " + buildArg + ", " +
//											runArg + ", or " + presentArg + ".");
//		}
		if (task == HELP) {
			printCommandLine(systemOut);
			System.exit(0);
		}

		if (outputSet) {
			if (outputPath == null) {
				complainAndQuit("An output path must be specified.", true);
			}

			outputFolder = new File(outputPath);
			if (outputFolder.exists()) {
				if (force) {
					Util.removeDir(outputFolder);
				} else {
					complainAndQuit("The output folder already exists. " +
													"Use --force to remove it.", false);
				}
			}

			if (!outputFolder.mkdirs()) {
				complainAndQuit("Could not create the output folder.", false);
			}
		}

//		// run static initialization that grabs all the prefs
//		// (also pass in a prefs path if that was specified)
//		if (preferencesPath != null) {
//			Preferences.init(preferencesPath);
//		}

		Preferences.init();
		Base.locateSketchbookFolder();

		if (sketchPath == null) {
			complainAndQuit("No sketch path specified.", true);

//		} else if (!pdePath.toLowerCase().endsWith(".pde")) {
//			complainAndQuit("Sketch path must point to the main .pde file.", false);

		} else {

			if (outputSet) {
				if (outputPath.equals(sketchPath)) {
					complainAndQuit("The sketch path and output path cannot be identical.", false);
				}
			}

			boolean success = false;

//			JavaMode javaMode =
//				new JavaMode(null, Base.getContentFile("modes/java"));
			JavaMode javaMode = (JavaMode)
				ModeContribution.load(null, Platform.getContentFile("modes/java"),
															"processing.mode.java.JavaMode").getMode();
			try {
				sketch = new Sketch(pdePath, javaMode);

				if (!outputSet) {
					outputFolder = sketch.makeTempFolder();
				}

				if (task == BUILD || task == RUN || task == PRESENT) {
					JavaBuild build = new JavaBuild(sketch);
					File srcFolder = new File(outputFolder, "source");
					String className = build.build(srcFolder, outputFolder, true);
//					String className = build.build(sketchFolder, outputFolder, true);
					if (className != null) {
						success = true;
						if (task == RUN || task == PRESENT) {
							Runner runner = new Runner(build, this);
							if (task == PRESENT) {
								runner.present(sketchArgs);
							} else {
								runner.launch(sketchArgs);
							}
							success = !runner.vmReturnedError();
						}
					} else {
						success = false;
					}

				} else if (task == EXPORT) {
					if (outputPath == null) {
						javaMode.handleExportApplication(sketch);
					} else {
						JavaBuild build = new JavaBuild(sketch);
						build.build(true);
						if (build != null) {
							String variant = Platform.getVariant();
							success = build.exportApplication(outputFolder, platform, variant, embedJava);
						}
					}
				}
				if (!success) {	// error already printed
					System.exit(1);
				}
				systemOut.println("Finished.");
				System.exit(0);

			} catch (SketchException re) {
				statusError(re);
				System.exit(1);

			} catch (IOException e) {
				e.printStackTrace();
				System.exit(1);
			}
		}
	}


	public void statusNotice(String message) {
		systemErr.println(message);
	}


	public void statusError(String message) {
		systemErr.println(message);
	}


	public void statusError(Exception exception) {
		if (exception instanceof SketchException) {
			SketchException re = (SketchException) exception;

			int codeIndex = re.getCodeIndex();
			if (codeIndex != -1) {
				// format the runner exception like emacs
				//blah.java:2:10:2:13: Syntax Error: This is a big error message
				// Emacs doesn't like the double line thing coming from Java
				// https://github.com/processing/processing/issues/2158
				String filename = sketch.getCode(codeIndex).getFileName();
				int line = re.getCodeLine() + 1;
				int column = re.getCodeColumn() + 1;
				//if (column == -1) column = 0;
				// TODO if column not specified, should just select the whole line.
				// But what's the correct syntax for that?
				systemErr.println(filename + ":" +
					line + ":" + column + ":" +
					line + ":" + column + ":" + " " + re.getMessage());

			} else {	// no line number, pass the trace along to the user
				exception.printStackTrace();
			}
		} else {
			exception.printStackTrace();
		}
	}


	void complainAndQuit(String lastWords, boolean schoolEmFirst) {
		if (schoolEmFirst) {
			printCommandLine(systemErr);
		}
		systemErr.println(lastWords);
		System.exit(1);
	}


	static void printCommandLine(PrintStream out) {
		out.println();
		out.println("Command line edition for Processing " + Base.getVersionName() + " (Java Mode)");
		out.println();
		out.println("--help							 Show this help text. Congratulations.");
		out.println();
		out.println("--sketch=<name>			Specify the sketch folder (required)");
		out.println("--output=<name>			Specify the output folder (optional and");
		out.println("										 cannot be the same as the sketch folder.)");
		out.println();
		out.println("--force							The sketch will not build if the output");
		out.println("										 folder already exists, because the contents");
		out.println("										 will be replaced. This option erases the");
		out.println("										 folder first. Use with extreme caution!");
		out.println();
		out.println("--build							Preprocess and compile a sketch into .class files.");
		out.println("--run								Preprocess, compile, and run a sketch.");
		out.println("--present						Preprocess, compile, and run a sketch in presentation mode.");
		out.println();
		out.println("--export						 Export an application.");
		out.println("--no-java						Do not embed Java. Use at your own risk!");
		out.println("--platform					 Specify the platform (export to application only).");
		out.println("										 Should be one of 'windows', 'macosx', or 'linux'.");
//		out.println("--bits							 Must be specified if libraries are used that are");
//		out.println("										 32- or 64-bit specific such as the OpenGL library.");
//		out.println("										 Otherwise specify 0 or leave it out.");

		out.println();
		out.println("The --build, --run, --present, or --export must be the final parameter");
		out.println("passed to Processing. Arguments passed following one of those four will");
		out.println("be passed through to the sketch itself, and therefore available to the");
		out.println("sketch via the 'args' field. To pass options understood by PApplet.main(),");
		out.println("write a custom main() method so that the preprocessor does not add one.");
		out.println("https://github.com/processing/processing/wiki/Command-Line");

		out.println();
	}


	@Override
	public void startIndeterminate() {
	}


	@Override
	public void stopIndeterminate() {
	}


	@Override
	public void statusHalt() {
	}


	@Override
	public boolean isHalted() {
		return false;
	}
}
