content: /*
   * Copyright (c) 2010 Pentaho Corporation.  All rights reserved. 
   * This software was developed by Pentaho Corporation and is provided under the terms 
   * of the GNU Lesser General Public License, Version 2.1. You may not use 
   * this file except in compliance with the license. If you need a copy of the license, 
   * please go to http://www.gnu.org/licenses/lgpl-2.1.txt. The Original Code is Time Series 
   * Forecasting.  The Initial Developer is Pentaho Corporation.
   *
   * Software distributed under the GNU Lesser Public License is distributed on an "AS IS" 
   * basis, WITHOUT WARRANTY OF ANY KIND, either express or  implied. Please refer to 
   * the license for the specific language governing your rights and limitations.
   */
  
  /*
   *    AdvancedConfigPanel.java
   *    Copyright (C) 2010 Pentaho Corporation
   */
  
  package weka.classifiers.timeseries.gui;
  
  import java.awt.BorderLayout;
  import java.awt.Dimension;
  import java.awt.GridLayout;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
  import java.io.BufferedReader;
  import java.io.BufferedWriter;
  import java.io.File;
  import java.io.FileReader;
  import java.io.FileWriter;
  import java.io.IOException;
  import java.io.PrintWriter;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Vector;
  
  import javax.swing.BorderFactory;
  import javax.swing.DefaultComboBoxModel;
  import javax.swing.JButton;
  import javax.swing.JCheckBox;
  import javax.swing.JComboBox;
  import javax.swing.JFileChooser;
  import javax.swing.JLabel;
  import javax.swing.JOptionPane;
  import javax.swing.JPanel;
  import javax.swing.JSpinner;
  import javax.swing.JTabbedPane;
  import javax.swing.JTable;
  import javax.swing.JTextField;
  import javax.swing.SpinnerNumberModel;
  import javax.swing.event.ListSelectionEvent;
  import javax.swing.event.ListSelectionListener;
  import javax.swing.filechooser.FileFilter;
  import javax.swing.table.DefaultTableModel;
  
  import weka.classifiers.Classifier;
  import weka.classifiers.timeseries.WekaForecaster;
  import weka.classifiers.timeseries.core.CustomPeriodicTest;
  import weka.classifiers.timeseries.core.OverlayForecaster;
  import weka.classifiers.timeseries.core.TSLagMaker;
  import weka.classifiers.timeseries.eval.TSEvalModule;
  import weka.classifiers.timeseries.eval.TSEvaluation;
  import weka.core.Attribute;
  import weka.core.Capabilities;
  import weka.core.Capabilities.Capability;
  import weka.core.Instances;
  import weka.core.Range;
  import weka.core.SerializedObject;
  import weka.gui.AttributeSelectionPanel;
  import weka.gui.ExtensionFileFilter;
  import weka.gui.GenericObjectEditor;
  import weka.gui.PropertyPanel;
  
  public class AdvancedConfigPanel extends JPanel {
    
    /**
     * For serialization
     */
    private static final long serialVersionUID = 5465960083615138964L;
  
    /** The training instances to operate on */
    protected Instances m_instances;
    
    /** A reference to the simple config panel */
    protected SimpleConfigPanel m_simpleConfig;
    
    /** A tabbed pane to hold the individual advanced configuration panels */
    JTabbedPane m_configHolder = new JTabbedPane();
    
    /** Editor for selecting and configuring the base algorithm */
    protected GenericObjectEditor m_baseLearnerEditor = 
      new GenericObjectEditor();
    
    /** Property panel for editing base algorithm */
    protected PropertyPanel m_baseLearnerPanel = 
      new PropertyPanel(m_baseLearnerEditor);
    
    /** Custom lags checkbox */
    protected JCheckBox m_useCustomLags = 
      new JCheckBox("Use custom lag lengths");
    
    /** Min lag spinner */
    protected JSpinner m_minLagSpinner;
    
    /** Max lag spinner */
    protected JSpinner m_maxLagSpinner;
    
    /** Adjust for variance check box */
    protected JCheckBox m_adjustForVarianceCheckBox = 
      new JCheckBox("Adjust for variance");
    
    /** Field for fine tuning the lags that are created by specifying a range */
    protected JTextField m_fineTuneLagsField = new JTextField();
    
    /** Check box for averaging consecutive long lags */
    protected JCheckBox m_averageLongLags =
      new JCheckBox("Average consecutive long lags");
    
    /** Spinner for selecting which lag to start averaging from */
    protected JSpinner m_averageLagsAfter;
    
    /** Spinner for selecting how many consecutive long lags to average into one new field */
    protected JSpinner m_numConsecutiveToAverage;
    
    /** Data structure for holding the field names for date-derived fields */
    protected Instances m_dateDerivedPeriodicsHeader;
    
    /** Custom date-derived fields check box */
    protected JCheckBox m_customizeDateDerivedPeriodics = 
      new JCheckBox("Customize");
    
    /** Edit button for custom date-derived fields */
    protected JButton m_editCustomPeriodicBut = new JButton("Edit");
    
    /** Add button for custom date-derived fields */
    protected JButton m_addCustomPeriodicBut = new JButton("New");
    
    /** Delete button for custom date-derived fields */
    protected JButton m_deleteCustomPeriodicBut = new JButton("Delete");
  
    /** Save button for saving date-derived periodics to a file */
    protected JButton m_savePeriodicBut = new JButton("Save");
    
    /** Load button for loading pre-defined date-derived periodics from a file */
    protected JButton m_loadPeriodicBut = new JButton("Load");
    
    /** File chooser for saving/loading date-derived periodics */
    protected JFileChooser m_fileChooser;
    
    /** Map of custom date-derived fields */
    protected Map<String, ArrayList<CustomPeriodicTest>> m_customPeriodics = 
      new HashMap<String, ArrayList<CustomPeriodicTest>>();
    
    protected static final int NUM_PREDEFINED_PERIODICS = 7;
    
    /**
     * Inner class extending AttributeSelectionPanel that adds methods
     * to get the encapsulated JTable and clear the current table model.
     */
    protected class AttributeSelectionPanelExtended extends 
      AttributeSelectionPanel {
      
      /** For serialization */
      private static final long serialVersionUID = -6863797282164060690L;
  
      public AttributeSelectionPanelExtended(boolean include, boolean remove,
          boolean invert, boolean pattern) {
        super(include, remove, invert, pattern);
      }
      
      public JTable getTable() {
        return m_Table;
      }
      
      public void clearTableModel() {
        m_Model = null;
        m_Table.setModel(new DefaultTableModel());
        m_Table.revalidate();
        m_Table.repaint();
      }
    }
    
    /** Panel for holding and selecting date-derived periodic attributes */
    protected AttributeSelectionPanelExtended m_dateDerivedPeriodicSelector = 
      new AttributeSelectionPanelExtended(false, false, false, false);
    
    /** Non date-based primary periodic stuff */
    protected JComboBox m_primaryPeriodicCombo = new JComboBox();
    
    /** Panel for holding and selecting overlay fields */
    protected AttributeSelectionPanelExtended m_overlaySelector = 
      new AttributeSelectionPanelExtended(true, true, true, true);
    
    /** Check box for overlay fields */
    protected JCheckBox m_useOverlayData = new JCheckBox("Use overlay data");
    
    /** Holds the structure of overlay fields */
    protected Instances m_overlayHeader;
    
    /** Holds the names of the currently selected evaluation modules */
    protected Instances m_evaluationModsHeader;
    
    /** Panel for holding and selecting evaluation modules */
    protected AttributeSelectionPanel m_evaluationMetrics = 
      new AttributeSelectionPanel(false, false, false, false);
    
    /** Perform training set evaluation check box */
    protected JCheckBox m_trainingCheckBox = new JCheckBox("Evaluate on training");
    
    /** Perform hold-out evaluation check box */
    protected JCheckBox m_holdoutCheckBox = new JCheckBox("Evaluate on held out training");
    
    /** Text field for setting the size of the hold-out set */
    protected JTextField m_holdoutSize = new JTextField();
    
    /** show the separate test set checkbox and button? */
    protected boolean m_allowSeparateTestSet = true;
    
    /** Separate test set check box */
    protected JCheckBox m_separateTestSetCheckBox = 
      new JCheckBox("Evaluate on a separate test set");
    
    /** Button for selecting a separate test set */
    protected JButton m_testSetBut = new JButton("Separate test set");
    
    /** Check box for outputting predictions */
    protected JCheckBox m_outputPredsCheckBox = 
      new JCheckBox("Output predictions at step");
    
    /** Combo box for selecting which target to output predictions for */
    protected JComboBox m_outputPredsCombo = new JComboBox();
    protected JLabel m_outputPredsComboLabel = new JLabel("Target to output", JLabel.RIGHT);
    
    /** Spinner for selecting which step to output */
    protected JSpinner m_outputStepSpinner;
    protected JLabel m_outputStepLabel = new JLabel("Step to output", JLabel.RIGHT);
    
    /** Output future predictions check box */
    protected JCheckBox m_outputFutureCheckBox = 
      new JCheckBox("Output future predictions beyond end of series ");
    
    /** Graph predictions at step check box */
    protected JCheckBox m_graphPredsAtStepCheckBox =
      new JCheckBox("Graph predictions at step");
    
    /** Spinner for selecting which step to graph predictions at */
    protected JSpinner m_graphPredsAtStepSpinner;
    protected JLabel m_stepLab = new JLabel("Steps to graph");
    
    /** Graph targets for specific step */
    protected JCheckBox m_graphTargetForStepsCheckBox =
      new JCheckBox("Graph target at steps:");  
    
    /** Graph target at steps combo box */
    protected JComboBox m_graphTargetAtStepsCombo = new JComboBox();
    protected JLabel m_targetComboLabel = new JLabel("Target to graph", JLabel.RIGHT);
    
    /** Text field for specifying a range of steps to graph for the selected target */
    protected JTextField m_stepRange = new JTextField("1");
    
    /** Graph future predictions check box */
    protected JCheckBox m_graphFutureCheckBox =
      new JCheckBox("Graph future predictions beyond end of series");
    
    static {
      GenericObjectEditor.registerEditors();
    }
    
    /**
     * Get a title for displaying in the tab that will hold this panel
     * 
     * @return a title for this configuration panel
     */
    public String getTabTitle() {
      return "Advanced configuration";
    }
    
    /**
     * Get the tool tip for this configuration panel
     * 
     * @return the tool tip for this configuration panel
     */
    public String getTabTitleToolTip() {
      return "Advanced configuration";
    }
    
    /**
     * Get the underlying Weka classifier that will be used to make the
     * predictions
     * 
     * @return the underlying Weka classifier
     */
    public Classifier getBaseClassifier() {
      return (Classifier)m_baseLearnerEditor.getValue();
    }
    
    /**
     * Set the instances that will be used in the training and evaluation of
     * the forecaster 
     * 
     * @param train the instances to use in the training and evaluation process
     */
    public void setInstances(Instances train) {
      m_instances = train;
      updatePanel();
    }
    
    /**
     * Set the enabled/disabled status of date-derived periodic panel
     * and its associated widgets.
     * 
     * @param s true if date-derived periodicis is to be enabled
     */
    public void enableDateDerivedPeriodics(boolean s) {
      m_customizeDateDerivedPeriodics.setEnabled(s);
      if (!s) {
        m_customizeDateDerivedPeriodics.setSelected(false);
        m_addCustomPeriodicBut.setEnabled(false);
        m_editCustomPeriodicBut.setEnabled(false);
        m_deleteCustomPeriodicBut.setEnabled(false);
        m_savePeriodicBut.setEnabled(false);
        m_loadPeriodicBut.setEnabled(false);
      }
    }
    
    /**
     * Returns true if date-derived periodics is enabled
     * 
     * @return if date-derived periodics is enabled.
     */
    public boolean isEnabledCustomizeDateDerivedPeriodics() {
      return m_customizeDateDerivedPeriodics.isEnabled();
    }
    
    /**
     * Returns true if the date-derived periodics check box is selected.
     * 
     * @return true if the date-derived periodics check box is selected.
     */
    public boolean getCustomizeDateDerivedPeriodics() {
      return m_customizeDateDerivedPeriodics.isSelected();
    }
    
    /**
     * Returns true if the user has opted to customize the lags.
     * 
     * @return true if custom lags are in use.
     */
    public boolean isUsingCustomLags() {
      return m_useCustomLags.isSelected();
    }
    
    /**
     * Gets the size of the holdout set.
     * 
     * @return the size of the holdout set or 0 if no holdout set is being used.
     */
    public double getHoldoutSetSize() {
      double result = 0;
      
      if (m_holdoutCheckBox.isSelected()) {
        try {
          result = Double.parseDouble(m_holdoutSize.getText());
        } catch (NumberFormatException ex) {}
      }
      
      return result;
    }
    
    /**
     * Returns true if the user has opted to output future predictions
     * 
     * @return if the user has opted to output future predictions
     */
    public boolean getOutputFuturePredictions() {
      return m_outputFutureCheckBox.isSelected();
    }
    
    /**
     * Returns at which step to output predictions. Returns
     * 0 if the user has not opted to output predictions.
     * 
     * @return step at which to output predictions or 0 if
     * no predictions are to be output.
     */
    public int getOutputPredictionsAtStep() {
      if (!m_outputPredsCheckBox.isSelected()) {
        return 0;
      }
      
      return ((SpinnerNumberModel)m_outputStepSpinner.
          getModel()).getNumber().intValue();
    }
    
    /**
     * Returns true if the user has opted to graph a target at
     * specified steps
     * 
     * @return true if the user has opted to graph a target at
     * specified steps
     */
    public boolean getGraphTargetForSteps() {
      return m_graphTargetForStepsCheckBox.isSelected();
    }
    
    /**
     * Return the target that is to be graphed at various
     * steps
     * 
     * @return the target that is to be graphed at specified steps or
     * null if the user has not opted to graph a targert at specified
     * steps
     */
    public String getGraphTargetForStepsTarget() {
      if (!getGraphTargetForSteps()) {
        return null;
      }
      
      return m_graphTargetAtStepsCombo.getSelectedItem().toString();
    }
    
    /**
     * If the user has opted to graph a target a various steps, then this
     * method returns the list of steps that they have selected.
     * 
     * @return the list of steps at which to graph a target, or null if the
     * user has not opted to graph a target at various steps.
     */
    public List<Integer> getGraphTargetForStepsStepList() {
      String rng = m_stepRange.getText();
      
      if (rng == null || rng.length() == 0) {
        return null;
      }
      
      Range range = new Range(rng);
      range.setUpper(m_simpleConfig.getHorizonValue());
      int[] indices = range.getSelection();
      
      List<Integer> rangeList = new ArrayList<Integer>();
      for (int i : indices) {
        rangeList.add((i + 1));
      }
      
      return rangeList;
    }
    
    /**
     * Get the selected target to output predictions for. Returns
     * null if the user has not opted to output predictions.
     * 
     * @return the name of the target to output predictions for or
     * null if no predictions are to be output.
     */
    public String getOutputPredictionsTarget() {
      if (!m_outputPredsCheckBox.isSelected()) {
        return null;
      }
      
      return m_outputPredsCombo.getSelectedItem().toString();
    }
    
    /**
     * Get the step number to graph all the targets at.
     * 
     * @return the step number to graph all the targets at, or
     * 0 if the user has not opted to graph all the targets.
     */
    public int getGraphPredictionsAtStep() {
      if (!m_graphPredsAtStepCheckBox.isSelected()) {
        return 0;
      }
      
      return ((SpinnerNumberModel)m_graphPredsAtStepSpinner.
          getModel()).getNumber().intValue();
    }
    
    /**
     * Returns true if the user has opted to graph future predictions
     * 
     * @return true if the user has opted graph future predictions
     */
    public boolean getGraphFuturePredictions() {
      return m_graphFutureCheckBox.isSelected();
    }
    
    /**
     * Constructor
     * 
     * @param s a reference to the simple configuration panel
     * @param allowSeparateTestSet true if the separate test set button
     * is to be displayed
     */
    public AdvancedConfigPanel(SimpleConfigPanel s, 
        boolean allowSeparateTestSet) {
      
      m_allowSeparateTestSet = allowSeparateTestSet;
      m_simpleConfig = s;
  
      setLayout(new BorderLayout());
      layoutLearnerPanel();
      layoutLagPanel();        
      layoutDateDerivedPeriodicPanel();
      layoutOverlayPanel();
      layoutEvaluationPanel();
      layoutOutputPanel();
  
      add(m_configHolder, BorderLayout.CENTER);
    }
    
    /**
     * Constructor
     * 
     * @param s a reference to the simple configuration panel
     */
    public AdvancedConfigPanel(SimpleConfigPanel s) {
      this(s, true);
    }
    
    /**
     * Updates various enabled/selected status of widgets based on the current
     * configuration
     */
    public void updatePanel() {
      updateDateDerivedPanel();
      updatePrimaryPeriodic();
      updateOutputPanel();
      updateOverlayPanel();
    }
    
    /**
     * Updates the status/selection of widgets on the overlay panel
     */
    public void updateOverlayPanel() {
      Instances newI = createAvailableOverlayList();
      if (newI == null) {
        m_useOverlayData.setSelected(false);
        m_useOverlayData.setEnabled(false);
        m_overlaySelector.clearTableModel();
      } else {
        m_useOverlayData.setEnabled(true);
        if (m_useOverlayData.isSelected()) {
          if (m_overlayHeader == null || 
              !newI.equalHeaders(m_overlayHeader)) {
            m_overlayHeader = newI;
            m_overlaySelector.setInstances(newI);
          }
        }
      }
    }
    
    private Instances createAvailableOverlayList() {
      
      String ppfn = m_primaryPeriodicCombo.getSelectedItem().toString();
      Instances result = null;
      if (m_simpleConfig.m_targetHeader != null) {
        ArrayList<Attribute> availableAtts = new ArrayList<Attribute>();
        
        int[] selectedTargets = m_simpleConfig.m_targetPanel.getSelectedAttributes();
        for (int i = 0; i < m_instances.numAttributes(); i++) {
          // skip all date attributes
          if (m_instances.attribute(i).isDate()) {
            continue;
          }
          
          // skip any primary periodic
          if (m_instances.attribute(i).name().equals(ppfn)) {
            continue;
          }
          
          if (m_simpleConfig.m_targetHeader.
              attribute(m_instances.attribute(i).name()) != null) {
            // now need to check whether it's been selected as a target
            int indexToCheck = m_simpleConfig.m_targetHeader.
              attribute(m_instances.attribute(i).name()).index();
            
            boolean ok = true;
            for (int j = 0; j < selectedTargets.length; j++) {
              if (indexToCheck == selectedTargets[j]) {
                ok = false; // can't use this attribute
                break;
              }
            }
            
            if (ok) {
              availableAtts.add(new Attribute(m_instances.attribute(i).name()));
            }
          } else {
            // this is available
            availableAtts.add(new Attribute(m_instances.attribute(i).name()));
          }
        }
        if (availableAtts.size() > 0) {
          result = new Instances("Overlay",availableAtts, 1);
        }
      }
      
      return result;
    }
    
    /**
     * Updates the status/selection of various widgets on the output panel
     */
    public void updateOutputPanel() {
      if (m_simpleConfig.m_targetHeader != null) {
        // configure the target combos
        Vector<String> candidates = new Vector<String>();
        for (int i = 0; i < m_simpleConfig.m_targetHeader.numAttributes(); i++) {
          candidates.add(m_simpleConfig.m_targetHeader.attribute(i).name());
        }
        
        m_graphTargetAtStepsCombo.setModel(new DefaultComboBoxModel(candidates));
        m_outputPredsCombo.setModel(new DefaultComboBoxModel(candidates));
      }
    }
    
    /**
     * Updates the status/selection of various widgets on the date-derived
     * periodics panel
     */
    protected void updateDateDerivedPanel() {
      if (m_simpleConfig.m_timeStampCombo.getSelectedItem() == null) {
        return;
      }
      String selectedTimeStamp = m_simpleConfig.m_timeStampCombo.getSelectedItem().toString();
      if (selectedTimeStamp.equals("<None>") || 
          selectedTimeStamp.equals("<Use and artificial time stamp>")) {
        m_customizeDateDerivedPeriodics.setSelected(false);
        m_customizeDateDerivedPeriodics.setEnabled(false);
        m_editCustomPeriodicBut.setEnabled(false);
        m_addCustomPeriodicBut.setEnabled(false);
        m_deleteCustomPeriodicBut.setEnabled(false);
        m_savePeriodicBut.setEnabled(false);
        m_loadPeriodicBut.setEnabled(false);
      } else if (m_instances != null) {
        Attribute timeStampAtt = m_instances.attribute(selectedTimeStamp);
        if (timeStampAtt != null && timeStampAtt.isDate()) {
          m_customizeDateDerivedPeriodics.setEnabled(true);
        } else {
          m_customizeDateDerivedPeriodics.setSelected(false);
          m_customizeDateDerivedPeriodics.setEnabled(false);
          m_editCustomPeriodicBut.setEnabled(false);
          m_addCustomPeriodicBut.setEnabled(false);
          m_deleteCustomPeriodicBut.setEnabled(false);
          m_savePeriodicBut.setEnabled(false);
          m_loadPeriodicBut.setEnabled(false);
        }
      } else {
        m_customizeDateDerivedPeriodics.setSelected(false);
        m_customizeDateDerivedPeriodics.setEnabled(false);
        m_editCustomPeriodicBut.setEnabled(false);
        m_addCustomPeriodicBut.setEnabled(false);
        m_deleteCustomPeriodicBut.setEnabled(false);
        m_savePeriodicBut.setEnabled(false);
        m_loadPeriodicBut.setEnabled(false);
      }
    }
    
    /**
     * Sets up the primary periodic drop-down box
     */
    protected void updatePrimaryPeriodic() {
      Vector<String> entries = new Vector<String>();
      
      entries.add("<None>");
      if (m_instances != null) {
        for (int i = 0; i < m_instances.numAttributes(); i++) {
          if (m_instances.attribute(i).isNominal()) {
            entries.add(m_instances.attribute(i).name());
          }
        }
      }
      m_primaryPeriodicCombo.setModel(new DefaultComboBoxModel(entries));
    }
    
    /**
     * Updates the status of eval and output widgets
     */
    protected void updateEvalAndOutputEnabledStatus() {
      boolean enable = (m_trainingCheckBox.isSelected() || 
          m_holdoutCheckBox.isSelected() ||
          m_separateTestSetCheckBox.isSelected()); 
  
      // enable/disable and deselect all prediction and graphing widgets
      m_outputPredsCheckBox.setEnabled(enable);
      
      //m_outputFutureCheckBox.setEnabled(enable);
      
      m_graphPredsAtStepCheckBox.setEnabled(enable);
      //m_outputStepSpinner.setEnabled(enable);
      //m_graphPredsAtStepSpinner.setEnabled(enable);
      m_graphTargetForStepsCheckBox.setEnabled(enable);
      
      //m_graphFutureCheckBox.setEnabled(enable);
      
      if (!enable) {
        m_outputPredsCheckBox.setSelected(false);
      //  m_outputFutureCheckBox.setSelected(false);
        m_graphPredsAtStepCheckBox.setSelected(false);
        m_graphTargetForStepsCheckBox.setSelected(false);
        
        
        //m_graphFutureCheckBox.setSelected(false);
      }
      
      boolean enabled = m_graphTargetForStepsCheckBox.isSelected();
      m_stepLab.setEnabled(enabled); m_stepRange.setEnabled(enabled);
      m_targetComboLabel.setEnabled(enabled); m_graphTargetAtStepsCombo.setEnabled(enabled);
      m_graphPredsAtStepSpinner.setEnabled(enabled);
      
      enabled = m_outputPredsCheckBox.isSelected();
      m_outputStepLabel.setEnabled(enabled); m_outputPredsCombo.setEnabled(enabled);
      m_outputPredsComboLabel.setEnabled(enable); m_outputStepSpinner.setEnabled(enabled);
  
          
      m_testSetBut.setEnabled(!m_holdoutCheckBox.isSelected());
      m_separateTestSetCheckBox.setEnabled(!m_holdoutCheckBox.isSelected());
      m_holdoutSize.setEnabled(m_holdoutCheckBox.isSelected());
      if (m_holdoutCheckBox.isSelected()) {
        m_separateTestSetCheckBox.setSelected(false);
      }
    }
    
    /**
     * Layout the evaluation panel
     */
    protected void layoutEvaluationPanel() {
      JPanel basePanel = new JPanel();
      basePanel.setLayout(new BorderLayout());
      /*basePanel.setBorder(BorderFactory.
          createTitledBorder("Evaluation")); */
      
      JPanel base1 = new JPanel();
      base1.setLayout(new BorderLayout());
      base1.add(m_evaluationMetrics, BorderLayout.CENTER);
      List<TSEvalModule> evalModulesL = TSEvalModule.getModuleList();
      ArrayList<Attribute> atts = new ArrayList<Attribute>();
      int numMods = 0;
      for (TSEvalModule s : evalModulesL) {
        if (!(s.getEvalName().equals("Error"))) {
          atts.add(new Attribute(s.toString()));
          numMods++;
        }
      }
      Instances modInsts = new Instances("Eval modules", atts, 1);
      m_evaluationModsHeader = modInsts;
      m_evaluationMetrics.setInstances(modInsts);
      boolean[] selected = new boolean[numMods];
      selected[0] = true; selected[2] = true;
      try {
        m_evaluationMetrics.setSelectedAttributes(selected);
      } catch (Exception ex) {}
      m_evaluationMetrics.setPreferredScrollableViewportSize(new Dimension(260,80));
      m_evaluationMetrics.setBorder(BorderFactory.createTitledBorder("Metrics"));
      
      JPanel temp1 = new JPanel();
      temp1.setLayout(new BorderLayout());
      temp1.setBorder(BorderFactory.createTitledBorder("Test options"));
      temp1.add(m_trainingCheckBox, BorderLayout.NORTH);
      //m_trainingCheckBox.setSelected(true);
      m_trainingCheckBox.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          updateEvalAndOutputEnabledStatus();
          if (m_simpleConfig != null) {
            m_simpleConfig.m_performEvaluation.
              setSelected(m_trainingCheckBox.isSelected() ||
                  m_holdoutCheckBox.isSelected());
          }
        }
      });
      
      JPanel temp2 = new JPanel();
      temp2.setLayout(new BorderLayout());
      temp2.add(m_holdoutCheckBox, BorderLayout.WEST);
      temp2.add(m_holdoutSize, BorderLayout.EAST);
      m_holdoutSize.setEnabled(false);
      
      int width = m_minLagSpinner.getPreferredSize().width;
      int height = m_minLagSpinner.getPreferredSize().height;
      m_holdoutSize.setPreferredSize(new Dimension(width * 1, height));
      m_holdoutSize.setMinimumSize(new Dimension(width * 1, height));
      m_holdoutSize.setToolTipText("Number of instances (value >=1) or percentage " +
      		"(value < 1) to hold out from the end of the data");
      m_holdoutSize.setText("0.3");
      m_holdoutCheckBox.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          updateEvalAndOutputEnabledStatus();
          if (m_simpleConfig != null) {
            m_simpleConfig.m_performEvaluation.
              setSelected(m_trainingCheckBox.isSelected() ||
                  m_holdoutCheckBox.isSelected());
          }
        }
      });
      temp1.add(temp2, BorderLayout.CENTER);
      
      if (m_allowSeparateTestSet) {
        JPanel checkAndButHolder = new JPanel();
        checkAndButHolder.setLayout(new BorderLayout());
        checkAndButHolder.add(m_separateTestSetCheckBox, BorderLayout.NORTH);
        checkAndButHolder.add(m_testSetBut, BorderLayout.SOUTH);
        temp1.add(checkAndButHolder, BorderLayout.SOUTH);
        m_separateTestSetCheckBox.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            updateEvalAndOutputEnabledStatus();
          }
        });
      }
                  
      JPanel temp3 = new JPanel();
      temp3.setLayout(new BorderLayout());
      temp3.add(temp1, BorderLayout.NORTH);
      base1.add(temp3, BorderLayout.EAST);
      basePanel.add(base1, BorderLayout.NORTH);
      //basePanel.add(outputOptsHolder, BorderLayout.EAST);
      
      
      
      m_testSetBut.setToolTipText("Evaluate on a separate test set");
      m_testSetBut.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          // TODO
        }
      });
      
      //holder.add(basePanel, BorderLayout.EAST);
      m_configHolder.addTab("Evaluation", null, basePanel, "Evaluation options");
    }
    
    /**
     * Layout the output panel
     */
    protected void layoutOutputPanel() {
      JPanel outputOptsHolder = new JPanel();
      outputOptsHolder.setLayout(new GridLayout(1,2));    
  
      JPanel textOutHolder = new JPanel();
      textOutHolder.setLayout(new BorderLayout());
      textOutHolder.setBorder(BorderFactory.createTitledBorder("Output options"));
      
      SpinnerNumberModel snm = new SpinnerNumberModel();
      snm.setValue(1); snm.setMinimum(1);
      m_outputStepSpinner = new JSpinner(snm);
      Dimension spinD = m_outputStepSpinner.getPreferredSize();
      spinD = new Dimension((int)(spinD.getWidth() * 1.5), 
          (int)spinD.getHeight());
      m_outputStepSpinner.setPreferredSize(spinD);
      JPanel checkHolder = new JPanel();
      checkHolder.setLayout(new BorderLayout());
      checkHolder.add(m_outputPredsCheckBox, BorderLayout.WEST);
      //labAndSpinnerHolder.add(m_outputStepSpinner, BorderLayout.EAST);
      JPanel comboAndSpinnerHolder = new JPanel();
      comboAndSpinnerHolder.setLayout(new BorderLayout());
      
      JPanel combo1Holder = new JPanel(); combo1Holder.setLayout(new BorderLayout());
      combo1Holder.setBorder(BorderFactory.createEmptyBorder(0, 0, 1, 0));
      combo1Holder.add(m_outputPredsCombo, BorderLayout.EAST);
  
      combo1Holder.add(m_outputPredsComboLabel, BorderLayout.CENTER);
      
      comboAndSpinnerHolder.add(combo1Holder, BorderLayout.NORTH);
      
      JPanel spinnerHolder1 = new JPanel(); 
      spinnerHolder1.setLayout(new BorderLayout());
      spinnerHolder1.add(m_outputStepSpinner, BorderLayout.EAST);
      spinnerHolder1.add(m_outputStepLabel, BorderLayout.CENTER);
      
      JPanel spinnerHolder2 = new JPanel();
      spinnerHolder2.setLayout(new BorderLayout());
      spinnerHolder2.setBorder(BorderFactory.createEmptyBorder(1, 0, 0, 0));
      spinnerHolder2.add(spinnerHolder1, BorderLayout.NORTH);
      
      comboAndSpinnerHolder.add(spinnerHolder2, BorderLayout.CENTER);
      textOutHolder.add(comboAndSpinnerHolder, BorderLayout.CENTER);
      
      textOutHolder.add(checkHolder, BorderLayout.NORTH);
      m_outputStepSpinner.setEnabled(false);
      m_outputStepLabel.setEnabled(false);
      m_outputPredsCombo.setEnabled(false);
      m_outputPredsCheckBox.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          m_outputStepSpinner.setEnabled(m_outputPredsCheckBox.isSelected());
          m_outputStepLabel.setEnabled(m_outputPredsCheckBox.isSelected());
          m_outputPredsComboLabel.setEnabled(m_outputPredsCheckBox.isSelected());
          m_outputPredsCombo.setEnabled(m_outputPredsCheckBox.isSelected());
        }
      });
      
      JPanel temp1 = new JPanel();
      temp1.setLayout(new BorderLayout());
      temp1.add(m_outputFutureCheckBox, BorderLayout.NORTH);
      textOutHolder.add(temp1, BorderLayout.SOUTH);
      outputOptsHolder.add(textOutHolder);
      m_outputFutureCheckBox.setSelected(true);
      
      JPanel graphOutputHolder = new JPanel();
      graphOutputHolder.setLayout(new BorderLayout());
      graphOutputHolder.setBorder(BorderFactory.
          createTitledBorder("Graphing options"));
      
      outputOptsHolder.add(graphOutputHolder);
      
      snm = new SpinnerNumberModel();
      snm.setValue(1); snm.setMinimum(1);
      m_graphPredsAtStepSpinner = new JSpinner(snm);
      spinD = m_graphPredsAtStepSpinner.getPreferredSize();
      spinD = new Dimension((int)(spinD.getWidth() * 1.5), 
          (int)spinD.getHeight());
      m_graphPredsAtStepSpinner.setPreferredSize(spinD);
      JPanel labAndSpinnerHolder = new JPanel();
      labAndSpinnerHolder.setLayout(new BorderLayout());
      labAndSpinnerHolder.add(m_graphPredsAtStepCheckBox, BorderLayout.WEST);
      labAndSpinnerHolder.add(m_graphPredsAtStepSpinner, BorderLayout.EAST);
      graphOutputHolder.add(labAndSpinnerHolder, BorderLayout.NORTH);
      m_graphPredsAtStepCheckBox.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          m_graphPredsAtStepSpinner.
            setEnabled(m_graphPredsAtStepCheckBox.isSelected());
        }
      });
      m_graphPredsAtStepSpinner.setEnabled(false);
      
      JPanel comboAndRangeHolder = new JPanel();
      comboAndRangeHolder.setLayout(new BorderLayout());
      comboAndRangeHolder.add(m_graphTargetForStepsCheckBox, BorderLayout.NORTH);
      JPanel temp2 = new JPanel();
      temp2.setLayout(new BorderLayout());
      temp2.setBorder(BorderFactory.createEmptyBorder(0, 0, 1, 0));
      temp2.add(m_graphTargetAtStepsCombo, BorderLayout.EAST);
  
      temp2.add(m_targetComboLabel, BorderLayout.CENTER);
      JPanel temp3 = new JPanel();
      temp3.setLayout(new BorderLayout());
      temp3.add(temp2, BorderLayout.NORTH);
      
      JPanel textHolder = new JPanel();
      textHolder.setLayout(new BorderLayout());
      //textHolder.setBorder(BorderFactory.createEmptyBorder(1, 0, 0, 0));
      textHolder.add(m_stepRange, BorderLayout.EAST);
      m_stepRange.setPreferredSize(new Dimension((int)spinD.getWidth() * 2,
          (int)spinD.getHeight()));
      
      m_stepLab.setToolTipText("Comma separated list of step numbers to graph");
      m_stepRange.setToolTipText("Comma separated list of step numbers to graph");
      JPanel ll = new JPanel(); ll.setLayout(new BorderLayout());
      ll.add(m_stepLab, BorderLayout.EAST);    
  
      textHolder.add(ll, BorderLayout.CENTER);
      temp3.add(textHolder, BorderLayout.CENTER);
  
      JPanel ll2 = new JPanel(); ll2.setLayout(new BorderLayout());
      ll2.add(temp3, BorderLayout.NORTH);
      comboAndRangeHolder.add(ll2, BorderLayout.CENTER);        
      comboAndRangeHolder.add(m_graphFutureCheckBox, BorderLayout.SOUTH);
      m_graphFutureCheckBox.setSelected(true);
      
      m_stepLab.setEnabled(false); m_stepRange.setEnabled(false);
      m_targetComboLabel.setEnabled(false); m_graphTargetAtStepsCombo.setEnabled(false);
      m_graphTargetForStepsCheckBox.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          boolean enabled = m_graphTargetForStepsCheckBox.isSelected();
          m_stepLab.setEnabled(enabled); m_stepRange.setEnabled(enabled);
          m_targetComboLabel.setEnabled(enabled); m_graphTargetAtStepsCombo.setEnabled(enabled);
        }
      });
      
      graphOutputHolder.add(comboAndRangeHolder, BorderLayout.CENTER);
      
      m_configHolder.addTab("Output", null, outputOptsHolder, "Configure output");
    }
    
    private Instances createDateDerivedPeriodicList() {
      ArrayList<Attribute> atts = new ArrayList<Attribute>();
      atts.add(new Attribute("AM")); atts.add(new Attribute("DayOfWeek"));
      atts.add(new Attribute("DayOfMonth")); atts.add(new Attribute("NumDaysInMonth"));
      atts.add(new Attribute("Weekend")); atts.add(new Attribute("Month"));
      atts.add(new Attribute("Quarter"));
      
      // Custom periodics
      for (String name : m_customPeriodics.keySet()) {
        atts.add(new Attribute("c_" + name));
      }
      
      Instances insts = new Instances("Periodics", atts, 1);
      
      return insts;
    }
    
    private String displayAddEditDialog(List<CustomPeriodicTest> testList, 
        String fieldName) {
      CustomPeriodicEditor ed = new CustomPeriodicEditor(testList);
      if (fieldName != null && fieldName.length() > 0) {
        ed.setFieldName(fieldName);
      }
      int result = JOptionPane.showConfirmDialog(this, ed, 
          "Add/Edit custom periodic field", JOptionPane.OK_CANCEL_OPTION);
      
      if (result == JOptionPane.OK_OPTION) {
        if (testList.size() == 0) {
          // Nothing added to the list, so just cancel
          return "";
        }
        return ed.getFieldName();
      } else {
        return ""; // indicates cancel
      }
    }
    
    /**
     * Layout the overlay panel
     */
    protected void layoutOverlayPanel() {
      JPanel basePanel = new JPanel();
      basePanel.setLayout(new BorderLayout());
      
      JPanel temp = new JPanel();
      temp.setLayout(new BorderLayout());
      //temp
      JPanel checkHolder = new JPanel();
      checkHolder.setLayout(new BorderLayout());
      checkHolder.add(m_useOverlayData, BorderLayout.NORTH);
      m_useOverlayData.setEnabled(false);
      
      temp.add(checkHolder, BorderLayout.EAST);
      temp.add(m_overlaySelector, BorderLayout.CENTER);
      m_overlaySelector.setPreferredScrollableViewportSize(new Dimension(250,70));
      
      JPanel botP = new JPanel();
      botP.setLayout(new BorderLayout());
      botP.setBorder(BorderFactory.
          createTitledBorder("Overlay data selection"));
      botP.add(temp, BorderLayout.CENTER);
      
      m_useOverlayData.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          if (m_instances != null && m_useOverlayData.isSelected()) {
            if (m_overlaySelector.getSelectionModel() != null) {
              if (m_overlayHeader == null){
                m_overlayHeader = createAvailableOverlayList();
              } else {
                Instances newI = createAvailableOverlayList();
                if (newI == null) {
                  m_overlaySelector.clearTableModel();
                  m_overlayHeader = null;
                  return;
                } else if (!newI.equalHeaders(m_overlayHeader)) {
                  m_overlayHeader = newI;
                }
              }
              m_overlaySelector.setInstances(m_overlayHeader);
            }
          } else {
            m_overlaySelector.clearTableModel();
          }
        }
      });
      
      basePanel.add(botP, BorderLayout.NORTH);
      
      m_configHolder.addTab("Overlay data", null, basePanel,
        "Specify attributes that are to be considered as \"overlay\" data");
    }
    
    protected void savePeriodicsToFile() throws IOException {
      if (m_fileChooser == null) {
        m_fileChooser = 
          new JFileChooser(new File(System.getProperty("user.home")));
        m_fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
        FileFilter filter = new ExtensionFileFilter(".periodics", "Time series date-derived " +
        		"periodic attribute definitions (*periodics)");
        m_fileChooser.addChoosableFileFilter(filter);
      }
      int result = m_fileChooser.showSaveDialog(this);
      if (result == JFileChooser.APPROVE_OPTION) {
        File saveFile = m_fileChooser.getSelectedFile();
        if (saveFile.toString().indexOf(".periodics") < 0) {
          saveFile = new File(saveFile.toString() + ".periodics");
        }
        
        PrintWriter br = 
          new PrintWriter(new BufferedWriter(new FileWriter(saveFile)));
        
        br.print("time-series-periodics\n");    
        
        int[] selected = m_dateDerivedPeriodicSelector.getSelectedAttributes();
        if (m_dateDerivedPeriodicsHeader != null) {
          for (int i = 0; i < selected.length; i++) {
            int s = selected[i];
            String name = m_dateDerivedPeriodicsHeader.attribute(s).name();
            if (s < NUM_PREDEFINED_PERIODICS) {
              br.print("*pre-defined*:" + name + "\n");
            } else {
              
              // remove the "c_"
              name = name.replaceFirst("c_", "");
              ArrayList<CustomPeriodicTest> selectedP = 
                m_customPeriodics.get(name);
              
              br.print("*custom*:" + name + "\n");
              for (int j = 0; j < selectedP.size(); j++) {
                br.print(selectedP.get(j).toString() + "\n");
              }            
            }
          }
        }
        br.flush();
        br.close();
      }        
    }
    
    protected void loadPeriodicsFromFile() throws IOException {
      if (m_fileChooser == null) {
        m_fileChooser = 
          new JFileChooser(new File(System.getProperty("user.home")));
        m_fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
        FileFilter filter = new ExtensionFileFilter(".periodics", "Time series date-derived " +
                  "periodic attribute definitions (*periodics)");
        m_fileChooser.addChoosableFileFilter(filter);
      }
      
      int result = m_fileChooser.showOpenDialog(this);
      if (result == JFileChooser.APPROVE_OPTION) {
        File loadFile = m_fileChooser.getSelectedFile();
        
        BufferedReader br = new BufferedReader(new FileReader(loadFile));
        String identifierLine = br.readLine();
        boolean ok = false;
        if (identifierLine != null) {
          if (identifierLine.equalsIgnoreCase("time-series-periodics")) {
            ok = true;
          }
        }
        
        if (!ok) {
          JOptionPane.showConfirmDialog(this, "\"" + loadFile.toString() + "\" does not" +
          		"\nappear to be a periodic attribute definition file", 
          		"Unrecognised file type", JOptionPane.ERROR_MESSAGE);
          return;
        }
        
        String line = null;
        boolean withinCustom = false;
        m_customPeriodics.clear();
        List<String> predefined = new ArrayList<String>();
        ArrayList<CustomPeriodicTest> testList = null;
        String currentCustomName = null;
        while ((line = br.readLine()) != null) {
          if (line.startsWith("*pre-defined*:")) {
            if (withinCustom) {
              // finish custom
              m_customPeriodics.put(currentCustomName, testList);
            }
            
            withinCustom = false;
            line = line.substring(line.indexOf(":") + 1, line.length()).trim();
            // process predefined type
            predefined.add(line);
          } else if (line.startsWith("*custom*:")) {
            if (withinCustom) {
              // finish custom
              m_customPeriodics.put(currentCustomName, testList);
            }
  
            // start new custom      
            testList = new ArrayList<CustomPeriodicTest>();
            currentCustomName = 
              line.substring(line.indexOf(":") + 1, line.length()).trim();
            withinCustom = true;
  
          } else if (withinCustom) {
            // process custom part
            try {
              CustomPeriodicTest t = new CustomPeriodicTest(line);
              testList.add(t);
            } catch (IllegalArgumentException ex) {
              ex.printStackTrace();
              JOptionPane.showConfirmDialog(this, "A problem occurred while parsing\n" +
              		"the following custom periodic test:\n"
                  + line, "Error parsing custom test", JOptionPane.ERROR_MESSAGE);
              return;
            }
            
          }
        }
        
        br.close();
        
        // check for last custom (if exists)
        if (testList != null && testList.size() > 0) {
          m_customPeriodics.put(currentCustomName, testList);
        }
        
        m_dateDerivedPeriodicSelector.clearTableModel();
        Instances insts = createDateDerivedPeriodicList();
        m_dateDerivedPeriodicSelector.setInstances(insts);
        m_dateDerivedPeriodicsHeader = insts;
        
        boolean[] selected = new boolean[insts.numAttributes()];
        for (int i = 0; i < insts.numAttributes(); i++) {
          if (i < NUM_PREDEFINED_PERIODICS) {
            switch (i) {
              case 0:
                if (predefined.contains("AM")) {
                  selected[i] = true;
                }
                break;
              case 1:
                if (predefined.contains("DayOfWeek")) {
                  selected[i] = true;
                }
                break;
              case 2:
                if (predefined.contains("DayOfMonth")) {
                  selected[i] = true;
                }
                break;
              case 3:
                if (predefined.contains("NumDaysInMonth")) {
                  selected[i] = true;
                }
                break;
              case 4:
                if (predefined.contains("Weekend")) {
                  selected[i] = true;
                }
                break;
              case 5:
                if (predefined.contains("Month")) {
                  selected[i] = true;
                }
                break;
              case 6:
                if (predefined.contains("Quarter")) {
                  selected[i] = true;
                }
                break;
            }
          } else {
            selected[i] = true;
          }
        }
        try {
          m_dateDerivedPeriodicSelector.setSelectedAttributes(selected);
        } catch (Exception ex) {
          ex.printStackTrace();
        }
      }
    }
    
    protected void layoutDateDerivedPeriodicPanel() {
      JPanel basePanel = new JPanel();
      basePanel.setLayout(new BorderLayout());        
      
      JPanel temp = new JPanel();
      temp.setLayout(new BorderLayout());
      //temp
      JPanel checkHolder = new JPanel();
      checkHolder.setLayout(new BorderLayout());
      checkHolder.add(m_customizeDateDerivedPeriodics, BorderLayout.EAST);
      JPanel editButsP = new JPanel();
      editButsP.setLayout(new GridLayout(1,5));
      editButsP.add(m_addCustomPeriodicBut);
      editButsP.add(m_deleteCustomPeriodicBut);
      editButsP.add(m_editCustomPeriodicBut);
      editButsP.add(m_savePeriodicBut);
      editButsP.add(m_loadPeriodicBut);
      
      m_loadPeriodicBut.setEnabled(false);
      m_savePeriodicBut.setEnabled(false);
      m_savePeriodicBut.setToolTipText("Save checked date-derived " +
      		"periodic definitions to a file");
      m_savePeriodicBut.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          try {
            savePeriodicsToFile();
          } catch (IOException ex) {
            ex.printStackTrace();
          }
        }
      });
      
      m_loadPeriodicBut.setToolTipText("Load pre-saved date-derived periodic " +
      		"definitions from a file");
      m_loadPeriodicBut.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          try {
            loadPeriodicsFromFile();
          } catch (IOException ex) {
            ex.printStackTrace();
          }
        }
      });
      
      checkHolder.add(editButsP, BorderLayout.WEST);
      m_editCustomPeriodicBut.setEnabled(false);
      m_editCustomPeriodicBut.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          int selectedRow = m_dateDerivedPeriodicSelector.getTable().getSelectedRow();
          if (selectedRow > NUM_PREDEFINED_PERIODICS - 1) {
            String fieldName = m_dateDerivedPeriodicsHeader.attribute(selectedRow).name();
            
            // need to trim off the "c_"
            fieldName = fieldName.replaceFirst("c_", "");
            ArrayList<CustomPeriodicTest> toEdit = m_customPeriodics.get(fieldName);
            if (toEdit == null) {
              System.err.println("Oh oh, couldn't find " + fieldName + " to edit!");
            } else {
              // now make a copy of it
              try {
                SerializedObject so = new SerializedObject(toEdit);
                toEdit = (ArrayList<CustomPeriodicTest>)so.getObject();
                
                String newFieldName = displayAddEditDialog(toEdit, fieldName);
                if (newFieldName.length() == 0) {
                  // cancel selected so do nothing
                } else if (!newFieldName.equals(fieldName)) {
                  // user has changed the field name. Delete the old one first
                  m_customPeriodics.remove(fieldName);
                  m_customPeriodics.put(newFieldName, toEdit);
                  
                  // now have to refresh the list in order to show the new name                
                  // what's been selected so far?
                  int[] selected = m_dateDerivedPeriodicSelector.getSelectedAttributes();
                  Instances insts = createDateDerivedPeriodicList();
                  m_dateDerivedPeriodicSelector.setInstances(insts);
                  m_dateDerivedPeriodicsHeader = insts;
                  
                  boolean[] newSelected = new boolean[insts.numAttributes()];
                  for (int i = 0; i < selected.length; i++) {
                    newSelected[selected[i]] = true;
                  }
                  try {
                    m_dateDerivedPeriodicSelector.setSelectedAttributes(newSelected);
                  } catch (Exception ex) {
                    ex.printStackTrace();
                  }
                } else {
                  // name unchanged, so can just update the map
                  m_customPeriodics.put(fieldName, toEdit);
                }
              } catch (Exception e1) {
                e1.printStackTrace();
              }
            }          
          }
        }
      });
      
      m_deleteCustomPeriodicBut.setEnabled(false);
      m_deleteCustomPeriodicBut.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          int selectedRow = m_dateDerivedPeriodicSelector.getTable().getSelectedRow();
          if (selectedRow > NUM_PREDEFINED_PERIODICS - 1) {
            String fieldName = m_dateDerivedPeriodicsHeader.attribute(selectedRow).name();
            
            // need to trim off the "c_"
            fieldName = fieldName.replaceFirst("c_", "");
            
            // Confirm delete
            int result = JOptionPane.showConfirmDialog(AdvancedConfigPanel.this, 
                "Delete field " + fieldName
                + "?", "Delete custom periodic field", JOptionPane.YES_NO_OPTION);
            if (result == JOptionPane.NO_OPTION) {
              return;
            }
            
            m_customPeriodics.remove(fieldName);
            
            // what's been selected so far?
            int[] selected = m_dateDerivedPeriodicSelector.getSelectedAttributes();
            Instances oldList = m_dateDerivedPeriodicsHeader;
  
            // now need to rebuild the list
            Instances insts = createDateDerivedPeriodicList();
            m_dateDerivedPeriodicSelector.setInstances(insts);
            m_dateDerivedPeriodicsHeader = insts;
            
            // make sure that selected stuff stays selected
            boolean[] newSelected = new boolean[insts.numAttributes()];
            for (int i = 0; i < selected.length; i++) {
              Attribute toFind = oldList.attribute(selected[i]);
              Attribute toSet = insts.attribute(toFind.name());
              if (toSet != null) {
                newSelected[toSet.index()] = true;
              }
            }
            try {
              m_dateDerivedPeriodicSelector.setSelectedAttributes(newSelected);
            } catch (Exception e1) {
              e1.printStackTrace();
            }
          }
        }
      });
      
      m_addCustomPeriodicBut.setEnabled(false);
      m_addCustomPeriodicBut.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {        
          ArrayList<CustomPeriodicTest> testList = new ArrayList<CustomPeriodicTest>();
          String fieldName = displayAddEditDialog(testList, null);
          if (fieldName.length() == 0) {          
            // basically do nothing
          } else {
            // Add this new one to the Map
            m_customPeriodics.put(fieldName, testList);
  
            // what's been selected so far?
            int[] selected = m_dateDerivedPeriodicSelector.getSelectedAttributes();
            Instances insts = createDateDerivedPeriodicList();
            m_dateDerivedPeriodicSelector.setInstances(insts);
            m_dateDerivedPeriodicsHeader = insts;
            
            boolean[] newSelected = new boolean[insts.numAttributes()];
            for (int i = 0; i < selected.length; i++) {
              newSelected[selected[i]] = true;
            }
  
            // select the newly created field
            newSelected[newSelected.length - 1] = true;
            try {
              m_dateDerivedPeriodicSelector.setSelectedAttributes(newSelected);
            } catch (Exception e1) {
              e1.printStackTrace();
            }
          }
        }
      });
      
      temp.add(checkHolder, BorderLayout.NORTH);
      temp.add(m_dateDerivedPeriodicSelector, BorderLayout.CENTER);
      m_dateDerivedPeriodicSelector.setPreferredScrollableViewportSize(new Dimension(250,80));
      JPanel botP = new JPanel();
      botP.setLayout(new BorderLayout());
      botP.setBorder(BorderFactory.
          createTitledBorder("Date-derived periodic creation"));
      botP.add(temp, BorderLayout.CENTER);
      
      JPanel primaryPeriodicP = new JPanel();
      primaryPeriodicP.setLayout(new BorderLayout());
      //primaryPeriodicP
        
      primaryPeriodicP.add(m_primaryPeriodicCombo, BorderLayout.NORTH);
      JPanel topP = new JPanel();
      topP.setLayout(new BorderLayout());
      topP.setBorder(BorderFactory.createTitledBorder("Periodic attribute"));
      topP.add(primaryPeriodicP, BorderLayout.EAST);
      
      JPanel temp2 = new JPanel();
      temp2.setLayout(new BorderLayout());
      temp2.add(topP, BorderLayout.EAST);
      temp2.add(botP, BorderLayout.CENTER);
  //    basePanel.add(primaryPeriodicP, BorderLayout.EAST);
      basePanel.add(temp2, BorderLayout.NORTH);
      
      m_customizeDateDerivedPeriodics.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          if (m_instances != null && m_customizeDateDerivedPeriodics.isSelected()) {
            String selectedTimeStamp = 
              m_simpleConfig.m_timeStampCombo.getSelectedItem().toString();
            Attribute timeStampAtt = m_instances.attribute(selectedTimeStamp);
            
            if (timeStampAtt != null && timeStampAtt.isDate()) {
              if (m_dateDerivedPeriodicSelector.getSelectionModel() != null) {
                Instances insts = createDateDerivedPeriodicList();
                
                m_dateDerivedPeriodicSelector.setInstances(insts);
                m_dateDerivedPeriodicsHeader = insts;
                m_addCustomPeriodicBut.setEnabled(true);
                m_savePeriodicBut.setEnabled(true);
                m_loadPeriodicBut.setEnabled(true);
                
                m_dateDerivedPeriodicSelector.getSelectionModel().
                  addListSelectionListener(new ListSelectionListener() {
                    public void valueChanged(ListSelectionEvent e) {
                      if (!e.getValueIsAdjusting()) {
                        if (m_dateDerivedPeriodicSelector.getTable().
                            getSelectedRow() > NUM_PREDEFINED_PERIODICS - 1) {
                          m_editCustomPeriodicBut.setEnabled(true);
                          m_deleteCustomPeriodicBut.setEnabled(true);
                        } else {
                          m_editCustomPeriodicBut.setEnabled(false);
                          m_deleteCustomPeriodicBut.setEnabled(false);
                        }
                      }
                    }
                  });
              }
            }
          } else {
            m_addCustomPeriodicBut.setEnabled(false);
            m_loadPeriodicBut.setEnabled(false);
            m_savePeriodicBut.setEnabled(false);
          }
        }
      });
      
      updatePrimaryPeriodic();
          
      //holder.add(basePanel, BorderLayout.WEST);
      m_configHolder.addTab("Periodic attributes", null, basePanel,
          "Specify/clustomize periodic attributes");
      updateDateDerivedPanel();
    }
    
    /**
     * Layout the learner panel
     */
    protected void layoutLearnerPanel() {
      JPanel basePanel = new JPanel();
      basePanel.setLayout(new BorderLayout());
      basePanel.setBorder(BorderFactory.
          createTitledBorder("Base learner configuration"));
      
      m_baseLearnerEditor.setClassType(Classifier.class);
      m_baseLearnerEditor.setValue(new weka.classifiers.functions.SMOreg());
      
      Capabilities capabilities = new Capabilities(null);
      capabilities.disableAll();
      capabilities.enable(Capability.NOMINAL_ATTRIBUTES);
      capabilities.enable(Capability.NUMERIC_ATTRIBUTES);
      capabilities.enable(Capability.NUMERIC_CLASS);
      capabilities.enableAllAttributeDependencies();
      capabilities.enableAllClassDependencies();
      m_baseLearnerEditor.setCapabilitiesFilter(capabilities);
      
      basePanel.add(m_baseLearnerPanel, BorderLayout.NORTH);
      //add(basePanel, BorderLayout.NORTH);
      m_configHolder.addTab("Base learner", null, basePanel, 
          "Base learner configuration");
    }
    
    /**
     * Layout the lag panel 
     */
    protected void layoutLagPanel() {
      // lag stuff
      JPanel lagPanel = new JPanel();
      lagPanel.setLayout(new GridLayout(1,2));
      //lagPanel.setBorder(BorderFactory.createTitledBorder("Lag creation"));    
      
      SpinnerNumberModel snm = new SpinnerNumberModel();
      snm.setValue(1); snm.setMinimum(1);
      m_minLagSpinner = new JSpinner(snm);
      Dimension spinD = m_minLagSpinner.getPreferredSize();
      spinD = new Dimension((int)(spinD.getWidth() * 1.5), 
          (int)spinD.getHeight());
      m_minLagSpinner.setPreferredSize(spinD);
      
      JPanel temp1 = new JPanel();
      temp1.setBorder(BorderFactory.createEmptyBorder(0, 0, 1, 0));
      temp1.setLayout(new BorderLayout());
      final JLabel minLagLab = new JLabel("Minimum lag", JLabel.RIGHT);
      temp1.add(minLagLab, BorderLayout.CENTER);
      temp1.add(m_minLagSpinner, BorderLayout.EAST);
      JPanel spinnerHolder = new JPanel();
      spinnerHolder.setLayout(new BorderLayout());
      spinnerHolder.setBorder(BorderFactory.createTitledBorder("Lag length"));
      
      JPanel vH = new JPanel();
      vH.setLayout(new BorderLayout());
  
      JPanel varianceHolder = new JPanel();
      varianceHolder.setLayout(new BorderLayout());
      varianceHolder.add(m_adjustForVarianceCheckBox, BorderLayout.EAST);
      m_adjustForVarianceCheckBox.setSelected(false);
      vH.add(varianceHolder, BorderLayout.NORTH);
      
      JPanel checkHolder = new JPanel();
      checkHolder.setLayout(new BorderLayout());
      checkHolder.add(m_useCustomLags, BorderLayout.EAST);
      vH.add(checkHolder, BorderLayout.SOUTH);
      
      spinnerHolder.add(vH, BorderLayout.NORTH);
      spinnerHolder.add(temp1, BorderLayout.CENTER);
      
      snm = new SpinnerNumberModel();
      snm.setValue(12); snm.setMinimum(1);
      m_maxLagSpinner = new JSpinner(snm);
      m_maxLagSpinner.setPreferredSize(spinD);
      JPanel temp2 = new JPanel();
      temp2.setLayout(new BorderLayout());
      temp2.setBorder(BorderFactory.createEmptyBorder(1, 0, 1, 0));
      final JLabel maxLagLab = new JLabel("Maximum lag", JLabel.RIGHT);
      temp2.add(maxLagLab, BorderLayout.CENTER);
      temp2.add(m_maxLagSpinner, BorderLayout.EAST);
      
      final JLabel fineTuneLab = new JLabel("Fine tune lag selection", JLabel.RIGHT);
      JPanel fPanel = new JPanel(); fPanel.setLayout(new BorderLayout());
      fPanel.add(temp2, BorderLayout.NORTH);
      
      JPanel fineHolder = new JPanel(); fineHolder.setLayout(new BorderLayout());
      fineHolder.setBorder(BorderFactory.createEmptyBorder(1, 0, 0, 0));
      fineHolder.add(fineTuneLab, BorderLayout.CENTER);
      fineHolder.add(m_fineTuneLagsField, BorderLayout.EAST);
      m_fineTuneLagsField.setPreferredSize(spinD);
      fineTuneLab.setEnabled(false);
      fineTuneLab.setToolTipText("Specify ranges to fine tune " +
      		"lags within minimum and maximum (e.g. 2,3,6-8)");
      m_fineTuneLagsField.setEnabled(false);
      m_fineTuneLagsField.setToolTipText("Specify ranges to fine tune " +
          "lags within minimum and maximum (e.g. 2,3,6-8)");
      
      fPanel.add(fineHolder,BorderLayout.SOUTH);
              
      spinnerHolder.add(fPanel, BorderLayout.SOUTH);
      
      m_useCustomLags.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          boolean enabled = m_useCustomLags.isSelected();
          m_minLagSpinner.setEnabled(enabled);
          m_maxLagSpinner.setEnabled(enabled);
          minLagLab.setEnabled(enabled);
          maxLagLab.setEnabled(enabled);
          fineTuneLab.setEnabled(enabled);
          m_fineTuneLagsField.setEnabled(enabled);
          
          m_averageLongLags.setEnabled(enabled);
          if (!enabled) {
            m_averageLongLags.setSelected(false);
          }
          /*m_averageLagsAfter.setEnabled(enabled);
          m_numConsecutiveToAverage.setEnabled(enabled); */
        }
      });    
      
      snm = new SpinnerNumberModel();
      snm.setValue(2); snm.setMinimum(1);
      m_averageLagsAfter = new JSpinner(snm);
      m_averageLagsAfter.setPreferredSize(spinD);
      
      snm = new SpinnerNumberModel();
      snm.setValue(2); snm.setMinimum(2);
      m_numConsecutiveToAverage = new JSpinner(snm);
      m_numConsecutiveToAverage.setPreferredSize(spinD);
      
      JPanel averageLagHolder = new JPanel();
      averageLagHolder.setBorder(BorderFactory.createTitledBorder("Averaging"));
      averageLagHolder.setLayout(new BorderLayout());
      JPanel avCheckHolder = new JPanel();
      avCheckHolder.setLayout(new BorderLayout());
      avCheckHolder.add(m_averageLongLags, BorderLayout.EAST);
      averageLagHolder.add(avCheckHolder, BorderLayout.NORTH);
      JPanel temp3 = new JPanel();
      temp3.setLayout(new BorderLayout());
      temp3.setBorder(BorderFactory.createEmptyBorder(0, 0, 1, 0));
      final JLabel avLab =new JLabel("Average lags longer than", JLabel.RIGHT); 
      temp3.add(avLab, BorderLayout.CENTER);
      temp3.add(m_averageLagsAfter, BorderLayout.EAST);
      
      JPanel aH = new JPanel();
      aH.setLayout(new BorderLayout());
      aH.add(temp3, BorderLayout.NORTH);
      
      //averageLagHolder.add(temp3, BorderLayout.CENTER);
      JPanel temp4 = new JPanel();
      temp4.setLayout(new BorderLayout());
      temp4.setBorder(BorderFactory.createEmptyBorder(1, 0, 0, 0));
      final JLabel numConsLab = new JLabel("# consecutive lags to average", JLabel.RIGHT); 
      temp4.add(numConsLab, BorderLayout.CENTER);
      temp4.add(m_numConsecutiveToAverage, BorderLayout.EAST);
      aH.add(temp4, BorderLayout.SOUTH);
      averageLagHolder.add(aH, BorderLayout.SOUTH);
      
      m_useCustomLags.setSelected(false);
      m_minLagSpinner.setEnabled(false);
      m_maxLagSpinner.setEnabled(false);
      minLagLab.setEnabled(false);
      maxLagLab.setEnabled(false);
      m_averageLongLags.setEnabled(false);
      avLab.setEnabled(false);
      m_averageLagsAfter.setEnabled(false);
      m_numConsecutiveToAverage.setEnabled(false);
      numConsLab.setEnabled(false);
      
      m_averageLongLags.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          if (m_useCustomLags.isSelected()) {
            boolean enabled = m_averageLongLags.isSelected();
            m_averageLagsAfter.setEnabled(enabled);
            m_numConsecutiveToAverage.setEnabled(enabled);
            avLab.setEnabled(enabled);
            numConsLab.setEnabled(enabled);
          }
        }
      });
          
      lagPanel.add(spinnerHolder);//, BorderLayout.CENTER);
      lagPanel.add(averageLagHolder);//, BorderLayout.EAST);
      
      JPanel tempP = new JPanel();
      tempP.setLayout(new BorderLayout());
      tempP.add(lagPanel, BorderLayout.NORTH);
      
      
              
      //add(tempP, BorderLayout.CENTER);
      m_configHolder.addTab("Lag creation", null, tempP, 
          "Customize lagged variables");
    }
    
    /**
     * Apply the configuration defined in this panel to the supplied forecaster
     * 
     * @param forecaster the forecaster to apply the configuration to
     * @throws Exception if a problem occurs
     */
    public void applyToForecaster(WekaForecaster forecaster) throws Exception {
      if (forecaster != null) {
        TSLagMaker lagMaker = forecaster.getTSLagMaker();
        
        // Set variance adjustment
        lagMaker.setAdjustForVariance(m_adjustForVarianceCheckBox.isSelected());
        
        int minLag = ((SpinnerNumberModel)m_minLagSpinner.getModel()).
          getNumber().intValue();
        int maxLag = ((SpinnerNumberModel)m_maxLagSpinner.getModel()).
          getNumber().intValue();
        if (m_useCustomLags.isSelected()) {
          
          // set lag lengths from spinners                
          if (maxLag < minLag) {
            throw new Exception("Maximum lag value must be greater than or equal to" +
            		" the minimum lag value");
          }
          
          if (m_instances != null && maxLag > m_instances.numInstances()) {
            throw new Exception("The maximum lag can't exceed the number instances (" +
            		m_instances.numInstances() + ") in the data!");
          }
          
          lagMaker.setMinLag(minLag); lagMaker.setMaxLag(maxLag);
          
          if (m_fineTuneLagsField.getText() != null && 
              m_fineTuneLagsField.getText().length() > 0) {
            lagMaker.setLagRange(m_fineTuneLagsField.getText());
          }
          
          // set up averaging from spinners
          if (m_averageLongLags.isSelected()) {
            lagMaker.setAverageConsecutiveLongLags(true);
            
            int avLagsAfter = ((SpinnerNumberModel)m_averageLagsAfter.getModel()).
              getNumber().intValue();
            int numToAv = ((SpinnerNumberModel)m_numConsecutiveToAverage.getModel()).
              getNumber().intValue();
            
            if (avLagsAfter < minLag || avLagsAfter > maxLag) {
              throw new Exception("Point at which to start lag averaging must " +
              		"lie between the minimum and maximum lag value.");
            }
            
            lagMaker.setAverageLagsAfter(avLagsAfter);
            lagMaker.setNumConsecutiveLongLagsToAverage(numToAv);
          } else {
            lagMaker.setAverageConsecutiveLongLags(false);
          }
        } else {
          lagMaker.setAverageConsecutiveLongLags(false);
          lagMaker.setLagRange("");
        }
        
        // date-derived periodic customization
        if (getCustomizeDateDerivedPeriodics()) {
          // reset all to false
          lagMaker.setAddAMIndicator(false); lagMaker.setAddDayOfWeek(false);
          lagMaker.setAddMonthOfYear(false); lagMaker.setAddQuarterOfYear(false);
          lagMaker.setAddWeekendIndicator(false); lagMaker.setAddDayOfMonth(false);
          lagMaker.setAddNumDaysInMonth(false);
          
          int[] selected = m_dateDerivedPeriodicSelector.getSelectedAttributes();
          if (m_dateDerivedPeriodicsHeader != null) {
            Map<String, ArrayList<CustomPeriodicTest>> custom =
              new HashMap<String, ArrayList<CustomPeriodicTest>>();
            for (int i = 0; i < selected.length; i++) {
              int s = selected[i];
              String name = m_dateDerivedPeriodicsHeader.attribute(s).name();
              if (s < NUM_PREDEFINED_PERIODICS) {
                if (name.equals("AM")) {
                  lagMaker.setAddAMIndicator(true);              
                }
                if (name.equals("DayOfWeek")) {
                  lagMaker.setAddDayOfWeek(true);
                }
                if (name.equals("DayOfMonth")) {
                  lagMaker.setAddDayOfMonth(true);
                }
                if (name.equals("NumDaysInMonth")) {
                  lagMaker.setAddNumDaysInMonth(true);
                }
                if (name.equals("Weekend")) {
                  lagMaker.setAddWeekendIndicator(true);
                }
                if (name.equals("Month")) {
                  lagMaker.setAddMonthOfYear(true);
                }
                if (name.equals("Quarter")) {
                  lagMaker.setAddQuarterOfYear(true);
                }
              } else {
                // remove the "c_"
                name = name.replaceFirst("c_", "");
                ArrayList<CustomPeriodicTest> selectedP = 
                  m_customPeriodics.get(name);
                custom.put(name, selectedP);
                  
              }
            }
            lagMaker.clearCustomPeriodics();
            if (custom.size() > 0) {            
              lagMaker.setCustomPeriodics(custom);
            }
          }
        }
        
        // non-date primary periodic attribute 
        String ppfn = m_primaryPeriodicCombo.getSelectedItem().toString();
        if (!ppfn.equals("<None>")) {
          lagMaker.setPrimaryPeriodicFieldName(ppfn);
        } else {
          lagMaker.setPrimaryPeriodicFieldName("");
        }
        
        if (m_useOverlayData.isSelected() && m_overlayHeader != null && 
            forecaster instanceof OverlayForecaster) {
          int[] selected = m_overlaySelector.getSelectedAttributes();
          String overlayList = "";
          for (int i = 0; i < selected.length; i++) {
            overlayList += m_overlayHeader.attribute(selected[i]).name() + ",";
          }
          
          if (overlayList.length() > 0) {
            overlayList = overlayList.substring(0, overlayList.lastIndexOf(','));
            ((OverlayForecaster)forecaster).setOverlayFields(overlayList);
          } else {
            ((OverlayForecaster)forecaster).setOverlayFields(null);
          }
        } else {
          if (forecaster instanceof OverlayForecaster) {
            ((OverlayForecaster)forecaster).setOverlayFields(null);
          }
        }
      }
    }
    
    /**
     * Apply the configuration defined in this panel to the supplied evaluation
     * object
     * 
     * @param eval the evaluation object to apply the configuration to
     * @param forecaster the forecaster in use
     * @throws Exception if a problem occurs
     */
    public void applyToEvaluation(TSEvaluation eval, WekaForecaster forecaster) 
      throws Exception {
      
      // eval on training
      eval.setEvaluateOnTrainingData(m_trainingCheckBox.isSelected());
      
      if (!eval.getEvaluateOnTrainingData() && !eval.getEvaluateOnTestData()) {
        // throw new Exception("Must evaluate")
      }
      
      // evaluation modules
      int[] selected = m_evaluationMetrics.getSelectedAttributes();
      
      if (selected.length == 0) {
        throw new Exception("Must select at least one evaluation metric.");
      }
      
      List<TSEvalModule> modsList = TSEvalModule.getModuleList();
      String modListS = "";
      for (int s : selected) {
        String name = m_evaluationModsHeader.attribute(s).name();
        for (TSEvalModule mod : modsList) {
          if (name.equals(mod.toString())) {
            modListS += mod.getEvalName() + ",";
          }
        }
      }
      
      modListS = modListS.substring(0, modListS.lastIndexOf(','));
      eval.setEvaluationModules(modListS);
      
      // TODO separate test set
    }
    
    /**
     * Tests the Weka advanced config panel from the command line.
     *
     * @param args must contain the name of an arff file to load.
     */
    public static void main(String[] args) {
  
      try {
        if (args.length == 0) {
          throw new Exception("supply the name of an arff file");
        }
        Instances i = new Instances(new java.io.BufferedReader(
                                    new java.io.FileReader(args[0])));
        SimpleConfigPanel scp = new SimpleConfigPanel(null);
        scp.setInstances(i);
        AdvancedConfigPanel acp = new AdvancedConfigPanel(scp);
        //acp.setInstances(i);
        
        final javax.swing.JFrame jf =
          new javax.swing.JFrame("Weka Forecasting");
        jf.getContentPane().setLayout(new BorderLayout());
        jf.getContentPane().add(acp, BorderLayout.CENTER);
        jf.addWindowListener(new java.awt.event.WindowAdapter() {
          public void windowClosing(java.awt.event.WindowEvent e) {
            jf.dispose();
            System.exit(0);
          }
        });
        jf.pack();
        jf.setVisible(true);
      } catch (Exception ex) {
        ex.printStackTrace();
        System.err.println(ex.getMessage());
      }
    }
  }
  