package nxm.sys.libg;

import java.awt.event.ActionListener;
import java.text.Collator;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Vector;

import javax.swing.AbstractListModel;
import javax.swing.ListModel;

import nxm.sys.lib.Midas;
import nxm.sys.lib.Parser;
import nxm.sys.lib.Shell;
import nxm.sys.libg.MJPopup.HasValue;

/** A class to implement a graphical file selection widget for use by the {@link MJPopup} library.
    This is a separate class to allow use of the NetBeans GUI builder since the design is more
    complex than most other popups.

    NOTE: MJFileChooser does NOT currently support Java Serialization, even though it extends classes that support it.

    @author  Neon Ngo
    @author  J. Czechowski IV
    @since   NeXtMidas 2.5.0
*/
class MJFileChooser extends javax.swing.JPanel implements ActionListener, HasValue {
  /** NOTE: this should be changed if the structure/fields of this class changes.
   *  This is being left to avoid warnings, though this class is not truly serializable.
   *  We are not overwriting readObject and writeObject because serialization will error out on javax.swing.GroupLayout */
  private static final long serialVersionUID = 2012012400311L; // for Serializable interface

  /** File chooser filename path options.
      <pre>
        FullPath  - Display full path to file (e.g. /home/user/nxm/sys/dat/apenny.prm)
        NoPath    - Display filename without path (e.g. apenny.prm)
        AuxPath   - Display {@code <aux>/<filename>} (e.g. DAT/apenny.prm)
        AuxQual   - Display {@code <filename>}{AUX=&lt;aux&gt;} (e.g. apenny.prm{AUX=DAT})
      </pre>
   */
  public static final String pathOptionList = "FullPath,NoPath,AuxPath,AuxQual";

  /** Path Option: Display full path to file (see {@link #pathOptionList}). */
  public static final int FULLPATH = 1;
  /** Path Option: Display filename without path (see {@link #pathOptionList}). */
  public static final int NOPATH   = 2;
  /** Path Option: Display <code>&lt;aux&gt;/&lt;filename&gt;</code> (see {@link #pathOptionList}). */
  public static final int AUXPATH  = 3;
  /** Path Option: Display <code>&lt;filename&gt;{AUX=&lt;aux&gt;}</code> (see {@link #pathOptionList}). */
  public static final int AUXQUAL  = 4;

  private static final String AUX_FILE_SEPARATOR = "/"; // Used with AuxPath option

  private transient Midas  midas;              // Midas Context
  /** Initial filename */
  private           String seedFilename;
  /** Filename filter option */
  private           String filter;
  /** Name of the popup type (see getName()) */
  private           String name;
  /** Filename path option (defaults to FullPath) */
  private           int    pathOption;

  /** DataModel for displaying files */
  private           DataListModel  fileListModel = new DataListModel();
  private transient FileComparator comparator    = new FileComparator(); // FileComparator for sorting files

  /** Creates new form MJFileChooser dialog, and set Midas Context
      @param name            The name of the popup component (can be null).
      @param msg             The message to display (not null).
      @param defValue        The default filename for the file name to display (can be null)
      @param filter          The filename filter options (null=DisplayAll)
      @param selectedAuxList The selected AUX list (null=AUX.WRITE)
      @param pathOpt         The filename path option (see {@link #pathOptionList}).
      @param midas           The Midas context reference.
   */
  public MJFileChooser(String name, Object msg, Object defValue, String filter,
                       String selectedAuxList, String pathOpt, Midas midas) {
    this.name         = name;
    this.midas        = (midas    != null)? midas               : Shell.getSharedMidasContext();
    this.seedFilename = (defValue != null)? defValue.toString() : "";
    this.filter       = (filter   != null)? filter              : "*";
    this.pathOption   = Parser.find(pathOptionList,pathOpt,AUXQUAL,0,Parser.E_WARNING);

    initComponents();                 // initialize graphics components
    updateAuxList(selectedAuxList);   // select specified AUXes
    formNameList();                   // form file name list
    msgLabel.setText(msg.toString()); // displays the message text
  }

  // These three methods implement HasValue
  public String getName()  { return name; }
  public int    getInfo()  { return 0; }
  public Object getValue() { return jSelectedFilename.getText(); }

  /** Refresh the contents of the file name list from the currently selected AUXes */
  public void refresh() { formNameList(); }

  /** Return the seed (initial) filename to use for this file chooser
   *  @return the seed (initial) filename to use for this file chooser
   */
  public String getSeedFilename() { return seedFilename; }

  /** Return the filename filter to use for this file chooser
   *  @return the filename filter to use for this file chooser
   */
  public String getFilter() { return filter; }

  /** Return a SortedListModel instead of the unordered ListModel */
  private ListModel<String> getFileListModel() { return fileListModel; }

  /** Return the ListModel for the AUX list */
  private ListModel<String> getAuxListModel() {
    ListModel<String> auxListModel;

    if (midas != null) {
      String[] auxList = midas.io.getAuxList();
      Arrays.sort(auxList);

      auxListModel = new DataListModel(auxList);
    } else {
      auxListModel = new DataListModel(null);
    }
    return auxListModel;
  }

  /** Form the ListModel of all the AUXes to put in JList
      @param selectedAuxList list of AUXes to return the indices of in the model for selection
   */
  private void updateAuxList (String selectedAuxList) {
    if (midas != null) {
      ListModel<String> auxListModel = jAuxList.getModel();
      String[]  readAux;

      if (selectedAuxList != null) {
        readAux = selectedAuxList.split("[|,]");
      } else {
        // readAux = midas.io.getReadAux();                // 2.7.0 and prior
        readAux = new String[] { midas.io.getWriteAux() }; // 2.7.1 Bug 1855
      }
      Arrays.sort(readAux);

      jAuxList.clearSelection();
      for (int i = 0; i < auxListModel.getSize(); i++) {
        String aux = auxListModel.getElementAt(i).toString();

        if (Arrays.binarySearch(readAux, aux) >= 0) {
          jAuxList.addSelectionInterval(i,i);
        }
      }
    }
  }

  /** Form the Filename list from the selected AUXes and add them to the Filenames ListModel.
      NOTE: This can take a long time if there are lots of files in the selected AUXes.
   */
  private void formNameList() {
    String filter = jFilterComboBox.getSelectedItem() + "";
    int pos       = filter.indexOf('.');
    String root   = filter;
    String ext    = "*";
    if (pos >= 0) {
      root = filter.substring(0, pos);
      ext  = filter.substring(pos + 1);
    }

    fileListModel.removeAllElements();
    List<String> selectedAuxes = jAuxList.getSelectedValuesList(); // Java 7 API (vs deprecated getSelectedValues())
    for (String aux : selectedAuxes) {
      String path    = midas.io.getAuxPath(aux);
      String[] files = midas.io.listFiles(path, root, ext);
      if (files==null) continue;
      fileListModel.addElements(aux + AUX_FILE_SEPARATOR, files);
    } // end for loop
    fileListModel.sortBy(comparator); // sort files list with current comparator
  }

  /** This method is called from within the constructor to initialize the form.
   * WARNING: Do NOT modify this code. The content of this method is
   * always regenerated by the Form Editor.
   */
  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
  private void initComponents() {

    jFilesPopupMenu = new javax.swing.JPopupMenu();
    javax.swing.JMenuItem jRefreshMenuItem = new javax.swing.JMenuItem();
    javax.swing.JSeparator jSeparator1 = new javax.swing.JSeparator();
    javax.swing.JRadioButtonMenuItem jSortByAuxMenuItem = new javax.swing.JRadioButtonMenuItem();
    javax.swing.JRadioButtonMenuItem jSortByFilenameMenuItem = new javax.swing.JRadioButtonMenuItem();
    javax.swing.JSeparator jSeparator2 = new javax.swing.JSeparator();
    javax.swing.JRadioButtonMenuItem jSortAscending = new javax.swing.JRadioButtonMenuItem();
    javax.swing.JRadioButtonMenuItem jSortDescending = new javax.swing.JRadioButtonMenuItem();
    javax.swing.ButtonGroup sortOptionGroup = new javax.swing.ButtonGroup();
    javax.swing.ButtonGroup sortOrderGroup = new javax.swing.ButtonGroup();
    javax.swing.JScrollPane auxListScrollPane = new javax.swing.JScrollPane();
    jAuxList = new javax.swing.JList<>();
    javax.swing.JScrollPane fileListScrollPane = new javax.swing.JScrollPane();
    jFileList = new javax.swing.JList<>();
    jSelectedFilename = new javax.swing.JTextField();
    jFilterComboBox = new javax.swing.JComboBox<>();
    javax.swing.JLabel jAuxLabel = new javax.swing.JLabel();
    javax.swing.JLabel jFilenameLabel = new javax.swing.JLabel();
    javax.swing.JLabel jFilterLabel = new javax.swing.JLabel();
    javax.swing.JLabel jFilesLabel = new javax.swing.JLabel();
    msgLabel = new javax.swing.JLabel();

    jFilesPopupMenu.setToolTipText("");
    jFilesPopupMenu.setLabel("hi");
    jFilesPopupMenu.setName(""); // NOI18N

    jRefreshMenuItem.setText("Refresh List");
    jRefreshMenuItem.setToolTipText("Refresh Files List");
    jRefreshMenuItem.addActionListener(this);
    jFilesPopupMenu.add(jRefreshMenuItem);
    jFilesPopupMenu.add(jSeparator1);

    sortOptionGroup.add(jSortByAuxMenuItem);
    jSortByAuxMenuItem.setSelected(true);
    jSortByAuxMenuItem.setText("Sort By AUX");
    jSortByAuxMenuItem.setToolTipText("Sort By AUX, then Filename");
    jSortByAuxMenuItem.addActionListener(this);
    jFilesPopupMenu.add(jSortByAuxMenuItem);

    sortOptionGroup.add(jSortByFilenameMenuItem);
    jSortByFilenameMenuItem.setText("Sort By Filename");
    jSortByFilenameMenuItem.setToolTipText("Sort By Filename");
    jSortByFilenameMenuItem.addActionListener(this);
    jFilesPopupMenu.add(jSortByFilenameMenuItem);
    jFilesPopupMenu.add(jSeparator2);

    sortOrderGroup.add(jSortAscending);
    jSortAscending.setSelected(true);
    jSortAscending.setText("Ascending");
    jSortAscending.setToolTipText("Ascending Sort Order");
    jSortAscending.addActionListener(this);
    jFilesPopupMenu.add(jSortAscending);

    sortOrderGroup.add(jSortDescending);
    jSortDescending.setText("Descending");
    jSortDescending.setToolTipText("Descending Sort Order");
    jSortDescending.addActionListener(this);
    jFilesPopupMenu.add(jSortDescending);

    auxListScrollPane.setMaximumSize(jAuxList.getMaximumSize());
    auxListScrollPane.setMinimumSize(jAuxList.getMinimumSize());
    auxListScrollPane.setPreferredSize(jAuxList.getPreferredSize());

    jAuxList.setModel(getAuxListModel());
    jAuxList.setMaximumSize(new java.awt.Dimension(50, 0));
    jAuxList.setMinimumSize(new java.awt.Dimension(30, 40));
    jAuxList.addListSelectionListener(new javax.swing.event.ListSelectionListener() {
      public void valueChanged(javax.swing.event.ListSelectionEvent evt) {
        jAuxListValueChanged(evt);
      }
    });
    auxListScrollPane.setViewportView(jAuxList);

    fileListScrollPane.setMaximumSize(jFileList.getMaximumSize());
    fileListScrollPane.setMinimumSize(jFileList.getMinimumSize());
    fileListScrollPane.setPreferredSize(jFileList.getPreferredSize());

    jFileList.setModel(getFileListModel());
    jFileList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
    jFileList.setMinimumSize(new java.awt.Dimension(100, 40));
    jFileList.addMouseListener(new java.awt.event.MouseAdapter() {
      public void mousePressed(java.awt.event.MouseEvent evt) {
        displayPopupMenu(evt);
      }
      public void mouseReleased(java.awt.event.MouseEvent evt) {
        displayPopupMenu(evt);
      }
    });
    jFileList.addListSelectionListener(new javax.swing.event.ListSelectionListener() {
      public void valueChanged(javax.swing.event.ListSelectionEvent evt) {
        jFileListValueChanged(evt);
      }
    });
    fileListScrollPane.setViewportView(jFileList);

    jSelectedFilename.setText(getSeedFilename());

    jFilterComboBox.setEditable(true);
    jFilterComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { this.filter }));
    jFilterComboBox.setToolTipText("Filter Format =  *.extension1|extension2|extension3|...");

    jAuxLabel.setLabelFor(jAuxList);
    jAuxLabel.setText("AUXes");
    jAuxLabel.setToolTipText("AUX Selection List");
    jAuxLabel.setFocusable(false);
    jAuxLabel.setOpaque(true);

    jFilenameLabel.setLabelFor(jSelectedFilename);
    jFilenameLabel.setText("File Name");

    jFilterLabel.setLabelFor(jFilterComboBox);
    jFilterLabel.setText("Filter");

    jFilesLabel.setLabelFor(jFileList);
    jFilesLabel.setText("Files");
    jFilesLabel.setToolTipText("Files Selection List");

    msgLabel.setText("Message");

    javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
    this.setLayout(layout);
    layout.setHorizontalGroup(
      layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(layout.createSequentialGroup()
        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
          .addGroup(layout.createSequentialGroup()
            .addContainerGap()
            .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
              .addComponent(jFilenameLabel)
              .addComponent(jFilterLabel)
              .addComponent(auxListScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 90, Short.MAX_VALUE))
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
              .addComponent(fileListScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 241, Short.MAX_VALUE)
              .addComponent(jFilterComboBox, javax.swing.GroupLayout.Alignment.TRAILING, 0, 241, Short.MAX_VALUE)
              .addComponent(jSelectedFilename, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 241, Short.MAX_VALUE)))
          .addComponent(msgLabel))
        .addContainerGap())
      .addGroup(layout.createSequentialGroup()
        .addContainerGap()
        .addComponent(jAuxLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 90, Short.MAX_VALUE)
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addComponent(jFilesLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 232, Short.MAX_VALUE)
        .addGap(21, 21, 21))
    );
    layout.setVerticalGroup(
      layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(layout.createSequentialGroup()
        .addComponent(msgLabel)
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(jAuxLabel)
          .addComponent(jFilesLabel))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
          .addComponent(fileListScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 201, Short.MAX_VALUE)
          .addComponent(auxListScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 201, Short.MAX_VALUE))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(jFilenameLabel)
          .addComponent(jSelectedFilename, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
          .addComponent(jFilterComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
          .addComponent(jFilterLabel))
        .addContainerGap())
    );
  }// </editor-fold>//GEN-END:initComponents

  private void jFileListValueChanged(javax.swing.event.ListSelectionEvent evt) {//GEN-FIRST:event_jFileListValueChanged
    if (!evt.getValueIsAdjusting()) {
      String selected = jFileList.getSelectedValue();
      if (selected == null) return; // nothing was selected, so return

      int    index   = selected.indexOf(AUX_FILE_SEPARATOR);
      String aux     = selected.substring(0, index);
      String name    = selected.substring(index + 1);
      String auxPath = (midas == null)? "": midas.io.getAuxPath(aux);

      String filename;
      switch (pathOption) {
        default:       // default matches FULLPATH
        case FULLPATH: filename = auxPath + name;                  break;
        case NOPATH:   filename = name;                            break;
        case AUXPATH:  filename = aux + AUX_FILE_SEPARATOR + name; break;
        case AUXQUAL:  filename = name + "{AUX=" + aux + "}";      break;
      }

      jSelectedFilename.setText(filename);
      jSelectedFilename.setColumns(filename.length());
    }
  }//GEN-LAST:event_jFileListValueChanged

  private void displayPopupMenu(java.awt.event.MouseEvent evt) {
    if (evt.isPopupTrigger()) jFilesPopupMenu.show(evt.getComponent(), evt.getX(), evt.getY());
  }

  /** AUX list selection has changed */
  private void jAuxListValueChanged(javax.swing.event.ListSelectionEvent evt) {//GEN-FIRST:event_jAuxListValueChanged
    if (!evt.getValueIsAdjusting()) formNameList();
  }//GEN-LAST:event_jAuxListValueChanged


  public void actionPerformed(java.awt.event.ActionEvent evt) {
    String actionCommand = evt.getActionCommand();
    if (actionCommand.startsWith("Refresh")) {
      refresh();
    } else if (actionCommand.equals("Sort By Filename")) {
      if (! AUX_FILE_SEPARATOR.equals(comparator.skip)) {
        comparator.skip = AUX_FILE_SEPARATOR;
        fileListModel.sortBy(comparator);
      }
    } else if (actionCommand.equals("Sort By AUX")) {
      if (comparator.skip != null) {
        comparator.skip = null;
        fileListModel.sortBy(comparator);
      }
    } else if (actionCommand.equals("Ascending")) {
      if (!comparator.ascending) {
        comparator.ascending = true;
        fileListModel.sortBy(comparator);
      }
    } else if (actionCommand.equals("Descending")) {
      if (comparator.ascending) {
        comparator.ascending = false;
        fileListModel.sortBy(comparator);
      }
    } else {
      String message = "MJFileChooser: Unsupported ActionEvent=" + evt;
      if (midas != null) midas.info(message);
      else Shell.info(message);
    }
  }

  // Variables declaration - do not modify//GEN-BEGIN:variables
  /** The AUX selection list */
  private javax.swing.JList<String> jAuxList;
  /** The file selection list */
  private javax.swing.JList<String> jFileList;
  /** The main files popup menu*/
  private javax.swing.JPopupMenu jFilesPopupMenu;
  /** The file filter combo box */
  private javax.swing.JComboBox<String> jFilterComboBox;
  /** The selected filename */
  private javax.swing.JTextField jSelectedFilename;
  /** The label for the message to display */
  private javax.swing.JLabel msgLabel;
  // End of variables declaration//GEN-END:variables

  /** Comparator to do a string comparison while ignoring (skipping), the specified "skip" string at the beginning.
   *  If skip string is null, then use Comparator from {@link Collator#getInstance()}.
   *  @since NeXtMidas 2.5.0
   */
  private static class FileComparator implements Comparator<Object> {
    protected String   skip       = null;
    protected boolean  ascending  = true; // true for ascending (default), false for descending
    private Comparator<Object> comparator = Collator.getInstance();

    protected FileComparator() {}

    public int compare (Object o1, Object o2) {
      if ( (skip != null) && (o1 instanceof String) && (o2 instanceof String) )  {
        String s1  = (String) o1;
        String s2  = (String) o2;
        int index1 = s1.indexOf(skip);
        int index2 = s2.indexOf(skip);
        if (index1>0) o1 = s1.substring(index1);
        if (index2>0) o2 = s2.substring(index2);
      }
      int result = comparator.compare(o1, o2);
      if (!ascending) result = -result;
      return result;
    }
  } // end class FileComparator


  /** Generic Data list model for JList that stores a Vector of data. <pre>
      Allows initialization with array of Objects.
      Allows removing all items from the data model.
      Allows adding an array of Objects to the data model.
      Allows sorting with a specified Comparator or null for natural order.
      </pre>
     @author Neon Ngo
     @since NeXtMidas 2.5.0
     @version $Id: MJFileChooser.java,v 1.12 2015/11/03 14:25:28 ntn Exp $
  */
  private static class DataListModel extends AbstractListModel<String> {
    /** NOTE: this should be changed if the structure/fields of this class changes. */
    private static final long serialVersionUID = 2012012100311L; // for Serializable interface

    private Vector<String> data;

    public DataListModel() {
      data = new Vector<>();
    }

    /** Constructs a Model object initialized with an array of Objects.
       @param objects an array of Objects
    */
    public DataListModel (final String[] objects) {
      data = new Vector<>();
      if (objects == null || objects.length <= 0) {
        return; // ignore null & empty objects
      }
      data.ensureCapacity(objects.length);

      for (int i = 0, len = objects.length; i<len; i++) {
        data.addElement(objects[i]);
      }
    }

    public int getSize() {
      return data.size();
    }

    public String getElementAt (int index) {
      if ( index >= 0 && index < data.size() ) {
        return data.elementAt(index);
      } else {
        return null;
      }
    }

    /** Add an Object to the list model */
    public void addElement (String anObject) {
      data.addElement(anObject);
      fireIntervalAdded(this,data.size()-1, data.size()-1);
    }

    /** Add all items in array of Objects to the list model */
    public void addElements (String[] objects) {
      if (objects == null || objects.length <= 0) return; // ignore null & empty objects

      int index0 = data.size();
      data.addAll(Arrays.asList(objects));
      fireIntervalAdded(this, index0, data.size()-1); // fires event as a group to be more efficient
    }

    /** Add all items in strings array to the list model, with a prefix. */
    public void addElements (String prefix, String[] strings) {
      if (strings == null || strings.length <= 0) {
        return; // ignore null & empty strings
      }
      int index0 = data.size();

      for (int i = 0, len = strings.length; i<len; i++) {
        data.addElement(prefix + strings[i]);
      }
      // fires event as a group to be more efficient
      fireIntervalAdded(this, index0, data.size()-1);
    }

    /** Empties the list. */
    public void removeAllElements () {
      if (data.size() > 0) {
        int lastIndex = data.size() - 1;
        data.removeAllElements();
        fireIntervalRemoved(this, 0, lastIndex);
      }
    }

    /** Sort list with the specified comparator using {@link Collections#sort(java.util.List, Comparator)}. */
    public void sortBy (Comparator<Object> comparator) {
      Collections.sort(data, comparator);
      int lastIndex = data.size() - 1;
      fireContentsChanged(this, 0, lastIndex);
    }
  } // end inner class DataListModel

}
