/*

Copyright: All contributers to the Umple Project

This file is made available subject to the open source license found at:
http://umple.org/license

Command line tools: To compile Umple files, or to do diffs

*/

namespace cruise.umple;

/**
 * Config is the parsed command line information for UmpleConsole.
 */
class UmpleConsoleConfig {
  depend java.util.*;
  depend java.util.stream.*;
  depend joptsimple.*;
  depend java.io.*;

  defaulted boolean version = false;
  defaulted boolean help = false;
  defaulted boolean performance = false;
  
  internal Optional<String> generate   = Optional.empty();
  defaulted boolean override           = false;
  internal Optional<String> path       = Optional.empty();
  internal Optional<String> compile    = Optional.empty();
  internal Optional<String> importFile = Optional.empty();
  internal List<String> suboptions     = Collections.<String>emptyList();

  String umpleFile;
  String[] linkedFiles;

  UmpleConsoleConfig(OptionSet optSet) {
    this(optSet.nonOptionArguments().size() > 0 ? optSet.nonOptionArguments().get(0) : null);

    this.version    = optSet.has("v");
    this.help       = optSet.has("help");
    this.performance = optSet.has("performance");
    this.importFile = Optional.ofNullable((String)optSet.valueOf("import"));
    this.generate   = Optional.ofNullable((String)optSet.valueOf("generate"));
    this.override   = optSet.has("override");
    this.path       = Optional.ofNullable((String)optSet.valueOf("path"));
    
    final List<String> files = optSet.nonOptionArguments();
    this.linkedFiles = (files.size() > 1 ? files.subList(1, files.size()) : Collections.<String>emptyList());

    this.suboptions = optSet.valuesOf("suboption").stream()
      .map(o -> (String)o)
      .collect(Collectors.toList());

    // compile takes a bit more logic..
    this.compile = Optional.ofNullable((String)optSet.valueOf("c"));
  }

  public List<File> getLinkedFilesAsFile() {
    return this.linkedFiles.stream().map(p -> new File(p)).collect(Collectors.toList());
  }

  public Optional<String> getGenerate() {
    return this.generate;
  }

  public void setGenerate(String generate) {
    this.generate = Optional.of(generate);
  }

  public Optional<String> getCompile() {
    return this.compile;
  }

  public void setCompile(String compile) {
    this.compile = Optional.of(compile);
  }

  public void setImportFile(String importFile) {
    this.importFile = Optional.of(importFile);
  }

  public Optional<String> getImportFile() {
    return this.importFile;
  }

  public void setPath(String path) {
    this.path = Optional.of(path);
  }

  public Optional<String> getPath() {
    return this.path;
  }

  public List<String> getSuboptions() {
    return this.suboptions;
  }

  public void setSuboptions(List<String> suboptions) {
    this.suboptions = suboptions;
  }

  public void addSuboption(String suboption) {
    this.suboptions.add(suboption);
  }
}

/*
 * Main program for the command line Umple compiler
 * Run java -jar umple.jar --help for details 
 * Takes an umple file as an argument, which can in turn use (include) other
 */
class UmpleConsoleMain
{
  depend cruise.umple.compiler.*;
  depend cruise.umple.compiler.exceptions.*;
  depend java.util.List;
  depend java.util.Arrays;
  depend joptsimple.*;
  depend java.io.*;
  depend java.text.DecimalFormat;

  UmpleConsoleConfig cfg;

  static OptionParser optparser = initializeOptionParser();

  //startTime repersents model run start time while endTime represents end time
  Double startTime = 0.0;
  Double endTime = 0.0;
  
  /**
   * Create a main console. If the args are invalid, an IllegalStateException is thrown. 
   * @throws IllegalStateException If invalid arguments are passed and joptsimple fails to parse. 
   */
  UmpleConsoleMain(String[] args) {
    optparser = initializeOptionParser();

    OptionSet optset = optParse(args);
    if (optset == null) {
      throw new IllegalStateException("Illegal arguments were passed, check usage.");
    }
    
    this.cfg = new UmpleConsoleConfig(optset);
  }

  public int runConsole() {
    if (preModelOptionProcess())
    {
      return 0;
    }

    if (cfg.getUmpleFile() == null || "".equals(cfg.getUmpleFile()))
      {
      System.out.println("Please specify <umple_file> to process");
      printUsage();
      return 1;
    }

    final String filename = cfg.getUmpleFile();
    System.out.println("Processing -> " + filename);

    final UmpleFile umpleFile = new UmpleFile(filename);
    //this loop is used to add linked umple files and to show names of them.
    cfg.getLinkedFilesAsFile().stream()
        .filter(File::exists)
        .map(File::getPath)
        .forEach(umpleFile::addLinkedFiles);
       
    final UmpleModel model = new UmpleModel(umpleFile);

    if (postModelOptionProcess(model)) {
      return 0;
    }

    try {
      try {
        startTime = System.currentTimeMillis()/1000.0;  
        model.run();
        endTime = System.currentTimeMillis()/1000.0;  

      } catch (UmpleCompilerException e) {
        System.err.print(e.getMessage());
        if (!model.isShouldGenerate())
        {
          return -1;
        }
      }

      // Compile success means the output was generated
      boolean compileSuccess = model.getLastResult().getWasSuccess();

      // The "c" option causes an attempt to compile the resulting base language
      // code. Not completely tested at current time.
      if (compileSuccess && cfg.getCompile().isPresent())
      {
        compileSuccess = CodeCompiler.compile(model, cfg.getCompile().get());
      }
      
       // Get the performance execution.
       // Print out performance test result.     
        if(cfg.getPerformance())
         {
        
           DecimalFormat df=new DecimalFormat("0.000");
        
           System.out.println("Performance Metrics:");
           System.out.println("--> Total Execution Time: " + df.format((endTime -startTime)) +" s");
           System.out.println("--> Parsing Time: " + df.format((model.getParsingTime() -startTime))+" s");
           for (String key : model. getHashMap().keySet()) 
           {
             Double generateTime = Double.parseDouble(model. getHashMap().get(key));
             
             System.out.println("--> Gernerating Target Language " + key + " time: "+df.format(generateTime)+" s");
           }                           
         }

      String successWord = compileSuccess ? "Success! " : "";
      System.out.println(successWord + "Processed "+ cfg.getUmpleFile() + ".");
      cfg.getLinkedFilesAsFile().stream()
        .filter(File::exists)
        .forEach(f -> System.out.println(successWord + "Processed "+ f.getPath() + "."));
      
      return (compileSuccess ? 0 : 1); 
    }
    catch(Exception ex)
    {
      System.err.println("Umple compiler error. Stack trace follows");
      cruise.umple.util.ExceptionDumper.dumpCompilerError(ex);
      return -1;
    }
  }

  public static void main(String[] args)
      {
    UmpleConsoleMain console = new UmpleConsoleMain(args);

    int result = console.runConsole();
    if (result < 0) {
      System.exit(result);
      }
    }
    
  private void generateUmple(String filename)
  {
    boolean isSuccess = false;
    try
    {
      UmpleImportModel umple;
      if (filename.endsWith(".scxml")) {
        ScxmlImportHandler handler = new ScxmlImportHandler();
        umple = handler.readDataFromXML(filename);
      } else {
        EcoreImportHandler handler = new EcoreImportHandler();
        umple = handler.readDataFromXML(filename);
      }
      isSuccess = umple.generateUmpleFile(filename + ".ump");
    }
    catch (Exception e)
    {
      System.err.print(e.getMessage());
    } 
    if (isSuccess)
    {
      System.out.println("Success! Processed "+ filename + ".");
    }
    else
    {
      System.err.print("No file generated, parsing error.");
    }
  }

  /**
    * optionProcess: process the option arguments which does not require UmpleModel
    * Argument: optSet - set of the options and corresponding arguments
    * Return: boolean - If application should terminate immediately after return
    */
  private boolean preModelOptionProcess()
  {
    if (cfg.getVersion())
    {
      System.out.println("Version: " + UmpleModel.VERSION_NUMBER);
    }
    else if (cfg.getHelp())
    {
      printUsage();
    }
    else if (cfg.getImportFile().isPresent())
    {
      generateUmple(cfg.getImportFile().get());
    }
    else
    {
      return false;
    }
    return true;
  }

  private boolean postModelOptionProcess(UmpleModel model) {
    if (cfg.getGenerate().isPresent()) {
      String path = cfg.getPath().orElse("");
      boolean override = cfg.getOverride();
      String lang = cfg.getGenerate().orElse(null);

      GenerateTarget gt = new GenerateTarget(lang, path);
      gt.setOverrideAll(override);
      
      cfg.getSuboptions().stream().forEach(gt::addSuboption); 

      model.addGenerate(Arrays.asList(gt));
    }
    
    return false;
  }
  
  private static OptionParser initializeOptionParser()
  {
    OptionParser optparser = new OptionParser();
    
    String languages = String.join(",", UmpleModel.validLanguages );
    optparser.acceptsAll(Arrays.asList("version", "v"), "Print out the current Umple version number");
    optparser.acceptsAll(Arrays.asList("performance", "p"), "Indicate time taken to parse and generate code");
    optparser.acceptsAll(Arrays.asList("help"), "Display the help message");
    optparser.acceptsAll(Arrays.asList("g", "generate"), "Specify the output language: " + languages).withRequiredArg().ofType(String.class);
    optparser.acceptsAll(Arrays.asList("override"), "If a output language <lang> is specified using option -g, output will only generate language <lang>");
    optparser.acceptsAll(Arrays.asList("path"), "If a output language is specified using option -g, output source code will be placed to path").withRequiredArg().ofType(String.class);
    optparser.acceptsAll(Arrays.asList("c","compile"), "Indicate to the entry class to compile, or with argument - to compile all outputfiles").withRequiredArg().ofType(String.class);
    optparser.acceptsAll(Arrays.asList("import","i"), "Indicate to read in XMI model and generate ump files").withRequiredArg().ofType(String. class);
    optparser.acceptsAll(Arrays.asList("suboption","s"), "Indicate to generate files with suboptions").withRequiredArg().ofType(String.class);
    
    return optparser;
  }
  
  private static void printUsage()
  {
    System.out.println("For more detailed information go to http://manual.umple.org"+System.lineSeparator()
      +"Usage: java -jar umple.jar [options] <umple_file>"+System.lineSeparator()
      +"Example: java -jar umple.jar airline.ump");
    try
    {
      optparser.printHelpOn(System.out);
    } 
    catch (IOException e)
    {
      e.printStackTrace();
    }
  }

  static OptionSet optParse(String[] args)
  {
    OptionSet optSet = null;

    try
    {
      optSet = optparser.parse(args);
    } 
    catch (joptsimple.OptionException e)
    {
      System.out.println("Option:" + e.getMessage());
      printUsage();
    }

    return optSet;
  }
}

/*
 * Main program for a jar that can inject calls to Umple at runtime
 */
class UmpleRunMain
{

  depend cruise.umple.compiler.*;
  depend cruise.umple.compiler.exceptions.*;
  depend cruise.umple.builder.*;
  depend cruise.umple.util.*;
  depend java.net.*;
  depend java.io.*;

  public static void main(String[] args)
  {
    if (args.length < 2)
    {
      System.out.println("Usage: java -jar umplerun.jar <umple_file> <cmd_file>\nExample: java -jar umple.jar airline.ump airline.cmd");
      return;
    }

    String filename = args[0];
    String cmdFilename = args[1];
    UmpleFile umpleFile = new UmpleFile(filename);
    UmpleModel model = new UmpleModel(umpleFile);
    boolean shouldDebug = args.length >= 3 && args[2].equals("--debug");
    boolean shouldCompile = !shouldDebug;

    try
    {
      if (shouldCompile)
      {
        System.out.print("Compiling "+ filename +"... ");
        model.run();
        System.out.println("success.");
      }
      else
      {
        System.out.print("Skipping compilation, entering debug mode");
      }

      System.out.print("Building model... ");
      Builder b = new Builder();
      URL jarfile = b.compile(".",umpleFile.getSimpleFileName() + ".jar",umpleFile.getSimpleFileName(),"1.6");

      if (jarfile == null)
      {
        System.out.println("failed");
        return;
      }
      System.out.println("success.");

      System.out.print("Loading model into memory... ");
      URL urls [] = { jarfile };
      URLClassLoader cl = new URLClassLoader(urls);
      System.out.println("success.");

      Command cmd = new Command(cl);

      System.out.println("Running commands:");
      boolean isFirst = true;
      for (String input : SampleFileWriter.readContent(new File(cmdFilename)).split("\n"))
      {
        if (isFirst)
        {
          cmd.addAttributes(input);
          isFirst = false;
          continue;
        }
        cmd.exec(input);
        for (String message : cmd.popMessages())
        {
          System.out.println("  " + message);
        }
      }

      System.out.println("Done.");
    }
    catch(Exception e)
    {
      System.out.println("failed.");
      System.err.print(e.getMessage());
    }
  }
}

/*
 * Main program for umplesync.jar which is used by umpleonline
 * when it wants to send changes to the diagram and have them applied to
 * the text and vice versa
 *
 * TODO: Code smell - rename?
 */
use AbstractServer.ump; 
use ConnectionToClient.ump;
 
class PlaygroundMain
{
  isA AbstractServer;
  
  depend cruise.umple.sync.*;
  depend cruise.umple.compiler.*;
  depend cruise.umple.compiler.exceptions.*;
  depend cruise.umple.util.*;
  depend java.io.*;
  depend java.util.*;
  depend java.net.*;
  depend com.sun.management.OperatingSystemMXBean;
  depend java.lang.management.*;

  Boolean isServer=false;
  int commandsRun = 0;
  static String[] previousCommand = new String[1];
  static String[] lastCommand = new String[1];
  static String[] currentCommand = new String[1];

  static PlaygroundMain theInstance;

  public PlaygroundMain(int port) 
  {
    super(port);
    previousCommand[0]="";    
    lastCommand[0]="";    
    currentCommand[0]="";
  }
  
  // The main method when started from the command line
  public static void main(String[] args) {
    theInstance = new PlaygroundMain(0);
    theInstance.processCommand(args, null);
  }
    
  // A command arriving when this is a server
  public void handleMessageFromClient
    (String msg, ConnectionToClient client)
  {
     // DEBUG Uncomment the following line to debug
  //   System.err.println("Received client command <"+msg+">");
    // Split the message from the client into tokens and process as a command
    List<String> ls = ArgumentTokenizer.tokenize(msg,false);
    processCommand(ls.toArray(new String[ls.size()]), client);
    try {
      client.close();
    }
    catch (IOException e) {
    }    
  } 
  
  public void returnCommandResult(String resultToOutput, ConnectionToClient client) {
    if(isServer) {
      // Client must not be null
      try {
        client.sendToClient(resultToOutput);
      }
      catch (IOException e) {
        System.err.print("Unable to send to client "+resultToOutput);
        // TO DO this is serious; should shut down?
      }
    }
    else {
      System.out.print(resultToOutput);
    }
  }

  // Process arguments either from the command line, or through a socket
  // If through a socket, the socket must be closed after returning
  public void processCommand(String[] args, ConnectionToClient client)
  {
    String answer = "";
    commandsRun++;
    previousCommand = lastCommand;
    lastCommand = currentCommand;
    currentCommand = args;

    if (args.length == 0 || ("-help".equals(args[0])) )
    {
      returnCommandResult("Syntax:\n", client);
      returnCommandResult("-version  : give umple version running\n", client);
      returnCommandResult("-help  : give command line syntax\n", client);
      returnCommandResult("-classList <filename>\n", client);
      returnCommandResult("-interfaceList <filename>\n", client);
      returnCommandResult("-source <filename>  : generate code using an embedded generate directive\n", client);
      returnCommandResult("-server <port>  : start this as a server to take commends\n", client);
      returnCommandResult("-quit  : quit if running as a server\n", client);

      
      returnCommandResult("-generate "+
        "<Java|Cpp|Ruby|Ecore|Papyrus|TextUml|GvStateDiagram|"+
        "GvClassDiagram|GvClassTraitDiagram|GvEntityRelationshipDiagram|"+
        "Alloy|NuSMV|Violet|Umlet> <filename>\n", client); // Compile filename and generate output
      returnCommandResult("<action> <deltaCode> <filename>\n", client);  
      return;
    }
    
    if ("-version".equals(args[0])) {
      returnCommandResult("Version: " + UmpleModel.VERSION_NUMBER, client);
      return;
    }    

    if ("-log".equals(args[0])) {
      if(isServer) {
          returnCommandResult("Number of commands run since start: "+commandsRun+"\n", client);      
          returnCommandResult("Last command: [[\n"+concatForLog(lastCommand)+"\n]]\n", client);      
          returnCommandResult("Previous command: [[\n"+concatForLog(previousCommand)+"\n]]\n", client);
          returnCommandResult("Port: "+getPort()+"\n", client);
          returnCommandResult("Number of clients working: "+getClientConnections().length+"\n", client);
          OperatingSystemMXBean osBean = ManagementFactory.getPlatformMXBean(OperatingSystemMXBean.class);         
          returnCommandResult("CPU load of this JVM: "+osBean.getProcessCpuLoad()+"\n", client);   
          returnCommandResult("CPU load overall: "+osBean.getSystemCpuLoad()+"\n", client);  
      }
      else {
        returnCommandResult("Not running as a server\n", client);        
      }
      return;
    }  

    // Start a server to process a series of commands that can be controlled from Php
    if ("-server".equals(args[0])) {
      isServer = true;

      // Start server on port 5556 if in a test environment, otherwise use port 5555
      // Test environment means that the directory has 'test' in it
      int port = 5555;
      if(System.getProperty("user.dir").contains("test")) {
        port = 5556;
      }
      

      if(args.length >=2) {
        try {
         port = Integer.parseInt(args[1]);
        }
        catch (NumberFormatException e)
        {}
      }
      setPort(port);
      try 
      {
        this.listen(); //Start listening for connections
      } 
      catch (Exception ex) 
      {
        System.out.println("ERROR - Could not listen for clients!");
      }
      return;
    }
    
    if ("-quit".equals(args[0])) {
      if(isServer) {
        try {
          this.close();
        }
        catch (IOException e) {
        }
      }
      System.exit(0);
    }

    // Anything other than -generate, and must have 3+ args
    if (args.length >= 3 && !"-generate".equals(args[0]))
    {
      String actionType = args[0];
      String deltaCode = args[1];
      String filename = args[2];
      String umpleCode = SampleFileWriter.readContent(new File(filename));

      SynchronizationAction action = action(actionType,deltaCode,umpleCode,filename);
      if (action != null)
      {
        action.go();
        returnCommandResult(action.getUmpleCode(), client);
        return;
      }
    }

    UmpleFile umpleFile = null;
    UmpleModel model = null;

    // Valid values include Java, Cpp, PhP, Ruby, Json, Yuml, Xmi, Ecore, Papyrus, TextUml,
    // GvStateDiagram, GvStructureDiagram, GvClassDiagram,GvClassTraitDiagram, GvEntityRelationshipDiagram, Alloy, NuSMV
    if ("-generate".equals(args[0]) && args.length >= 2)
    {
      String filename = args[2];
      GenerateTarget target = new GenerateTarget(args[1], ""); // output path goes here
      // Add any suboptions specified as -s blah -s blah
      for (int argno = 3; argno <= args.length-2; argno += 2)
      {
        if (args[argno].equals("-s"))
        {
          target.addSuboption(args[argno+1]);
        }
      }
      
      umpleFile = new UmpleFile(filename);
      model = new UmpleModel(umpleFile);
      target.setOverrideAll(true);
      model.addGenerate(target);
    }
    else
    {
      // All other commands have filename at second argument
      String filename = args[1];
      umpleFile = new UmpleFile(filename);
      model = new UmpleModel(umpleFile);
    }

    try
    {
      model.run();
    }
    catch(UmpleCompilerException e)
    {
      String errorToReport = model.getLastResult().toJSON();
      if(client == null) {
        System.err.print(errorToReport);
      }
      else {
        try {
          client.sendToClient("ERROR!!"+errorToReport);
        }
          catch (IOException e2) {
        }
      }
    }

    if ("-classList".equals(args[0]))
    {
      for (UmpleClass aClass : model.getUmpleClasses())
      {
        if (answer.length() > 0)
        {
          answer += ",";
        }
        answer += aClass.getName();
      }
    }
    if ("-interfaceList".equals(args[0]))
    {
      for (UmpleInterface aInterface : model.getUmpleInterfaces()) {
        if (answer.length() > 0)
        {
          answer += ",";
        }
        answer += aInterface.getName();
      }
    }
    else if ("-source".equals(args[0]))
    {
      for (Map.Entry<String, String> generatedCode : model.getGeneratedCode().entrySet()) {
        if (answer.length() > 0)
        {
          answer += "\n\n\n\n";
        }
				answer += "//%% NEW FILE " + generatedCode.getKey() + " BEGINS HERE %%\n\n";
				answer += generatedCode.getValue();
      }
    }
    else if ("-generate".equals(args[0]))
    {
      answer = model.getCode();
    }

    returnCommandResult(answer, client);
  }
  
  protected void serverStarted() {
    // Uncomment the following to debug
    // System.err.println("Umple server started on port "+getPort());
    // System.err.println("Working directory "+System.getProperty("user.dir"));
  }
  
  protected void serverStopped() {
    // Uncomment the following to debug
    // System.err.println("Umple server stopped accepting connections "+getPort());
  }
  
  protected void serverClosed() {
    // Uncomment the following to debug  
    //  System.err.println("Umple server closed on port "+getPort());
  }
  
  String concatForLog(String[] theCommand) {
    String result = "";
    for (String aString : theCommand) {
      result = result+"  ["+aString+"]\n"; // not used often so OK to be inefficient
    }
    return result;
  }
    
  private static SynchronizationAction action(String type, String deltaCode, String umpleCode, String filename)
  {
    if ("-addClass".equals(type))
    {
      return new NewAction(deltaCode,umpleCode,filename);
    }
    else if ("-addInterface".equals(type))
    {
      return new NewAction(deltaCode,umpleCode,filename);
    }
    else if ("-removeClass".equals(type))
    {
      return new DeleteAction(deltaCode,umpleCode,filename);
    }
    else if ("-editClass".equals(type))
    {
      return new EditAction(deltaCode,umpleCode,filename);
    }
    else if ("-removeInterface".equals(type))
    {
      return new DeleteAction(deltaCode,umpleCode,filename);
    }
    else if ("-editInterface".equals(type))
    {
      return new EditAction(deltaCode,umpleCode,filename);
    }
    else if ("-addAssociation".equals(type))
    {
      return new NewAssociationAction(deltaCode,umpleCode,filename);
    }
    else if ("-editAssociation".equals(type))
    {
      return new EditAssociationAction(deltaCode,umpleCode,filename);
    }
    else if ("-removeAssociation".equals(type))
    {
      return new DeleteAssociationAction(deltaCode,umpleCode,filename);
    }
    else if ("-addGeneralization".equals(type))
    {
      return new NewGeneralizationAction(deltaCode,umpleCode,filename);
    }
    else if ("-removeGeneralization".equals(type))
    {
      return new DeleteGeneralizationAction(deltaCode,umpleCode,filename);
    }
    else if ("-addPositioning".equals(type))
    {
      return new UpdatePositioningAction(deltaCode,umpleCode,filename);
    }
    else
    {
      return null;
    }
  }
}

use Compiler.ump;
use ArgumentTokenizer.ump;

