/*
 * Copyright (c) 2009, Takeyuki Nagao
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the
 * following conditions are met:
 * 
 *  * Redistributions of source code must retain the above
 *    copyright notice, this list of conditions and the
 *    following disclaimer.
 *  * Redistributions in binary form must reproduce the above
 *    copyright notice, this list of conditions and the
 *    following disclaimer in the documentation and/or other
 *    materials provided with the distribution.
 *    
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 */

package dvi.launcher;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;

import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;

import dvi.util.DviUtils;

public class Launcher {
  private static final Logger LOGGER = Logger.getLogger(Launcher.class.getName());
  
  private static final String LAUNCHER_MAINCLASS_KEY = "dvi.launcher.mainClass";
  private static final String BUILTIN_DEFAULT_MAINCLASS = "dvi.browser.DviBrowser";
  private static final String BUILTIN_DEFAULT_LOGGING_CONF = "conf/logging.properties";
  private static final String LAUNCHER_MAINCLASS_LOGGING_CONF_KEY = "dvi.launcher.mainClass.logging.config.file";
  private static final String LAUNCHER_LOGGING_CONF_KEY = "dvi.launcher.logging.config.file";
  
//  private static final String internalSeparator = "/";
//  private static final String platformSeparator = File.separator;

  
  public static File getUserDir()
  {
    Properties props = System.getProperties();
    String userDir = props.getProperty("user.dir");
    return new File(userDir);
  }
  
  public static void main(String[] args)
  {
    try {
      LOGGER.info("Starting the application launcher.");
      startApplication(args);
    } catch (Exception ex) {
      DviUtils.logStackTrace(LOGGER, Level.SEVERE, ex);
      SwingUtilities.invokeLater(new Runnable() {
        public void run() {
          showErrorMessage("Application exit with an error.  See log file for details.", "Launcher Error");
        }
      });
    }
  }
  
  public static void showErrorMessage(String msg, String title)
  {
    JOptionPane.showMessageDialog(null, msg, title, JOptionPane.ERROR_MESSAGE);
    LOGGER.info("Terminating.");
  }
  
  public static File getWorkDir()
  throws Exception
  {
    // We assume that the Launcher class is invoked either by
    // (1) double clicking dvibrowser.jar
    // or (2) by running java -jar path/to/dvibrowser.jar.
    // In the both cases, the system property java.class.path is 
    // a (possibly relative) path to dvibrowser.jar
    // So we read the value of java.class.path and set the
    // work dir to the directory of the jar file.
    // In the case (1), the result is identical to the directory
    // obtained by calling getUserDir().
    Properties props = System.getProperties();
    String classpath = props.getProperty("java.class.path");
    File jarFile = new File(classpath);
    if (jarFile.isFile() && jarFile.canRead()) {
      return jarFile.getParentFile();
    } else {
      throw new LauncherException
        ("Unable to determine the work dir: jar file does not exist:" + jarFile.getAbsolutePath());
    }
  }
  
  public static void startApplication(String [] args)
  throws Exception
  {
    File workDir = getWorkDir();
    File confDir = new File(workDir, "conf");
    
    Properties props = new Properties();
    String launcherPropertiesFilename = System.getProperty
      ("dvi.launcher.Launcher.config.file", "conf/launcher.properties");
    
    File launcherPropertiesFile = new File(workDir, launcherPropertiesFilename);
    if (launcherPropertiesFile.isFile() && launcherPropertiesFile.canRead()) {
      try {
        props.load(new FileInputStream(launcherPropertiesFile));
      } catch (FileNotFoundException e) {
        LOGGER.warning(e.toString());
      } catch (IOException e) {
        LOGGER.warning(e.toString());
      }
    }
    props.putAll(System.getProperties());
    
    String launcherLoggingConfFilename = props.getProperty(LAUNCHER_LOGGING_CONF_KEY, null);
    if (launcherLoggingConfFilename != null) {
      File f = new File(launcherLoggingConfFilename);
      if (f.isFile() && f.canRead()) {
        try {
          LOGGER.info("Loading logging configuration file: " + f);
          LogManager.getLogManager().readConfiguration(new FileInputStream(f));
          LOGGER.info("Loaded logging configuration file: " + f);
        } catch (FileNotFoundException e) {
          LOGGER.warning(e.toString());
        } catch (IOException e) {
          LOGGER.warning(e.toString());
        }
      }
    }

    File classpathsDir = new File(confDir, "classpaths.d");
        
    String mainClass = props.getProperty(LAUNCHER_MAINCLASS_KEY, BUILTIN_DEFAULT_MAINCLASS);

    String javaCommand = "java";
    if (DviUtils.isWindows()) {
      javaCommand = "javaw";
    }
    
    String classpath = buildClasspathFromDirectory(classpathsDir);
    LOGGER.config("CLASSPATH=" + classpath);
    LOGGER.config("WORKDIR=" + workDir);
    LOGGER.config("CONFDIR=" + confDir);
    
    String loggingConf = props.getProperty(LAUNCHER_MAINCLASS_LOGGING_CONF_KEY, BUILTIN_DEFAULT_LOGGING_CONF);
    
    ArrayList<String> cmds = new ArrayList<String>();
    cmds.add(javaCommand);
    cmds.add("-cp");
    cmds.add(classpath);
    cmds.add("-Djava.util.logging.config.file=" + loggingConf);
    cmds.add("-Ddvi.ctx.DefaultDviContext.usePackageShareDir=true");
    cmds.add(mainClass);

    LOGGER.config("Launching main class: " + mainClass);
    Process proc = DviUtils.unsafeRunCommandByShell(cmds.toArray(new String[cmds.size()]), null, workDir);
    DviUtils.dumpStreamAsync("stdout", proc.getInputStream(), System.out);
    DviUtils.dumpStreamAsync("stderr", proc.getErrorStream(), System.err);
    int exitCode = proc.waitFor();
    if (exitCode != 0) {
      throw new LauncherException
        ("Application exit with an error: " + DviUtils.join(" ", cmds.toArray(new String[cmds.size()])));
    }
  }

  private static String buildClasspathFromDirectory(File classpathsDir)
  {
    ArrayList<String> classpathList = new ArrayList<String>();
    File[] list = classpathsDir.listFiles();
    if (list == null) list = new File[0];
    Arrays.sort(list);
    for (File file : list) {
      if (file.isFile() && file.canRead()) {
        try {
          String [] lines = DviUtils.removeComments(DviUtils.readLinesFromFile(file), "#");
          for (String line : lines) {
            classpathList.add(mapInternalClasspathToPlatformClasspath(line));
          }
        } catch (IOException e) {
          LOGGER.warning(e.toString());
        }
      }
    }
    return DviUtils.join(getClasspathSeparator(), classpathList.toArray(new String[classpathList.size()]));
  }

  private static String mapInternalClasspathToPlatformClasspath(String str) {
    // It seems that POSIX style classpath are accepted by Sun Java VM in Windows.
    // So we don't actually map the string to platform specific representation.
    return str;
  }

  private static String getClasspathSeparator() {
    String classpathSep = ":";
    if (DviUtils.isWindows()) {
      classpathSep = ";";
    }
    return classpathSep;
  }
}
