/* -*- 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;

    // Turns out the output goes as MacRoman or something else useless.
    // http://code.google.com/p/processing/issues/detail?id=1418
    try {
      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 will 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;
  }
}
