/*
 * jNPad v0.3 - jNPad's an Simple Text Editor written in Java
 *
 * Copyright (C) 2014-2017  rgs
 *
 * Require JDK 1.6 (or later)
 *
 * 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *
 *
 * Info, Questions, Suggestions & Bugs Report to rgsevero@gmail.com
 */

package jnpad;

import static jnpad.util.Utilities.DIR_SEPARATOR;

import java.awt.Component;
import java.awt.DefaultKeyboardFocusManager;
import java.awt.GraphicsEnvironment;
import java.awt.KeyboardFocusManager;
import java.awt.Window;
import java.awt.event.KeyEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.LogManager;

import javax.swing.LayoutFocusTraversalPolicy;
import javax.swing.SwingUtilities;

import jnpad.config.Accelerators;
import jnpad.config.Config;
import jnpad.config.Mode;
import jnpad.text.JNPadTextArea;
import jnpad.ui.plaf.LAFUtils;
import jnpad.util.Platform;
import jnpad.util.Utilities;
import jnpad.util.Version;

/**
 * The Class JNPad.
 *
 * @version 0.3
 * @since   jNPad v0.1
 */
@SuppressWarnings("nls")
public final class JNPad {
  /** jNPad home directory. */
  public static final String      JNPAD_HOME;

  /** The Constant PROPS_DIR. */
  public static final String      PROPS_DIR;

  /** The Constant LOGGERING_FILE_PATH. */
  public static final String      LOGGERING_FILE_PATH;

  /** The Constant TITLE. */
  public static final String      TITLE                = "jNPad";
  
  /** The Constant EMAIL. */
  public static final String      EMAIL                = "rgsevero@gmail.com";

  private static Mode             mode;
  private static List<JNPadInput> inputFiles;
  private static String           sessionPath;
  
  static {
    String bootFullName = JNPad.class.getResource("JNPad.class").toString();
    File jNPadDir = null;
    try {
      if (! bootFullName.startsWith("jar:")) {
        // JNPad.class is not in a jar-file. Find a lib directory somewhere
        // above us to use
        File startingDir = (new File(new URI(bootFullName)).getParentFile());
        while ((startingDir != null) &&
            !(new File(startingDir.getParentFile(), "classes").isDirectory())) {
          startingDir = startingDir.getParentFile();
        }
        if (startingDir != null) {
          jNPadDir = startingDir.getParentFile();
        }
      }
      else {
        // The class is in a jar file, '!' separates the jar file name
        // from the class name. Cut off the class name and the "jar:" prefix.
        int classIndex = bootFullName.indexOf("!");
        String bootName = bootFullName.substring(4, classIndex);
        File finalFile = new File(new URI(bootName));
        jNPadDir = finalFile.getParentFile();
      }
    } 
    catch (URISyntaxException use) {
      //ignore
    }
    
    JNPAD_HOME = (jNPadDir != null) ? jNPadDir.getAbsolutePath() : System.getProperty("user.dir");
    PROPS_DIR = JNPAD_HOME + DIR_SEPARATOR + "props";
    LOGGERING_FILE_PATH = PROPS_DIR + DIR_SEPARATOR + "log.properties";
    
    //System.out.println("JNPAD_HOME: " + JNPAD_HOME);
    //System.out.println("PROPS_DIR: " + PROPS_DIR);
    //System.out.println("LOGGERING_FILE_PATH: " + LOGGERING_FILE_PATH);
  }

  /** no instances */
  private JNPad() {
    super();
  }

  /**
   * Prints the usage.
   */
  private static void printUsage() {
    System.out.println("Usage:");
    System.out.println("  jnpad [options] [[-e] <files-to-edit>] [-v <files-to-view>] [-s <session-file>]");
    System.out.println();
    System.out.println("jNPad v" + Version.getVersion() + " - Simple text editor");
    System.out.println();
    System.out.println("Where:");
    System.out.println("  -e, -edit, --edit <files-to-edit>       Files to edit");
    System.out.println("  -v, -view, --view <files-to-view>       Files to view (read only)");
    System.out.println("  -s, -session, --session <session-file>  Load a specific session");
    System.out.println();
    System.out.println("Options:");
    System.out.println("  -h, -help, --help, -?, /?               Give this help list");
    System.out.println("  -b, -basic, --basic                     Run in basic mode");
    System.out.println("  -d, -default, --default                 Run in default mode");
    System.out.println("  -m, -minimalist, --minimalist           Run in minimalist mode");
    System.out.println("  -version, --version                     Displays the version of the program");
    System.out.println();
    Utilities.exitSuccess();
  }

  /**
   * Prints the version.
   */
  private static void printVersion() {
    System.out.println(TITLE);
    System.out.println("version " + Version.getVersion()); 
    System.out.println(EMAIL);
    Utilities.exitSuccess();
  }

  /**
   * The main method.
   *
   * @param args the arguments
   */
  public static void main(String[] args) {
    try {
      checkJVMVersion();
      checkHeadless();
      
      configureLogging();
      
      parseCommandLine(args);
      
      if(mode != null) {
        Config.setMode(mode);
      }

      if(Accelerators.isUsingCompositeShortcuts()) {
        // Though the cause is not known, this must precede
        // UIManager.setLookAndFeel(), so that menu bar
        // interaction by ALT key interacts with swing.JMenuBar
        // (which uses L&F) instead of awt.MenuBar which we
        // don't use (and doesn't use L&F).
        // The difference of the behavior was seen on Sun JRE
        // 6u16 on Windows XP and Windows L&F.
        KeyboardFocusManager.setCurrentKeyboardFocusManager(new JNPadFocusManager());
      }
      
      try {
        LAFUtils.setLookAndFeel(Config.JNPAD_LAF.getValue());
      }
      catch (Exception ex) {
        System.err.println("Look and feel error: " + ex);
      }

      // Abrir la GUI. Se asegura que todas las instancias Swing/AWT y los
      // accesos se realizan en el thread de despacho de eventos (EDT).
      SwingUtilities.invokeLater(new Runnable() {
        public void run() {
          new JNPadFrame(inputFiles, sessionPath);
        }
      });
    }
    catch (Exception e) {
      System.err.println("***************************************************"); 
      System.err.println("    jNPad - ERROR                                \n"); 
      e.printStackTrace();
      System.err.println("***************************************************"); 
    }
  }

  /**
   * Checks if is key.
   *
   * @param s the string
   * @return true, if is key
   */
  private static boolean isKey(String s) {
    return
        s.equals("-e") || s.equals("-edit") || s.equalsIgnoreCase("--edit") ||
        s.equals("-v") || s.equals("-view") ||s.equalsIgnoreCase("--view") ||
        s.equals("-s") || s.equals("-session") ||s.equalsIgnoreCase("--session") ||
        s.equals("-b") || s.equals("-basic") ||s.equalsIgnoreCase("--basic") ||
        s.equals("-d") || s.equals("-default") ||s.equalsIgnoreCase("--default") ||
        s.equals("-m") || s.equals("-minimal") ||s.equalsIgnoreCase("--minimal") ||
        s.equals("-version") || s.equalsIgnoreCase("--version") ||
        s.equals("-h") || s.equals("-help") || s.equalsIgnoreCase("--help") || 
        s.equals("-?") || s.equals("/?");
  }

  /**
   * Parses the command line.
   *
   * @param args the args
   */
  private static void parseCommandLine(String[] args) {
    if (args == null)return;
    try {
      List<String> l_e = new ArrayList<String>();
      List<String> l_v = new ArrayList<String>();

      for (int i = 0; i < args.length; i++) {
        // -h, -help, --help, -?, /?
        if (args[i].equals("-help")
            || args[i].equals("-h")
            || args[i].equalsIgnoreCase("--help")
            || args[i].equals("-?")
            || args[i].equals("/?")) {
          printUsage();
        }

        // -version, --version
        if (args[i].equals("-version") || args[i].equalsIgnoreCase("--version")) {
          printVersion();
        }

        // -b, -basic, --basic
        if (mode == null &&
            (args[i].equals("-b") 
             || args[i].equals("-basic") 
             || args[i].equalsIgnoreCase("--basic"))) {
          mode = Mode.BASIC;
          continue;
        }

        // -d, -default, --default
        if (mode == null && 
            (args[i].equals("-d") 
             || args[i].equals("-default") 
             || args[i].equalsIgnoreCase("--default"))) {
          mode = Mode.DEFAULT;
          continue;
        }

        // -m, -minimalist, --minimalist
        if (mode == null && 
            (args[i].equals("-m") 
             || args[i].equals("-minimalist") 
             || args[i].equalsIgnoreCase("--minimalist"))) {
          mode = Mode.MINIMALIST;
          continue;
        }
        
        // -s, -session, --session <session-file>
        if (args[i].equals("-s")
            || args[i].equals("-session")
            || args[i].equalsIgnoreCase("--session")) {
          i++;
          if (i >= args.length) {
            printUsage();
          }
          sessionPath = args[i];
          continue;
        }
        
        // -e, -edit, --edit <files-to-edit>
        if (args[i].equals("-e")
        	|| args[i].equals("-edit") 
        	|| args[i].equalsIgnoreCase("--edit")) {
          i++;
          if (i >= args.length) {
            printUsage();
          }
          for (; i < args.length && !isKey(args[i]); i++) {
            if (!l_e.contains(args[i]))
              l_e.add(args[i]);
            else
              notifyDuplicateFile(args[i]);
          }
          if (i < args.length && isKey(args[i]))
            i--;
          continue;
        }

        // -v, -view, --view <files-to-view>
        if (args[i].equals("-v")
        	|| args[i].equals("-view") 
        	|| args[i].equalsIgnoreCase("--view")) {
          i++;
          if (i >= args.length) {
            printUsage();
          }
          for (; i < args.length && !isKey(args[i]); i++) {
            if (!l_v.contains(args[i]))
              l_v.add(args[i]);
            else
              notifyDuplicateFile(args[i]);
          }
          if (i < args.length && isKey(args[i]))
            i--;
          continue;
        }

        if (!isKey(args[i])) {
          if (!l_e.contains(args[i]))
            l_e.add(args[i]);
          else
            notifyDuplicateFile(args[i]);
        }
      }

      inputFiles = new ArrayList<JNPadInput>();
      for (String e : l_e)
        inputFiles.add(new JNPadInput(e, 0, false, false, false));
      for (String v : l_v)
        inputFiles.add(new JNPadInput(v, 0, true, false, false));
    }
    catch (Exception ex) {
      System.err.println("Error: " + ex);
      Utilities.exitFailure();
    }
  }
  
  /**
   * Notify duplicate file.
   *
   * @param file the file
   */
  private static void notifyDuplicateFile(String file) {
    System.out.println("WARNING: Duplicate file '" + file + "'");
  }

  /**
   * Configure logging.
   */
  private static void configureLogging() {
    InputStream is = null;
    try {
      File f = new File(PROPS_DIR);
      if (!f.exists()) {
        @SuppressWarnings("unused")
        boolean bool = f.mkdir(); //Keep FindBugs happy
      }

      f = new File(JNPAD_HOME + DIR_SEPARATOR + "logs");
      if (!f.exists()) {
        @SuppressWarnings("unused")
        boolean bool = f.mkdir(); //Keep FindBugs happy
      }

      is = new FileInputStream(LOGGERING_FILE_PATH);
      LogManager.getLogManager().readConfiguration(is);
    }
    catch (IOException ex) {
      // no pasa nada
    }
    finally {
      try {
        if (is != null) {
          is.close();
        }
      }
      catch (IOException ex) {
        ex.printStackTrace();
      }
    }
  }

  /**
   * Check Java Virtual Machine (JVM) version.
   */
  private static void checkJVMVersion() {
    if (!Platform.isJRESupported()) {
      System.err.println("You are using version "
                         + Platform.getJREVersion()
                         + ". jNPad requires Java 1.6 or later.");
      Utilities.exitFailure();
    }
  }

  /**
   * Check headless.
   */
  private static void checkHeadless() {
    if (GraphicsEnvironment.isHeadless()) {
      System.err.println("Unable to detect graphics environment.");
      Utilities.exitFailure();
    }
  }

  //////////////////////////////////////////////////////////////////////////////
  /**
   * The Class JNPadFocusManager.
   */
  private static class JNPadFocusManager extends DefaultKeyboardFocusManager {
    /**
     * Instantiates a new focus manager.
     */
    JNPadFocusManager() {
      setDefaultFocusTraversalPolicy(new LayoutFocusTraversalPolicy());
    }

    /**
     * Post process key event.
     *
     * @param e the key event
     * @return true, if successful
     * @see java.awt.DefaultKeyboardFocusManager#postProcessKeyEvent(java.awt.event.KeyEvent)
     */
    @Override
    public boolean postProcessKeyEvent(KeyEvent e) {
      if (!e.isConsumed()) {
        Component comp = (Component) e.getSource();
        if (!comp.isShowing()) {
          return true;
        }

        for (; ; ) {
          if (comp instanceof JNPadFrame) {
            ( (JNPadFrame) comp).processKeyEvent(e);
            return true;
          }
          else if (comp == null || comp instanceof Window ||
                   comp instanceof JNPadTextArea) {
            break;
          }
          else {
            comp = comp.getParent();
          }
        }
      }

      return super.postProcessKeyEvent(e);
    }
  }
  //////////////////////////////////////////////////////////////////////////////
  
}
