//=================================================================
//                 Scatter Plot MenuActionListener
//=================================================================
//
//    This class provides the means for controlling the required
// actions needed to execute the various activities used in the
// ScatterPlot plugin. The events are generated by the Plotter_MenuBar
// & Plotter_ToolBar classes.
//
//                 << MenuActionListener.java >>
//
//=================================================================
// Copyright (c) 2017 Dana M. Proctor.
// Version 1.0 11/01/2017
//
// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
// (http://opensource.org)
//
//=================================================================
// Revision History
// Changes to the code should be documented here and reflected
// in the present version number. Author information should
// also be included with the original copyright author.
//=================================================================
// Version 1.0 11/01/2017 Original Scatter Plot MenuActionListener Class.
//         
//-----------------------------------------------------------------
//                 danap@dandymadeproductions.com
//=================================================================

package com.dandymadeproductions.scatterplot;

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;

import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.filechooser.FileFilter;

import org.jfree.data.general.SeriesException;

import com.dandymadeproductions.lindyframe.LindyFrame;
import com.dandymadeproductions.lindyframe.utilities.LindyFrame_ProgressBar;
import com.dandymadeproductions.lindyframe.utilities.InputDialog;
import com.dandymadeproductions.lindyframe.gui.LindyFrame_MenuActionCommands;
import com.dandymadeproductions.lindyframe.utilities.LindyFrame_ResourceBundle;
import com.dandymadeproductions.lindyframe.utilities.LindyFrame_Utils;
import com.dandymadeproductions.lindyframe.utilities.ImageUtil;
import com.dandymadeproductions.scatterplot.data.DataChartSeries;
import com.dandymadeproductions.scatterplot.data.LineChartSeries;

/**
 *    The MenuActionListener class provides the means for controlling
 * the required actions needed to execute the various activities used
 * in the Scatter Plot plugin. The events are generated by the
 * Plotter_MenuBar &amp; Plotter_ToolBar classes.
 * 
 * @version 1.0 11/01/2017
 */

class MenuActionListener implements ActionListener
{
   // Class Instances
   private JFrame parent;
   private PlotPanel plotPanel;
   
   private LindyFrame_ResourceBundle resourceBundle;
   private String imagesDirectory;
   
   private String resourceTitleAlert, resourceErrorFile;
   private String lastOpenDirectory, lastSaveDirectory;
   
   //==============================================================
   // MenuActionListener Constructor.
   //==============================================================

   public MenuActionListener(JFrame mainFrame, PlotPanel plotPanel,
                             LindyFrame_ResourceBundle resourceBundle, String imagesDirectory)
   {
      parent = mainFrame;
      this.plotPanel = plotPanel;
      this.resourceBundle = resourceBundle;
      this.imagesDirectory = imagesDirectory;

      // Setup
      lastOpenDirectory = "";
      resourceTitleAlert = resourceBundle.getResourceString("MenuActionListener.dialogtitle.Alert",
                                                            "Alert");
      resourceErrorFile = resourceBundle.getResourceString("MenuActionListener.dialogmessage.FileNOTFound",
                                                           "File NOT Found");    
   }

   // ==============================================================
   // ActionEvent Listener method for detecting the inputs from the
   // application and directing to the appropriate routine.
   // ==============================================================

   public void actionPerformed(ActionEvent evt)
   {
      // Setting up some needed instance variables.
      String actionCommand;
      Object item;

      // Initializing
      item = evt.getSource();

      if (item instanceof JMenuItem)
         actionCommand = ((JMenuItem) item).getActionCommand();
      else if (item instanceof JButton)
         actionCommand = ((JButton) item).getActionCommand();
      else
         actionCommand = "";
      // System.out.println(actionCommand);

      // Directing Appropriate Actions.

      // =============================
      // File Actions
      
      // Open
      if (actionCommand.equals(Plotter_MenuBar.ACTION_FILE_OPEN))
      {
         openAction();
         return;
      }
      
      // Save As Image
      if (actionCommand.equals(Plotter_MenuBar.ACTION_SAVE_AS_IMAGE))
      {
         saveAsImageAction();
         return;
      }

      // Exit (This one caught by lindyFrame.)
      if (actionCommand.equals(LindyFrame_MenuActionCommands.ACTION_EXIT))
      {
         // System.out.println("File Exit");
         return;
      }
      
      // =============================
      // Edit Actions
      
      // Preferences
      if (actionCommand.equals(Plotter_MenuBar.ACTION_EDIT_PREFERENCES))
      {
         editPreferences();
         return;
      }
      
      // =============================
      // Data Actions

      // Import CSV File
      if (actionCommand.equals(Plotter_MenuBar.ACTION_DATA_IMPORT_CSV))
      {
         importAction();
         return;
      }
   }
   
   //==============================================================
   // Class Method to open a data file for plotting.
   //==============================================================

   private void openAction()
   {
      // Method Instances.
      JFileChooser dataFileChooser;
      String fileName;

      // Choosing the directory to import data from.
      if (lastOpenDirectory.equals(""))
         dataFileChooser = new JFileChooser();
      else
         dataFileChooser = new JFileChooser(new File(lastOpenDirectory));

      // Add a FileFilter for *.html and open dialog.
      dataFileChooser.setFileFilter(new FileFilter()
      {
         public boolean accept(File file)
         {
            String extension, fileName;
            int lastIndexOfDot;
            
            // All Directories
            if (file.isDirectory())
               return true;
            
            // Only, *.csv
            extension = "";
            fileName = file.getName();
            lastIndexOfDot = fileName.lastIndexOf('.');

            if (lastIndexOfDot > 0 &&  lastIndexOfDot < fileName.length() - 1)
                extension = fileName.substring(lastIndexOfDot + 1).toLowerCase();
            
            if (extension.equals("csv"))
               return true;
            
            // Nope.
            return false;
          }
         public String getDescription()
         {
            return "CSV Files";
         } 
      });

      int result = dataFileChooser.showOpenDialog(parent);

      // Looks like might be good so lets check and read data.
      if (result == JFileChooser.APPROVE_OPTION)
      {
         // Save the selected directory so can be used again.
         lastOpenDirectory = dataFileChooser.getCurrentDirectory().toString();

         // Collect file name.
         fileName = dataFileChooser.getSelectedFile().getName();
         fileName = dataFileChooser.getCurrentDirectory() + LindyFrame_Utils.getFileSeparator() + fileName;

         // Try Loading the file.
         if (!fileName.equals(""))
         {
            final String dataFileName = fileName;
            
            Thread importActionThread = new Thread(new Runnable()
            {
               public void run()
               {
                  loadCSVData(dataFileName, true);
               }
            }, "MenuActionListener.openAction()");
            importActionThread.start();
         }
         else
         {
            JOptionPane.showMessageDialog(null, resourceErrorFile, resourceTitleAlert,
                                          JOptionPane.ERROR_MESSAGE);
         }
      }
   }
   
   //==============================================================
   // Class Method to save a snapshot of the Chart's graphics to
   // a PNG image file.
   //==============================================================

   private void saveAsImageAction()
   {
      ImageUtil imageUtil = new ImageUtil(plotPanel.getChartsPanel(), lastSaveDirectory, "png");
      lastSaveDirectory = imageUtil.getLastSaveDiretory();
      return;
   }
   
   //==============================================================
   // Class Method facilitate the selection of preferences for the
   // query building. 
   //==============================================================

   private void editPreferences()
   {
      // Method Instances.
      InputDialog preferencesDialog;
      PreferencesPanel preferencesPanel;
      
      String resourceTitle;
      String resourceOK, resourceCancel;
      
      // Core options components panel.
      preferencesPanel = new PreferencesPanel(resourceBundle, imagesDirectory);

      // Setup a dialog with the content.
      Object content[] = {preferencesPanel};
      
      resourceTitle = resourceBundle.getResourceString(
         "MenuActionListener.dialogtitle.Preferences", "Preferences");
      resourceOK = resourceBundle.getResourceString("MenuActionListener.button.OK", "OK");
      resourceCancel = resourceBundle.getResourceString("MenuActionListener.button.Cancel", "Cancel");
      
      preferencesDialog = new InputDialog(null, resourceTitle, resourceOK, resourceCancel, content, null);
      preferencesDialog.pack();
      preferencesDialog.setPreferredSize(new Dimension(350, 355));
      preferencesDialog.setResizable(false);
      preferencesDialog.center();
      preferencesDialog.setVisible(true);

      // Collect the preferences on a confirmation.
      if (preferencesDialog.isActionResult())
      {
         preferencesPanel.setProperties();
         plotPanel.getChartsPanel().setProperties(plotPanel.getChartType());
      }
      
      preferencesDialog.dispose();
   }
   
   //==============================================================
   // Class Method to import a CSV data file for plotting.
   //==============================================================

   private void importAction()
   {
      // Method Instances.
      JFileChooser dataFileChooser;
      String fileName;

      // Choosing the directory to import data from.
      if (lastOpenDirectory.equals(""))
         dataFileChooser = new JFileChooser();
      else
         dataFileChooser = new JFileChooser(new File(lastOpenDirectory));

      // Add a FileFilter for *.csv and open dialog.
      String[] exts = {"csv"};
      dataFileChooser.setFileFilter(new CustomFileFilter(exts));

      int result = dataFileChooser.showOpenDialog(parent);

      // Looks like might be good so lets check and read data.
      if (result == JFileChooser.APPROVE_OPTION)
      {
         // Save the selected directory so can be used again.
         lastOpenDirectory = dataFileChooser.getCurrentDirectory().toString();

         // Collect file name.
         fileName = dataFileChooser.getSelectedFile().getName();
         fileName = dataFileChooser.getCurrentDirectory() + LindyFrame_Utils.getFileSeparator() + fileName;

         // Try Loading the file.
         if (!fileName.equals(""))
         {
            final String dataFileName = fileName;
            
            Thread importActionThread = new Thread(new Runnable()
            {
               public void run()
               {
                  loadCSVData(dataFileName, true);
               }
            }, "MenuActionLisenter.importActionThread");
            importActionThread.start();
         }
         else
         {
            JOptionPane.showMessageDialog(null, resourceErrorFile, resourceTitleAlert,
                                          JOptionPane.ERROR_MESSAGE);
         }
      }
   }
   
   //==============================================================
   // Class method to allow the loading of data from a specified
   // file name. The data should be in CSV format the delimiter
   // should be set via the Ajqvue preferences. The first column
   // data is assummed to be the x-axis data, all subsequent
   // columns are plotting as series to that first column.
   //
   // Lay language. First column is the x-axis plotting data to
   //               the rest of the columns.
   //==============================================================

   private void loadCSVData(String fileName, boolean useStatusDialog)
   {
      // Class Method Instances.
      FileReader fileReader;
      BufferedReader bufferedReader;

      String chartType;
      DataChartSeries data_series;
      ArrayList<String> fields;

      String delimiter;
      String currentLine;
      int fileLineLength, fieldNumber, line;
      String[] lineContent;
      boolean processError;

      LindyFrame_ProgressBar csvImportProgressBar;

      // Setting up.
      chartType = plotPanel.getChartType();
      
      if (chartType.equals(ScatterPlot.LINE_CHART) || chartType.equals(ScatterPlot.XYAREA_CHART)
          || chartType.equals(ScatterPlot.SCATTER_CHART) || chartType.equals(ScatterPlot.XYBAR_CHART))
         data_series = new LineChartSeries("Series "
                                           + (plotPanel.getChartsPanel().getSeriesCount() + 1));
      /*
      else if (chartType.equals(ScatterPlot.CATEGORY_LINE_CHART) || chartType.equals(ScatterPlot.BAR_CHART)
               || chartType.equals(ScatterPlot.AREA_CHART) || chartType.equals(ScatterPlot.PIE_CHART))
         data_series = new CategoryChartSeries("Series "
                                               + (plotPanel.getChartsPanel().getSeriesCount() + 1));
      else if (chartType.equals(ScatterPlot.BUBBLE_CHART))
         data_series = new BubbleChartSeries("Series " 
                                             + (plotPanel.getChartsPanel().getSeriesCount() + 1));
      */
      else
         return;
      
      fields = new ArrayList<String>();

      fileReader = null;
      bufferedReader = null;
      csvImportProgressBar = null;
      fileLineLength = 0;
      delimiter = ScatterPlot.DELIMITER;
      line = 0;

      processError = false;
      plotPanel.setDisableActions(true);
      
      csvImportProgressBar = new LindyFrame_ProgressBar("CSV Import To: Graph");

      // Begin the processing of the input CSV file by reading
      // each line and separating field data. Expectation
      // being that the first line will hold the field names
      // thereafter data.

      try
      {
         // Setting file reader & progress bar.
         fileReader = new FileReader(fileName);
         bufferedReader = new BufferedReader(fileReader);

         while ((currentLine = bufferedReader.readLine()) != null)
            fileLineLength++;

         csvImportProgressBar.setTaskLength(fileLineLength);
         csvImportProgressBar.pack();
         csvImportProgressBar.center();
         csvImportProgressBar.setVisible(useStatusDialog);

         // Beginning processing the input file for insertions
         // into the plot.

         bufferedReader.close();
         fileReader = new FileReader(fileName);
         bufferedReader = new BufferedReader(fileReader);

         fieldNumber = 0;
         line = 1;
         
         // Collect the number of fields, columns of data,
         // and create the chart series.
         
         bufferedReader.mark(5);
         currentLine = bufferedReader.readLine();
         if (currentLine != null)
            lineContent = currentLine.split(delimiter, 0);
         else
            throw new IOException();
            
         fieldNumber = lineContent.length;
         
         // Fields names stored & set.
         for (int i = 0; i < fieldNumber; i++)
         {
            fields.add(lineContent[i]);
            // System.out.print(lineContent[i] + " ");
         }
         // System.out.println();
         
         if (fieldNumber == 1)
         {
            plotPanel.getChartsPanel().setChartXAxisLabel("Line Index");
            plotPanel.getChartsPanel().setChartYAxisLabel(fields.get(0));
         }
         else
         {
            plotPanel.getChartsPanel().setChartXAxisLabel(fields.get(0));
            plotPanel.getChartsPanel().setChartYAxisLabel(fields.get(1));
         }
          
         // Begin processing the i/o to fill the chart.
         
         while ((currentLine = bufferedReader.readLine()) != null
                && !csvImportProgressBar.isCanceled())
         {
            // System.out.println("MenuActionListener loadCSVData() currentLine: "
            //                     + line + ":" + currentLine);

            // Data Processing.
            lineContent = currentLine.split(delimiter, fieldNumber);
            
            // Check
            if (fieldNumber != lineContent.length)
            {
               showError(line + 1, "");
               csvImportProgressBar.setCanceled(true);
               processError = true;
               continue;
            }
            
            // Single data column so just plot x-axis as
            // current data line, going to get a time
            // series plot.
            if (fieldNumber == 1)
            {
               String temp = lineContent[0];
               
               lineContent = new String[2];
               lineContent[0] = String.valueOf(line - 1);
               lineContent[1] = temp;  
            }
            
            // Just set lineContent with no data to zero.
            for (int i = 1; i < fieldNumber; i++)
               if (lineContent[i].isEmpty())
                  lineContent[i] = "0.0";
               
            try
            {
               if (fieldNumber >= 1)
               {
                  // <Number, Number>
                  if (chartType.equals(ScatterPlot.LINE_CHART)
                      || chartType.equals(ScatterPlot.XYAREA_CHART)
                      || chartType.equals(ScatterPlot.SCATTER_CHART)
                      || chartType.equalsIgnoreCase(ScatterPlot.XYBAR_CHART))
                     data_series.addDataPoint(Double.valueOf(lineContent[0]),
                                              Double.valueOf(lineContent[1]));
                  // <Number, Number, Number>
                  else if (chartType.equals(ScatterPlot.BUBBLE_CHART))
                  {
                     if (fieldNumber <= 2)
                        data_series.addDataPoint(Double.valueOf(lineContent[0]),
                                                 Double.valueOf(lineContent[1]),
                                                 Double.valueOf(lineContent[1]));
                     else
                        data_series.addDataPoint(Double.valueOf(lineContent[0]),
                                                 Double.valueOf(lineContent[1]),
                                                 Double.valueOf(lineContent[2]));      
                  }
                  // <String, Number>
                  else if (chartType.equals(ScatterPlot.CATEGORY_LINE_CHART)
                           || chartType.equals(ScatterPlot.BAR_CHART)
                           || chartType.equals(ScatterPlot.AREA_CHART)
                           || chartType.equals(ScatterPlot.PIE_CHART))
                     data_series.addDataPoint(lineContent[0],
                                              Double.valueOf(lineContent[1]));
                  // <Number, String>
                  else
                     data_series.addDataPoint(Double.valueOf(lineContent[0]), lineContent[1]);     
               }
            }
            catch (NumberFormatException | SeriesException e)
            {
               showError(line + 1, e.getMessage());
               
               if (LindyFrame.getDebug())
                  System.out.println("MenuActionListener loadCSVData() " + e.getMessage());
                  
               csvImportProgressBar.setCanceled(true);
               processError = true;
            }
            
            if (!processError)
               csvImportProgressBar.setCurrentValue(line++);
         }
         
         // Set the title & data to the chart.
         if (!processError)
         {
            (plotPanel.getChartsPanel()).addSeries(data_series);
            
            if (fileName.indexOf(LindyFrame_Utils.getFileSeparator()) != -1)
            {
               int index = fileName.lastIndexOf(LindyFrame_Utils.getFileSeparator());
               plotPanel.getChartsPanel().setChartTitle(fileName.substring(index + 1));
            }
            else
               plotPanel.getChartsPanel().setChartTitle(fileName);
         }
         else
         {
            data_series.clear();
            Runtime.getRuntime().gc();
         }
         
         // Clean up.
         csvImportProgressBar.dispose();
      }
      catch (IOException e)
      {
         csvImportProgressBar.dispose();

         JOptionPane
               .showMessageDialog(null, "Unable to Read Input File!", resourceTitleAlert,
                                  JOptionPane.ERROR_MESSAGE);
      }
      finally
      {
         try
         {
            if (bufferedReader != null)
               bufferedReader.close();
         }
         catch (IOException ioe)
         {
            if (LindyFrame.getDebug())
               System.out.println("MenuActionListener loadCSVData() Failed to Close BufferedReader. "
                                  + ioe);
         }
         finally
         {
            try
            {
               if (fileReader != null)
                  fileReader.close();
            }
            catch (IOException ioe)
            {
               if (LindyFrame.getDebug())
                  System.out.println("MenuActionListener loadCSVData() Failed to Close FileReader. " + ioe);
            }
         }
         plotPanel.setDisableActions(false);
      }
   }
   
   //==============================================================
   // Class Method to show and option pane showing that an error
   // has occured and at what line number.
   //==============================================================
   
   private void showError(int line, String error)
   {
      JOptionPane.showMessageDialog(null, "Error Line: " + line + "\n" + error,
         resourceTitleAlert, JOptionPane.ERROR_MESSAGE);
   }
   
   //==============================================================
   // Inner Class to handle the selection extensions for the
   // various diaglog file choosers.
   //==============================================================
   
   static class CustomFileFilter extends FileFilter
   {
      // Class Intances
      String[] extensions;
      
      public CustomFileFilter(String[] extensions)
      {
         this.extensions = extensions;
      }
      
      public boolean accept(File file)
      {
         // All Directories
         if (file.isDirectory())
            return true;
         
         for (String ext : extensions)
            if (ext.equals("*") || file.getName().toLowerCase().endsWith(ext))
               return true;
            
         // Nope.
         return false;
      }
      
      public String getDescription()
      {
         StringBuilder validExtension = new StringBuilder();
         
         for (String ext : extensions)
            validExtension.append("*." + ext + ", ");
         
         if (validExtension.toString().endsWith(", "))
            validExtension.delete((validExtension.length() - 2), validExtension.length());
         
         return validExtension.toString() + " Files";
      } 
   }
}