package brn.sim;

import jargs.gnu.CmdLineParser;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Random;

import org.python.core.PyException;
import org.python.util.PythonInterpreter;

import brn.distsim.client.data.Group;
import brn.distsim.client.data.GroupConfigurationFiles;
import brn.distsim.client.data.Package;
import brn.distsim.client.data.Simulation;
import brn.distsim.client.data.Study;
import brn.distsim.client.data.StudyPackages;
import brn.distsim.wrapper.RemoteSimulation;


import jist.swans.misc.Util;


public class DistSimStarter {

  private Connection definitions;

  private String antBuildFile = "build-distsim.xml";

  private String logPropFile;

  public DistSimStarter(String url, String user, String password)
      throws ClassNotFoundException, SQLException, IOException {
    Class.forName("com.mysql.jdbc.Driver");
    definitions = DriverManager.getConnection(url, user, password);
  }

  /**
   * @return the logPropFile
   */
  public String getLogPropFile() {
    return logPropFile;
  }

  /**
   * @param logPropFile the logPropFile to set
   */
  public void setLogPropFile(String logPropFile) {
    this.logPropFile = logPropFile;
  }

  /**
   * @return the antBuildFile
   */
  public String getAntBuildFile() {
    return antBuildFile;
  }

  /**
   * @param antBuildFile the antBuildFile to set
   */
  public void setAntBuildFile(String antBuildFile) {
    this.antBuildFile = antBuildFile;
  }

  protected void uploadPackage(String name, String version,
      String remoteUploadUrl, String remoteDownloadUrl, String localFile,
      String architecture) throws SQLException, IOException {
    Package p = new Package(definitions, name, version, architecture,
        remoteUploadUrl);
    if (null != localFile && localFile.length() != 0)
      p.upload(localFile);
    p = new Package(definitions, name, version, architecture, remoteDownloadUrl);
    p.performDbInsert();
  }

  protected Study createStudy(boolean jython, boolean realtime, String name, 
      String version, String remoteUploadUrl, String remoteDownloadUrl, 
      String localFile, String architecture) throws SQLException, IOException {
    uploadPackage(name, version, remoteUploadUrl, remoteDownloadUrl, localFile,
        architecture);

    String target = "run";
    if (jython && !realtime)
      target = "run-jpy";
    else if (!jython && realtime)
      target = "run-rt";
    else if (jython && realtime)
      target = "run-rt-jpy";
    
    Study study = new Study(definitions, name, version, "ant -f build-distsim.xml "
        + target + " -Drun.config=jist.properties -Drun.class="
        + name + " -Drun.args=--distsim");
    study.performDbInsert(0);

    StudyPackages pack = study.getPackages();
    pack.put(pack.new Package(name, version, "package"));
    pack.commit();
    return study;
  }

  protected void addGroup(Study study, int number, AbstractParams params,
      String logProperties) throws SQLException, IOException {
    String name = "group-" + number;

    Group group = new Group(definitions, name, study.getId());
    study.add(group);
    study.commit();

    GroupConfigurationFiles files = group.getConfigurationFiles();
    String remotePath = "config-" + number + ".xml";
    String config = Util.encodeObject(params);
    files.put(files.new File(remotePath, config, true));
    files.put(files.new File("jist.properties", logProperties));
    files.put(files.new File("build-distsim.xml", antBuildFile));
    files.commit();

//    GroupResultsFiles resFiles = group.getResultsFiles();
//    resFiles.put(resFiles.new File("stdout.txt"));
//    resFiles.commit();

    Simulation simulation = new Simulation(definitions, group.getId());
    group.add(simulation);

    Properties props = new Properties();
    props.put("driver.config", remotePath);
    simulation.put(props);

    group.commit();
  }

  public static Object getJythonObject(String interfaceName,
      String pathToJythonModule){
    Object javaInt = null;

    try {
      PythonInterpreter interpreter = new PythonInterpreter();
      interpreter.execfile(pathToJythonModule);
      String tempName = pathToJythonModule.substring(
          pathToJythonModule.lastIndexOf('/') + 1);
      tempName = tempName.substring(0, tempName.indexOf("."));
      String instanceName = tempName.toLowerCase();
      String javaClassName = tempName.substring(0, 1).toUpperCase()
          + tempName.substring(1);
      String objectDef = "=" + javaClassName + "()";
      interpreter.exec(instanceName + objectDef);

      Class JavaInterface = Class.forName(interfaceName);
      javaInt = interpreter.get(instanceName).__tojava__(JavaInterface);
    } catch (ClassNotFoundException e) {
      e.printStackTrace(); // Add logging here
      throw new RuntimeException(e);
    } catch (PyException e) {
      e.printStackTrace();
      System.err.println("Not able to get the Jython class reference");
      System.err.println("Note: The Jython class has to be the name of the file "
          +"it is defined in");
      System.exit(1);
    }

    return javaInt;
  }

  protected void startWrapper(String className, String version,
      String remoteUploadUrl, String remoteDownloadUrl, String localFile,
      String architecture, boolean jython, boolean realtime) throws Throwable {
    File file = new File(logPropFile);
    if (!file.exists())
      throw new FileNotFoundException(logPropFile);
    file = new File(antBuildFile);
    if (!file.exists())
      throw new FileNotFoundException(antBuildFile);
    
    List lstSimulations = null;
    boolean randomizeSimulationSuite = true;
    if (!jython) {
      Class testClass = Class.forName(className);

      SimulationSuite driver = (SimulationSuite) testClass.newInstance();
      lstSimulations = driver.getSimulationSuite(version);
      randomizeSimulationSuite = driver.isRandomizeSimulationSuite();
    } else {
      SimulationSuite driver = (SimulationSuite) getJythonObject(
          SimulationSuite.class.getName(), className);
      lstSimulations = driver.getSimulationSuite(version);
      randomizeSimulationSuite = driver.isRandomizeSimulationSuite();
    }
    if (null == lstSimulations || lstSimulations.size() == 0) {
      throw new Exception(
          "Class "
              + getClass().getSimpleName()
              + " does "
              + "not define a simulation suite. Please overwrite getSimulationSuite()");
    }

    Random random = new Random(1);
    Study study = createStudy(jython, realtime, className, version, 
        remoteUploadUrl, remoteDownloadUrl, localFile, architecture);
    int i = 1;
    while (!lstSimulations.isEmpty()) {
      int next = 0;
      if (randomizeSimulationSuite)
        next = random.nextInt(lstSimulations.size());
      AbstractParams params = (AbstractParams) lstSimulations.remove(next);

      addGroup(study, i++, params, logPropFile);
    }
  }

  private void stopWrappers() {
    try {
      Statement stmt = definitions.createStatement();
      stmt.execute("SELECT id, name FROM hosts");
      ResultSet rs = stmt.getResultSet();
      List wrapper = new ArrayList();

      while (rs.next()) {
        int id = rs.getInt(1);
        String hostName = rs.getString(2);

        if (1 == id && hostName.equals("test"))
          continue;

        try {
          Registry registry = LocateRegistry.getRegistry(hostName, 1099);
          RemoteSimulation sim = (RemoteSimulation) registry
              .lookup("host" + id);
          wrapper.add(sim);
        } catch (RemoteException e) {
          // e.printStackTrace();
          System.err.println("Unable to locate wrapper " + id + " at "
              + hostName + " (" + e.getMessage() + ")");
        } catch (NotBoundException e) {
          System.err.println("Unable to locate wrapper " + id + " at "
              + hostName + " (" + e.getMessage() + ")");
        }
      }

      for (int i = 0; i < wrapper.size(); i++) {
        RemoteSimulation sim = (RemoteSimulation) wrapper.get(i);
        try {
          sim.stop();
        } catch (RemoteException e) {
          // e.printStackTrace();
          System.err.println("Unable to stop wrapper " + sim + " ("
              + e.getMessage() + ")");
        }
      }

    } catch (SQLException e) {
      e.printStackTrace();
      System.err.println("Unable to read wrappers from db");
    }
  }

  /**
   * Parsed JiST command-line options.
   */
  public static class CommandLineOptions {
    /** database url */
    String dbUrl;

    /** database user */
    String dbUser;

    /** database password */
    String dbPassword;

    /** simulation class, must inherit from {@link SimulationSuite} */
    String simClass;

    /** simulation version */
    String simVersion;

    /**
     * optinal location of the simulation package zip file on the local file
     * system
     */
    String simPackageFile;

    /** location to store the simulation package zip for the distsim wrapper */
    String simRemoteUploadUrl;

    /** location frow where the simulation package zip could be retrieved */
    String simRemoteDownloadUrl;

    /** optinal properties file for log4j */
    String logPropFile;

    boolean stop;

    String architecture;

    boolean jython = false;
    /** location of the build-distsim.xml ant file */
    String antfile;
    /** whether to use realtime scheduler */
    boolean realtime = false;
  }

  /**
   * Parse command-line arguments.
   *
   * @param args command-line arguments
   * @return command-line options structure
   * @throws CmdLineParser.OptionException illegal command line option
   * @throws java.net.UnknownHostException unable to parse remote host
   *           address:port
   */
  protected static CommandLineOptions parseCommandLineOptions(String[] args)
      throws CmdLineParser.OptionException, java.net.UnknownHostException {
    // setup
    CmdLineParser parser = new CmdLineParser();
    CmdLineParser.Option opt_dburl = parser.addStringOption('l', "url");
    CmdLineParser.Option opt_dbuser = parser.addStringOption('u', "user");
    CmdLineParser.Option opt_dbpassword = parser.addStringOption('p',
        "password");
    CmdLineParser.Option opt_driver = parser.addStringOption('d', "driver");
    CmdLineParser.Option opt_version = parser.addStringOption('v', "version");
    CmdLineParser.Option opt_local = parser.addStringOption('l', "local");
    CmdLineParser.Option opt_remoteUpload = parser.addStringOption('p',
        "remoteUpload");
    CmdLineParser.Option opt_remoteDownload = parser.addStringOption('r',
        "remoteDownload");
    CmdLineParser.Option opt_log = parser.addStringOption('j', "log");
    CmdLineParser.Option opt_antfile = parser.addStringOption('f', "antfile");
    CmdLineParser.Option opt_stop = parser.addBooleanOption('s', "stop");
    CmdLineParser.Option opt_arch = parser.addStringOption('a', "architecture");
    CmdLineParser.Option opt_jython = parser.addBooleanOption('y', "jython");
    CmdLineParser.Option opt_realtime = parser.addBooleanOption('t', "realtime");
    
    // CmdLineParser.Option opt_help = parser.addBooleanOption('h', "help");

    // parse
    parser.parse(args);
    CommandLineOptions options = new CommandLineOptions();
    if (parser.getOptionValue(opt_dburl) != null)
    {
      options.dbUrl = (String) parser.getOptionValue(opt_dburl);
    }
    if (parser.getOptionValue(opt_dbuser) != null)
    {
      options.dbUser = (String) parser.getOptionValue(opt_dbuser);
    }
    if (parser.getOptionValue(opt_dbpassword) != null)
    {
      options.dbPassword = (String) parser.getOptionValue(opt_dbpassword);
    }
    if (parser.getOptionValue(opt_driver) != null)
    {
      options.simClass = (String) parser.getOptionValue(opt_driver);
    }
    if (parser.getOptionValue(opt_version) != null)
    {
      options.simVersion = (String) parser.getOptionValue(opt_version);
    }
    if (parser.getOptionValue(opt_local) != null)
    {
      options.simPackageFile = (String) parser.getOptionValue(opt_local);
    }
    if (parser.getOptionValue(opt_remoteUpload) != null)
    {
      options.simRemoteUploadUrl = (String) parser
          .getOptionValue(opt_remoteUpload);
    }
    if (parser.getOptionValue(opt_remoteDownload) != null)
    {
      options.simRemoteDownloadUrl = (String) parser
          .getOptionValue(opt_remoteDownload);
    }
    if (parser.getOptionValue(opt_log) != null)
    {
      options.logPropFile = (String) parser.getOptionValue(opt_log);
    }
    if (parser.getOptionValue(opt_arch) != null)
    {
      options.architecture = (String) parser.getOptionValue(opt_arch);
    }
    if (parser.getOptionValue(opt_stop) != null)
    {
      options.stop = true;
    }
    if(parser.getOptionValue(opt_jython)!=null)
    {
      options.jython = true;
    }
    if(parser.getOptionValue(opt_realtime) != null)
    {
      options.realtime = true;
    }
    if (parser.getOptionValue(opt_antfile) != null)
    {
      options.antfile = (String) parser.getOptionValue(opt_antfile);
    }

    return options;
  }

  /**
   * @param args
   */
  public static void main(String[] args) throws Throwable {
    CommandLineOptions opts = parseCommandLineOptions(args);

    if (null == opts.dbUrl || 0 == opts.dbUrl.length())
      throw new Exception("database url not specified");
    if (null == opts.dbUser || 0 == opts.dbUser.length())
      throw new Exception("database user not specified");

    DistSimStarter starter = new DistSimStarter(opts.dbUrl, opts.dbUser,
        opts.dbPassword);
    if (opts.stop) {
      starter.stopWrappers();
    } else {
      if (null == opts.simClass || 0 == opts.simClass.length())
        throw new Exception("simulation driver not specified");
      if (null == opts.simVersion || 0 == opts.simVersion.length())
        throw new Exception("simulation version not specified");

      if (null == opts.simRemoteDownloadUrl)
        opts.simRemoteDownloadUrl = opts.simRemoteUploadUrl;

      if (null == opts.architecture)
        opts.architecture = "all";

      if (null != opts.logPropFile)
        starter.setLogPropFile(opts.logPropFile);
      
      if (null != opts.antfile)
        starter.setAntBuildFile(opts.antfile);

      // remove back-slash
      if (opts.jython)
        opts.simClass = opts.simClass.replace('\\', '/');

      starter.startWrapper(opts.simClass, opts.simVersion,
          opts.simRemoteUploadUrl, opts.simRemoteDownloadUrl, opts.simPackageFile,
          opts.architecture, opts.jython, opts.realtime);
    }
  }

}
