/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.uima.tools.util.gui;

import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.File;

import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.filechooser.FileFilter;

/**
 * Composite component to allow file or directory input or selection. Comprises a JTextField, and a
 * browse button associated with a JFileChooser.
 */

public class FileSelector extends JPanel implements FocusListener {

  /** The Constant serialVersionUID. */
  private static final long serialVersionUID = -1438950608628876422L;

  /** The field. */
  private JTextField field;

  /** The browse button. */
  private BrowseButton browseButton;

  /**
   * Note that fileChooser is created lazily, to address issue UIMA-231. Apparently calls to
   * JFileChooser.setCurrentDirectory aren't reliable before the file chooser has been shown.
   */
  private JFileChooser fileChooser;

  /** The source. */
  private JComponent source;

  /** The file selector listener. */
  private FileSelectorListener fileSelectorListener = null;

  /** The previous value. */
  private String previousValue;

  /** The initial dir. */
  private File initialDir;

  /** The file chooser title. */
  private String fileChooserTitle;

  /** The selection mode. */
  private int selectionMode;

  /** The filter. */
  private FileFilter filter;

  /**
   * Creates a new FileSelector.
   * 
   * @param initialValue
   *          filename initially displayed in the text field
   * @param fileChooserTitle
   *          title of the JFileChooser launched when the user clicks Browse
   * @param selectionMode
   *          Can be either JFileChooser.FILES_ONLY, JFileChooser.DIRECTORIES_ONLY or
   *          JFileChooser.FILES_AND_DIRECTORIES
   */
  public FileSelector(String initialValue, String fileChooserTitle, int selectionMode) //
  {
    this(initialValue, fileChooserTitle, selectionMode, null);
  }

  /**
   * Creates a new FileSelector.
   * 
   * @param initialValue
   *          filename initially displayed in the text field
   * @param fileChooserTitle
   *          title of the JFileChooser launched when the user clicks Browse
   * @param selectionMode
   *          Can be either JFileChooser.FILES_ONLY, JFileChooser.DIRECTORIES_ONLY or
   *          JFileChooser.FILES_AND_DIRECTORIES
   * @param currentDir
   *          default directory for the file chooser
   */
  public FileSelector(String initialValue, String fileChooserTitle, int selectionMode,
          File currentDir) {
    this(initialValue, fileChooserTitle, selectionMode, currentDir, null);
  }

  /**
   * Creates a new FileSelector.
   * 
   * @param initialValue
   *          filename initially displayed in the text field
   * @param fileChooserTitle
   *          title of the JFileChooser launched when the user clicks Browse
   * @param selectionMode
   *          Can be either JFileChooser.FILES_ONLY, JFileChooser.DIRECTORIES_ONLY or
   *          JFileChooser.FILES_AND_DIRECTORIES
   * @param currentDir
   *          default directory for the file chooser
   * @param filter
   *          file filter used by the file chooser
   */
  public FileSelector(String initialValue, String fileChooserTitle, int selectionMode,
          File currentDir, FileFilter filter) {
    if (currentDir == null && initialValue != null) {
      currentDir = new File(initialValue).getAbsoluteFile();
    }
    this.initialDir = currentDir;
    this.fileChooserTitle = fileChooserTitle;
    this.selectionMode = selectionMode;
    this.filter = filter;

    setLayout(new BoxLayout(this, BoxLayout.X_AXIS));

    field = new JTextField(initialValue, 20);
    field.addFocusListener(this);
    add(field);

    previousValue = initialValue == null ? "" : initialValue;

    add(Box.createHorizontalStrut(8));

    browseButton = new BrowseButton("Browse..");
    add(browseButton);

    browseButton.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        JFileChooser chooser = getFileChooser();
        int returnVal = chooser.showOpenDialog(browseButton);

        if (returnVal == JFileChooser.APPROVE_OPTION) {
          File file = chooser.getSelectedFile();
          String fileString = file.getAbsolutePath();

          if (fileSelectorListener != null) {
            boolean result = fileSelectorListener.fileSelected(source, fileString);
            // Only update textField is successful:
            if (result == true) // Success
            {
              field.setText(fileString);
              previousValue = fileString;
            } else
              // Failure - restore previous value:
              chooser.setSelectedFile(new File(previousValue));
          } else {
            field.setText(fileString);
            previousValue = fileString;
          }
        }
      }
    });

    field.addFocusListener(new FocusAdapter() {
      @Override
      public void focusLost(FocusEvent e) {
        // did text change?
        String fileString = field.getText();
        if (!fileString.equals(previousValue)) {
          if (fileSelectorListener != null) {
            boolean result = fileSelectorListener.fileSelected(source, fileString);
            // Only update textField if successful:
            if (result == true) // Success
            {
              previousValue = fileString;
            } else
            // Failure - restore previous value:
            if (fileChooser != null) {
              fileChooser.setSelectedFile(previousValue == null ? null : new File(previousValue));
            }
          } else {
            previousValue = fileString;
          }
        }
      }
    });

    field.addKeyListener(new KeyAdapter() {
      @Override
      public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_ENTER) {
          String fileString = field.getText();
          if (fileSelectorListener != null) {
            String oldValue = previousValue;
            // have to set previousValue to new value here to avoid
            // focusLost event triggering infinite loop
            previousValue = fileString;

            boolean result = fileSelectorListener.fileSelected(source, fileString);
            if (!result) // undo
            {
              previousValue = oldValue;
              if (fileChooser != null) {
                fileChooser.setSelectedFile(previousValue == null ? null : new File(previousValue));
              }
            }
          } else {
            previousValue = fileString;
          }
        }
      }
    });
  }

  /**
   * Adds the file selector listener.
   *
   * @param fileSelectorListener
   *          the file selector listener
   * @param source
   *          the source
   */
  public void addFileSelectorListener(FileSelectorListener fileSelectorListener,
          JComponent source) {
    this.fileSelectorListener = fileSelectorListener;
    this.source = source;
  }

  /**
   * Gets the selected.
   *
   * @return the selected
   */
  public String getSelected() {
    return field.getText();
  }

  /**
   * Sets the selected.
   *
   * @param s
   *          the new selected
   */
  public void setSelected(String s) {
    field.setText(s);
    previousValue = s;
    if (s == null || s.length() == 0) {
      s = System.getProperty("user.dir");
    }
    File file = new File(s);

    // only modify file chooser if it has already been created
    if (this.fileChooser != null) {
      if (this.fileChooser.getFileSelectionMode() == JFileChooser.FILES_ONLY
              && file.isDirectory()) {
        this.fileChooser.setCurrentDirectory(file);
      } else {
        this.fileChooser.setSelectedFile(file);
      }
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see javax.swing.JComponent#setEnabled(boolean)
   */
  @Override
  public void setEnabled(boolean onOff) {
    field.setEnabled(onOff);
    browseButton.setEnabled(onOff);
  }

  /**
   * Clear.
   */
  public void clear() {
    field.setText("");
  }

  /**
   * The Class BrowseButton.
   */
  static class BrowseButton extends JButton {

    /** The Constant serialVersionUID. */
    private static final long serialVersionUID = 1086776109494251334L;

    /**
     * Instantiates a new browse button.
     *
     * @param s
     *          the s
     */
    public BrowseButton(String s) {
      super(s);
    }

    /*
     * (non-Javadoc)
     * 
     * @see javax.swing.JComponent#getInsets()
     */
    @Override
    public Insets getInsets() {
      return new Insets(3, 6, 3, 6);
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see java.awt.event.FocusListener#focusGained(java.awt.event.FocusEvent)
   */
  @Override
  public void focusGained(FocusEvent aEvent) {
  }

  /*
   * (non-Javadoc)
   * 
   * @see java.awt.event.FocusListener#focusLost(java.awt.event.FocusEvent)
   */
  @Override
  public void focusLost(FocusEvent aEvent) {
    if (aEvent.getComponent() == this.field) {
      // only modify file chooser if it has already been created
      if (this.fileChooser != null) {
        String path = this.getSelected();
        if (path.length() == 0) {
          path = System.getProperty("user.dir");
        }
        File file = new File(path);

        if (this.fileChooser.getFileSelectionMode() == JFileChooser.FILES_ONLY
                && file.isDirectory()) {
          this.fileChooser.setCurrentDirectory(file);
        } else {
          this.fileChooser.setSelectedFile(file);
        }
      }
    }

  }

  /**
   * Get the File Chooser user when the Browse button is clicked. This is lazily created when
   * needed, because of issue UIMA-231.
   * 
   * @return the file chooser
   */
  protected JFileChooser getFileChooser() {
    if (this.fileChooser == null) {
      String val = field.getText();
      final File selected = (val == null) ? null : new File(val);
      fileChooser = new JFileChooser(initialDir);
      fileChooser.setDialogTitle(fileChooserTitle);
      fileChooser.setFileSelectionMode(selectionMode);
      if (filter != null) {
        fileChooser.addChoosableFileFilter(filter);
      }
      fileChooser.setSelectedFile(selected);
    }
    return this.fileChooser;
  }

}
