import java.util.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import javax.swing.*;
import javax.swing.filechooser.*;
import javax.swing.border.*;
import javax.swing.SwingUtilities;
import java.net.*;
import java.util.Stack;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public class SmartInstallerTask extends SwingWorker<Boolean, String> {

  int BUFFER = 1024;

  int       architecture         = 0;
  String    installDirectoryName = null;
  JTextArea logArea              = null;
  boolean[] selectedPrograms     = null;

  // program chooser buttons
  String    programChoosers[] = {"R", "R Color Brewer Package", "R HMisc Package", "Python 2.6", "S-MART"};

  // Web addresses for the tools
  String packageAddresses[][] = {
    {"http://cran.cict.fr/bin/windows/base/R-2.11.0-win32.exe", "http://cran.cict.fr/bin/windows64/base/R-2.11.0-win64.exe"},
    {"", ""},
    {"", ""},
    {"http://www.python.org/ftp/python/2.6.5/python-2.6.5.msi", "http://www.python.org/ftp/python/2.6.5/python-2.6.5.amd64.msi"},
    {"http://urgi.versailles.inra.fr/content/download/1929/17848/file/s-mart-1.0.15.zip", "http://urgi.versailles.inra.fr/content/download/1929/17848/file/s-mart-1.0.15.zip"}
  };

  // Packages to install
  String rPackages[] = {"RColorBrewer", "Hmisc"};

  // Script lines
  String scriptLines[][] = {
    {"\"<INSTALLDIR>\\R-2.11.0-win32.exe\"", "\"<INSTALLDIR>\\R-2.11.0-win64.exe\""},
    {"\"<RFILE>\" CMD BATCH \"<INSTALLDIR>\\installRColorBrewer.R\"", "\"<RFILE>\" CMD BATCH \"<INSTALLDIR>\\installRColorBrewer.R\""},
    {"\"<RFILE>\" CMD BATCH \"<INSTALLDIR>\\installHmisc.R\"", "\"<RFILE>\" CMD BATCH \"<INSTALLDIR>\\installHmisc.R\""},
    {"msiexec /i \"<INSTALLDIR>\\python-2.6.5.msi\"", "msiexec /i \"<INSTALLDIR>\\python-2.6.5.amd64.msi\""},
    {"", ""}
  };

  // Files to uncompress
  String compressedFiles[][] = {
    {"", ""},
    {"", ""},
    {"", ""},
    {"", ""},
    {"<INSTALLDIR>\\s-mart-1.0.15.zip", "<INSTALLDIR>\\s-mart-1.0.15.zip"}
  };


  public SmartInstallerTask(JTextArea ta, boolean[] b, String s, int a) {
    logArea              = ta;
    selectedPrograms     = b;
    installDirectoryName = s;
    architecture         = a;
  }


  @Override
  public Boolean doInBackground() {
    boolean installOk;
    publish("Starting install\n");
    writeFiles();
    for (int i = 0; i < selectedPrograms.length; i++) {
      if (selectedPrograms[i]) {
        if (! install(i)) {
          return Boolean.FALSE;
        }
      }
    }
    removeFiles();
    setEnvironmentVariables();
    publish("Ending install\n");
    return Boolean.TRUE;
  }


  @Override
  protected void process(List<String> chunks) {
    for (String chunk: chunks) {
      logArea.append(chunk);
    }
  }


  private boolean launch(String command) {
    return realLaunch(new ProcessBuilder(command), command);
  }

  private boolean launch(String[] command) {
    return realLaunch(new ProcessBuilder(command), Arrays.toString(command));
  }

  private boolean realLaunch(ProcessBuilder pb, String command) {
    BufferedReader outputReader;
    pb                          = pb.redirectErrorStream(true);
    Process        process      = null;
    publish("      Starting command '" + command + "'\n");
    try {
      process = pb.start();
      BufferedInputStream outputStream = new BufferedInputStream(process.getInputStream());
      InputStream is        = process.getInputStream();
      InputStreamReader isr = new InputStreamReader(is);
      outputReader          = new BufferedReader(isr);
    }
    catch (Exception exception) {
      publish("      !Process cannot be started (command is '" + command + "')!\n");
      exception.printStackTrace();
      return false;
    }
    if (outputReader == null) {
      publish("      !Problem in the output of the command!\n");
      return false;
    }
    else {
      publish("      Output is:\n");
      try {
        publish("        ---\n");
        String line;
        while ((line = outputReader.readLine()) != null) {
          publish("        " + line + "\r\n");
        }
        publish("        ---\n");
      }
      catch (IOException e) {
        publish("      !Cannot get the output of the command!\n");
        return false;
      }
    }
    int exitValue = process.exitValue();
    if (exitValue != 0) {
      publish("      !Problem during the execution of the command '" + command + "'!\n");
      return false;
    }
    publish("      Ending command '" + command + "'\n");
    return true;
  }


  private File lookForFile(String fileName, String[] putativePlaces) {
    publish("      Looking for file " + fileName + "\n");
    for (String place: putativePlaces) {
      File file = new File(place, fileName);
      publish("      Look at " + file.getAbsolutePath() + "\n");
      if (file.exists()) {
        publish("      Found it in expected place " + file.getAbsolutePath() + "\n");
        return file;
      }
    }
    Stack<File> files = new Stack<File>();
    files.push(new File("\\"));
    while (! files.empty()) {
      File file = files.pop();
      for (File childFile: file.listFiles()) {
        if (childFile.isDirectory()) {
          files.push(childFile);
        }
        else {
          if (fileName.compareToIgnoreCase(childFile.getName()) == 0) {
            publish("      Found it in unexpected place " + childFile.getAbsolutePath() + "\n");
            return childFile;
          }
        }
      }
    }
    publish("      !Cannot file file '" + fileName + "'!\n");
    return null;
  }


  private boolean writeFile(String fileName, String content) {
    try {
      FileWriter     fw = new FileWriter(fileName);
      BufferedWriter bw = new BufferedWriter(fw);
      bw.write(content);
      bw.close();
      fw.close();
    }
    catch (Exception e) {
      publish("      !Cannot write file '" + fileName + "'!\n");
      return false;
    }
    return true;
  }


  private boolean removeFile(String fileName) {
    File file = new File(fileName);
    if (file.exists()) {
      if (! file.delete()) {
        publish("      !Cannot delete file '" + file.getAbsolutePath() + "'!\n");
        return false;
      }
    }
    return true;
  }


  private boolean writeFiles() {
    for (String rPackage: rPackages) {
      String fileName = installDirectoryName + File.separator + "install" + rPackage + ".R";
      String content  = "install.packages(\"" + rPackage + "\", repos = \"http://cran.cict.fr\", dependencies = TRUE)\n";
      if (! writeFile(fileName, content)) {
        publish("    !Cannot write file for R package '" + rPackage + "'!\n");
        return false;
      }
    }
    return true;
  }

  private boolean removeFiles() {
    for (String rPackage: rPackages) {
      File file = new File(installDirectoryName + File.separator + "install" + rPackage + ".R");
      if (! file.delete()) {
        publish("!Cannot delete R install file for " + rPackage + "!\n");
        return false;
      }
    }
    File file = new File(installDirectoryName + File.separator + "createUser.sql");
    if (! file.delete()) {
      publish("!Cannot delete mySQL configuration file!\n");
      return false;
    }
    return true;
  }

  private boolean install(int element) {
    publish("  Starting install of " + programChoosers[element] + "\n");
    downloadPackage(element);
    executeInstall(element);
    uncompressPackage(element);
    removePackage(element);
    postProcess(element);
    publish("  Ending install of " + programChoosers[element] + "\n");
    return true;
  }


  private String getLocalName(String remoteName) {
    String localName = installDirectoryName + File.separator + (new File(remoteName)).getName();
    int    position  = localName.indexOf("?");
    if (position >= 0) {
      localName = localName.substring(0, position);
    }
    return localName;
  }


  private boolean downloadPackage(int element) {
    String fileName  = packageAddresses[element][architecture];
    if (! "".equals(fileName)) {
      publish("    Starting download of " + programChoosers[element] + "\n");
      try {
        BufferedInputStream  bis = new BufferedInputStream(new URL(fileName).openStream());
        FileOutputStream     fos = new FileOutputStream(getLocalName(fileName));
        BufferedOutputStream bos = new BufferedOutputStream(fos, BUFFER);
        byte[] data = new byte[BUFFER];
        int x       = 0;
        while((x = bis.read(data, 0, BUFFER)) >= 0) {
          bos.write(data, 0, x);
        }
        bos.close();
        fos.close();
        bis.close();
      }
      catch (IOException e) {
        publish("    !Cannot download file '" + fileName + "'!\n");
        return false;
      }
      publish("    Ending download of " + programChoosers[element] + "\n");
    }
    return true;
  }


  private String replaceSubstring(String line) {
    if (line.contains("<INSTALLDIR>")) {
      String protectedDirectory = installDirectoryName.replaceAll("\\\\", "\\\\\\\\");
      line = line.replaceAll("<INSTALLDIR>", protectedDirectory);
    }
    if (line.contains("<RFILE>")) {
      String   userName = System.getenv().get("USERNAME");
      String[] possibleRDirectories = {"C:\\Program Files\\R-2.11.0", "C:\\Documents and Settings\\" + userName + "\\Mes documents\\R\\R-2.11.0\\bin", "C:\\Documents and Settings\\" + userName + "\\My documents\\R\\R-2.11.0\\bin"};
      String rDirectory = lookForFile("'.exe", possibleRDirectories).getAbsolutePath();
      rDirectory = rDirectory.replaceAll("\\\\", "\\\\\\\\");
      line = line.replaceAll("<RFILE>", rDirectory);
    }
    return line;
  }


  private boolean executeInstall(int element) {
    String commands           = scriptLines[element][architecture];
    if (! "".equals(commands)) {
      for (String command: commands.split(";")) {
        command = replaceSubstring(command);
        publish("    Starting command '" + command + "'\n");
        Process process = null;
        try {
          process = Runtime.getRuntime().exec(command);
        }
        catch (IOException e) {
          publish("    !Cannot execute command '" + command + "'!\n");
          return false;
        }
        try {
          process.waitFor();
        }
        catch (InterruptedException e) {
          publish("    !Cannot wait for the end of the command '" + command + "'!\n");
          return false;
        }
        int exitValue = process.exitValue();
        if (exitValue != 0) {
          publish("    !Problem during the execution of the command '" + command + "'!\n");
          return false;
        }
        publish("    Ending command '" + command + "'\n");
      }
    }
    return true;
  }


  private boolean uncompressPackage(int element) {
    String file = compressedFiles[element][architecture];
    if (! "".equals(file)) {
      file = replaceSubstring(file);
      publish("    Starting uncompressing file '" + file + "'\n");
      try {
        FileInputStream     fis = new FileInputStream(file);
        BufferedInputStream bis = new BufferedInputStream(fis);
        ZipInputStream      zis = new ZipInputStream(bis);
        ZipEntry            entry;
        while ((entry = zis.getNextEntry()) != null) {
          if (! entry.isDirectory()) {
            File newFile = new File(installDirectoryName + File.separator + entry.getName());
            // create parent directories
            File upDirectory = newFile.getParentFile();
            while (upDirectory != null){
              if (! upDirectory.exists()) {
                upDirectory.mkdir();
                publish("      Creating directory '" + upDirectory.getAbsolutePath() + "'\n");
              }
              upDirectory = upDirectory.getParentFile();
            }
            // write the files to the disk
            publish("      Extracting '" + entry.getName() + "' to '" + newFile.getAbsolutePath() + "'\n");
            int  count;
            byte data[] = new byte[BUFFER];
            FileOutputStream     fos = new FileOutputStream(newFile);
            BufferedOutputStream bos = new BufferedOutputStream(fos, BUFFER);
            while ((count = zis.read(data, 0, BUFFER)) != -1){
              bos.write(data, 0, count);
            }
            bos.flush();
            bos.close();
            fos.close();
          }
        }
        zis.close();
        bis.close();
        fis.close();
      }
      catch(FileNotFoundException e) {
        publish("    !Cannot find file '" + file + "'!\n");
        return false;
      }
      catch(Exception e){
        publish("    !Cannot uncompress file '" + file + "'!\n");
        return false;
      }
      publish("    Ending uncompressing file '" + file + "'\n");
    }
    return true;
  }


  private boolean removePackage(int element) {
    String packageName = packageAddresses[element][architecture];
    if ("".equals(packageName)) {
      return true;
    }
    String fileName = getLocalName(packageAddresses[element][architecture]);
    return removeFile(fileName);
  }


  private boolean postProcess(int element) {
    switch (element) {
      case 4:
        // Move S-MART files to parent directory
        File installDirectory = new File(installDirectoryName + File.separator + "S-Mart");
        for (File file: installDirectory.listFiles()) {
          File destinationFile = new File(file.getParentFile().getParentFile(), file.getName());
          if (! file.renameTo(destinationFile)) {
            publish("     !Cannot move '" + file.getAbsolutePath() + "' to '" + destinationFile.getAbsolutePath() + "'!\n");
          }
        }
        if (! installDirectory.delete()) {
          publish("     !Cannot remove installation S-MART directory '" + installDirectory.getAbsolutePath() + "'!\n");
        }
    }
    return true;
  }


  private boolean setEnvironmentVariables() {
    String[] command = {"REG", "ADD", "HKCU\\Environment", "/v", "PYTHONPATH", "/t", "REG_SZ", "/d", "\"" + installDirectoryName + "\\Python\"", "/f"};
    return launch(command);
  }
}

