package jrex.ui;

import jrex.ui.model.MutableTableModel;
import jrex.ui.model.PivotTableModel;
import com.google.common.collect.ListMultimap;
import com.google.gson.Gson;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.KeyStroke;
import javax.swing.SpinnerNumberModel;
import javax.swing.UIManager;
import javax.swing.table.TableModel;
import javax.swing.text.DefaultEditorKit;
import jrex.Expressions;

/**
 * Primary user interface frame.
 *
 * @author Mark George <mark.george@otago.ac.nz>
 */
public class MainFrame extends javax.swing.JFrame {

	private final Color defaultTextColor;

	public MainFrame() {
		initComponents();
		splitPane.setResizeWeight(0.66);
		spnColumns.setModel(new SpinnerNumberModel(0, 0, Integer.MAX_VALUE, 1));

		JPopupMenu rightClickMenu = new JPopupMenu();

		Action cut = new DefaultEditorKit.CutAction();
		cut.putValue(Action.NAME, "Cut");
		cut.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke("control X"));
		rightClickMenu.add(cut);

		Action copy = new DefaultEditorKit.CopyAction();
		copy.putValue(Action.NAME, "Copy");
		copy.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke("control C"));
		rightClickMenu.add(copy);

		Action paste = new DefaultEditorKit.PasteAction();
		paste.putValue(Action.NAME, "Paste");
		paste.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke("control V"));
		rightClickMenu.add(paste);

		Action tab = new DefaultEditorKit.InsertTabAction();
		tab.putValue(Action.NAME, "Insert Tab");
		rightClickMenu.add(tab);

		txtInput.setComponentPopupMenu(rightClickMenu);
		txtOutput.setComponentPopupMenu(rightClickMenu);
		txtRegex.setComponentPopupMenu(rightClickMenu);
		txtFormat.setComponentPopupMenu(rightClickMenu);
		txtBlanks.setComponentPopupMenu(rightClickMenu);

		defaultTextColor = txtRegex.getBackground();
		pnlReplace.setVisible(false);
		pnlReplace.setComponents(txtInput, txtOutput);

		Icon folderIcon = desaturateIcon(UIManager.getIcon("FileView.directoryIcon"));
		Icon floppyIcon = desaturateIcon(UIManager.getIcon("FileView.floppyDriveIcon"));
		Icon fileIcon = desaturateIcon(UIManager.getIcon("FileView.fileIcon"));

		btnSave.setIcon(floppyIcon);
		btnLoad.setIcon(folderIcon);
		btnClear.setIcon(fileIcon);
	}

	private Icon desaturateIcon(Icon icon) {
		int w = icon.getIconWidth();
		int h = icon.getIconHeight();
		GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
		GraphicsDevice gd = ge.getDefaultScreenDevice();
		GraphicsConfiguration gc = gd.getDefaultConfiguration();
		BufferedImage iconImage = gc.createCompatibleImage(w, h, Transparency.TRANSLUCENT);
		Graphics2D g2d = iconImage.createGraphics();
		icon.paintIcon(null, g2d, 0, 0);
		ColorConvertOp op = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null);
		op.filter(iconImage, iconImage);
		return new ImageIcon(iconImage);
	}

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

      bgQuotedOut = new javax.swing.ButtonGroup();
      bgQuotedIn = new javax.swing.ButtonGroup();
      bgCase = new javax.swing.ButtonGroup();
      splitPane = new javax.swing.JSplitPane();
      pnlInput = new javax.swing.JPanel();
      btnClear = new javax.swing.JButton();
      btnLoad = new javax.swing.JButton();
      scrollInput = new javax.swing.JScrollPane();
      txtInput = new javax.swing.JTextArea();
      lblExpression = new javax.swing.JLabel();
      txtRegex = new javax.swing.JTextField();
      spnColumns = new javax.swing.JSpinner();
      btnTsvInput = new javax.swing.JButton();
      btnCsvInput = new javax.swing.JButton();
      btnMatch = new javax.swing.JButton();
      scrollTable = new javax.swing.JScrollPane();
      jTable = new javax.swing.JTable();
      cbReplace = new javax.swing.JCheckBox();
      btnPivotWideToLong = new javax.swing.JButton();
      btnPivotLongToWide = new javax.swing.JButton();
      btnTranspose = new javax.swing.JButton();
      lblColumns = new javax.swing.JLabel();
      btnSaveExpr = new javax.swing.JButton();
      btnLoadExpr = new javax.swing.JButton();
      cbQuotedIn = new javax.swing.JCheckBox();
      rbDoubleIn = new javax.swing.JRadioButton();
      rbSingleIn = new javax.swing.JRadioButton();
      cbGuess = new javax.swing.JCheckBox();
      pnlOutput = new javax.swing.JPanel();
      lblFormat = new javax.swing.JLabel();
      txtFormat = new javax.swing.JTextField();
      btnFormat = new javax.swing.JButton();
      scrollOutput = new javax.swing.JScrollPane();
      txtOutput = new javax.swing.JTextArea();
      lblArgumentMarker = new javax.swing.JLabel();
      cbBlanks = new javax.swing.JCheckBox();
      txtBlanks = new javax.swing.JTextField();
      btnSwap = new javax.swing.JButton();
      btnCsvOutput = new javax.swing.JButton();
      btnTsvOutput = new javax.swing.JButton();
      txtMarker = new javax.swing.JTextField();
      btnSave = new javax.swing.JButton();
      cbTrim = new javax.swing.JCheckBox();
      cbQuotedOut = new javax.swing.JCheckBox();
      rbDoubleOut = new javax.swing.JRadioButton();
      rbSingleOut = new javax.swing.JRadioButton();
      cbCase = new javax.swing.JCheckBox();
      rbUpper = new javax.swing.JRadioButton();
      rbLower = new javax.swing.JRadioButton();
      pnlReplace = new jrex.ui.ReplacePanel();

      setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

      splitPane.setBorder(null);
      splitPane.setDividerLocation(366);
      splitPane.setDividerSize(15);
      splitPane.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT);
      splitPane.setName("splitPane"); // NOI18N
      splitPane.setOneTouchExpandable(true);

      pnlInput.setBorder(javax.swing.BorderFactory.createTitledBorder(null, "Input", javax.swing.border.TitledBorder.DEFAULT_JUSTIFICATION, javax.swing.border.TitledBorder.DEFAULT_POSITION, new java.awt.Font("Dialog", 0, 12))); // NOI18N
      pnlInput.setName("pnlInput"); // NOI18N

      btnClear.setText("Clear");
      btnClear.setName("btnClear"); // NOI18N
      btnClear.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(java.awt.event.ActionEvent evt) {
            btnClearActionPerformed(evt);
         }
      });

      btnLoad.setText("Open");
      btnLoad.setName("btnLoad"); // NOI18N
      btnLoad.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(java.awt.event.ActionEvent evt) {
            btnLoadActionPerformed(evt);
         }
      });

      scrollInput.setName("scrollInput"); // NOI18N

      txtInput.setColumns(20);
      txtInput.setFont(new java.awt.Font("Monospaced", 0, 12)); // NOI18N
      txtInput.setRows(5);
      txtInput.setName("txtInput"); // NOI18N
      scrollInput.setViewportView(txtInput);

      lblExpression.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT);
      lblExpression.setText("Capture Expression: ");
      lblExpression.setName("lblExpression"); // NOI18N

      txtRegex.setFont(new java.awt.Font("Monospaced", 0, 12)); // NOI18N
      txtRegex.setName("txtRegex"); // NOI18N
      txtRegex.addKeyListener(new java.awt.event.KeyAdapter() {
         public void keyPressed(java.awt.event.KeyEvent evt) {
            txtRegexKeyPressed(evt);
         }
      });

      spnColumns.setName("spnColumns"); // NOI18N
      spnColumns.addMouseWheelListener(new java.awt.event.MouseWheelListener() {
         public void mouseWheelMoved(java.awt.event.MouseWheelEvent evt) {
            spnColumnsMouseWheelMoved(evt);
         }
      });

      btnTsvInput.setText("TSV");
      btnTsvInput.setName("btnTsvInput"); // NOI18N
      btnTsvInput.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(java.awt.event.ActionEvent evt) {
            btnTsvInputActionPerformed(evt);
         }
      });

      btnCsvInput.setText("CSV");
      btnCsvInput.setName("btnCsvInput"); // NOI18N
      btnCsvInput.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(java.awt.event.ActionEvent evt) {
            btnCsvInputActionPerformed(evt);
         }
      });

      btnMatch.setMnemonic('c');
      btnMatch.setText("Capture");
      btnMatch.setName("btnMatch"); // NOI18N
      btnMatch.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(java.awt.event.ActionEvent evt) {
            btnMatchActionPerformed(evt);
         }
      });

      scrollTable.setName("scrollTable"); // NOI18N

      jTable.setName("jTable"); // NOI18N
      scrollTable.setViewportView(jTable);

      cbReplace.setText("Search/Replace ");
      cbReplace.setName("cbReplace"); // NOI18N
      cbReplace.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(java.awt.event.ActionEvent evt) {
            cbReplaceActionPerformed(evt);
         }
      });

      btnPivotWideToLong.setText("Pivot Wide to Long");
      btnPivotWideToLong.setName("btnPivotWideToLong"); // NOI18N
      btnPivotWideToLong.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(java.awt.event.ActionEvent evt) {
            btnPivotWideToLongActionPerformed(evt);
         }
      });

      btnPivotLongToWide.setText("Pivot Long to Wide");
      btnPivotLongToWide.setName("btnPivotLongToWide"); // NOI18N
      btnPivotLongToWide.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(java.awt.event.ActionEvent evt) {
            btnPivotLongToWideActionPerformed(evt);
         }
      });

      btnTranspose.setText("Transpose");
      btnTranspose.setName("btnTranspose"); // NOI18N
      btnTranspose.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(java.awt.event.ActionEvent evt) {
            btnTransposeActionPerformed(evt);
         }
      });

      lblColumns.setText("Columns: ");
      lblColumns.setName("lblColumns"); // NOI18N

      btnSaveExpr.setText("Save Expressions");
      btnSaveExpr.setName("btnSaveExpr"); // NOI18N
      btnSaveExpr.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(java.awt.event.ActionEvent evt) {
            btnSaveExprActionPerformed(evt);
         }
      });

      btnLoadExpr.setText("Load Expressions");
      btnLoadExpr.setName("btnLoadExpr"); // NOI18N
      btnLoadExpr.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(java.awt.event.ActionEvent evt) {
            btnLoadExprActionPerformed(evt);
         }
      });

      cbQuotedIn.setText("Quoted?");
      cbQuotedIn.setName("cbQuotedIn"); // NOI18N

      bgQuotedIn.add(rbDoubleIn);
      rbDoubleIn.setSelected(true);
      rbDoubleIn.setText("\"");
      rbDoubleIn.setName("rbDoubleIn"); // NOI18N

      bgQuotedIn.add(rbSingleIn);
      rbSingleIn.setText("'");
      rbSingleIn.setName("rbSingleIn"); // NOI18N

      cbGuess.setText("Guess?");
      cbGuess.setName("cbGuess"); // NOI18N

      javax.swing.GroupLayout pnlInputLayout = new javax.swing.GroupLayout(pnlInput);
      pnlInput.setLayout(pnlInputLayout);
      pnlInputLayout.setHorizontalGroup(
         pnlInputLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
         .addGroup(pnlInputLayout.createSequentialGroup()
            .addComponent(lblColumns)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(spnColumns, javax.swing.GroupLayout.PREFERRED_SIZE, 46, javax.swing.GroupLayout.PREFERRED_SIZE)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(cbGuess)
            .addGap(18, 18, 18)
            .addComponent(cbQuotedIn)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(rbDoubleIn)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(rbSingleIn)
            .addGap(18, 18, 18)
            .addComponent(btnCsvInput)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(btnTsvInput)
            .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
         .addComponent(scrollInput)
         .addGroup(pnlInputLayout.createSequentialGroup()
            .addComponent(btnLoad)
            .addGap(18, 18, 18)
            .addComponent(cbReplace)
            .addGap(18, 18, 18)
            .addComponent(btnSaveExpr)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(btnLoadExpr)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 616, Short.MAX_VALUE)
            .addComponent(btnClear))
         .addGroup(pnlInputLayout.createSequentialGroup()
            .addGroup(pnlInputLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
               .addGroup(pnlInputLayout.createSequentialGroup()
                  .addComponent(btnPivotWideToLong)
                  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                  .addComponent(btnPivotLongToWide)
                  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                  .addComponent(btnTranspose))
               .addGroup(pnlInputLayout.createSequentialGroup()
                  .addComponent(lblExpression)
                  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                  .addComponent(txtRegex)))
            .addGap(18, 18, 18)
            .addComponent(btnMatch))
         .addComponent(scrollTable)
      );

      pnlInputLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {btnPivotLongToWide, btnPivotWideToLong, btnTranspose});

      pnlInputLayout.setVerticalGroup(
         pnlInputLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
         .addGroup(pnlInputLayout.createSequentialGroup()
            .addGroup(pnlInputLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
               .addComponent(btnLoad, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
               .addComponent(btnClear, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
               .addComponent(cbReplace)
               .addComponent(btnSaveExpr)
               .addComponent(btnLoadExpr))
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(scrollInput, javax.swing.GroupLayout.DEFAULT_SIZE, 107, Short.MAX_VALUE)
            .addGap(11, 11, 11)
            .addGroup(pnlInputLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
               .addComponent(lblColumns)
               .addComponent(spnColumns, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
               .addComponent(cbQuotedIn)
               .addComponent(btnCsvInput)
               .addComponent(btnTsvInput)
               .addComponent(rbDoubleIn)
               .addComponent(rbSingleIn)
               .addComponent(cbGuess))
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addGroup(pnlInputLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
               .addComponent(lblExpression)
               .addComponent(txtRegex, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
               .addComponent(btnMatch))
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(scrollTable, javax.swing.GroupLayout.DEFAULT_SIZE, 99, Short.MAX_VALUE)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addGroup(pnlInputLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
               .addComponent(btnPivotWideToLong)
               .addComponent(btnPivotLongToWide)
               .addComponent(btnTranspose)))
      );

      splitPane.setTopComponent(pnlInput);

      pnlOutput.setBorder(javax.swing.BorderFactory.createTitledBorder(null, "Output", javax.swing.border.TitledBorder.DEFAULT_JUSTIFICATION, javax.swing.border.TitledBorder.DEFAULT_POSITION, new java.awt.Font("Dialog", 0, 12))); // NOI18N
      pnlOutput.setName("pnlOutput"); // NOI18N

      lblFormat.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT);
      lblFormat.setText("Format Expression: ");
      lblFormat.setName("lblFormat"); // NOI18N

      txtFormat.setName("txtFormat"); // NOI18N

      btnFormat.setMnemonic('f');
      btnFormat.setText("Format");
      btnFormat.setName("btnFormat"); // NOI18N
      btnFormat.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(java.awt.event.ActionEvent evt) {
            btnFormatActionPerformed(evt);
         }
      });

      scrollOutput.setName("scrollOutput"); // NOI18N

      txtOutput.setColumns(20);
      txtOutput.setFont(new java.awt.Font("Monospaced", 0, 12)); // NOI18N
      txtOutput.setRows(5);
      txtOutput.setName("txtOutput"); // NOI18N
      scrollOutput.setViewportView(txtOutput);

      lblArgumentMarker.setText("Column Prefix: ");
      lblArgumentMarker.setName("lblArgumentMarker"); // NOI18N

      cbBlanks.setText("Replace blanks? ");
      cbBlanks.setName("cbBlanks"); // NOI18N

      txtBlanks.setText("null");
      txtBlanks.setName("txtBlanks"); // NOI18N

      btnSwap.setMnemonic('o');
      btnSwap.setText("Output to Input");
      btnSwap.setName("btnSwap"); // NOI18N
      btnSwap.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(java.awt.event.ActionEvent evt) {
            btnSwapActionPerformed(evt);
         }
      });

      btnCsvOutput.setText("CSV");
      btnCsvOutput.setName("btnCsvOutput"); // NOI18N
      btnCsvOutput.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(java.awt.event.ActionEvent evt) {
            btnCsvOutputActionPerformed(evt);
         }
      });

      btnTsvOutput.setText("TSV");
      btnTsvOutput.setName("btnTsvOutput"); // NOI18N
      btnTsvOutput.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(java.awt.event.ActionEvent evt) {
            btnTsvOutputActionPerformed(evt);
         }
      });

      txtMarker.setText("?");
      txtMarker.setName("txtMarker"); // NOI18N

      btnSave.setText("Save");
      btnSave.setName("btnSave"); // NOI18N
      btnSave.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(java.awt.event.ActionEvent evt) {
            btnSaveActionPerformed(evt);
         }
      });

      cbTrim.setText("Trim?");
      cbTrim.setName("cbTrim"); // NOI18N

      cbQuotedOut.setText("Quoted?");
      cbQuotedOut.setName("cbQuotedOut"); // NOI18N

      bgQuotedOut.add(rbDoubleOut);
      rbDoubleOut.setSelected(true);
      rbDoubleOut.setText("\"");
      rbDoubleOut.setName("rbDoubleOut"); // NOI18N

      bgQuotedOut.add(rbSingleOut);
      rbSingleOut.setText("'");
      rbSingleOut.setName("rbSingleOut"); // NOI18N

      cbCase.setText("Transform Case?");
      cbCase.setName("cbCase"); // NOI18N

      bgCase.add(rbUpper);
      rbUpper.setSelected(true);
      rbUpper.setText("Upper");
      rbUpper.setName("rbUpper"); // NOI18N

      bgCase.add(rbLower);
      rbLower.setText("Lower");
      rbLower.setName("rbLower"); // NOI18N

      javax.swing.GroupLayout pnlOutputLayout = new javax.swing.GroupLayout(pnlOutput);
      pnlOutput.setLayout(pnlOutputLayout);
      pnlOutputLayout.setHorizontalGroup(
         pnlOutputLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
         .addComponent(scrollOutput)
         .addGroup(pnlOutputLayout.createSequentialGroup()
            .addComponent(lblArgumentMarker)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(txtMarker, javax.swing.GroupLayout.PREFERRED_SIZE, 32, javax.swing.GroupLayout.PREFERRED_SIZE)
            .addGap(18, 18, 18)
            .addComponent(cbBlanks)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(txtBlanks, javax.swing.GroupLayout.PREFERRED_SIZE, 62, javax.swing.GroupLayout.PREFERRED_SIZE)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
            .addComponent(btnCsvOutput)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(btnTsvOutput, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 470, Short.MAX_VALUE)
            .addComponent(btnSwap)
            .addGap(18, 18, 18)
            .addComponent(btnSave))
         .addGroup(pnlOutputLayout.createSequentialGroup()
            .addComponent(lblFormat)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(txtFormat)
            .addGap(18, 18, 18)
            .addComponent(btnFormat))
         .addGroup(pnlOutputLayout.createSequentialGroup()
            .addComponent(cbTrim)
            .addGap(18, 18, 18)
            .addComponent(cbQuotedOut)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(rbDoubleOut)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(rbSingleOut)
            .addGap(18, 18, 18)
            .addComponent(cbCase)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(rbUpper)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(rbLower)
            .addGap(0, 0, Short.MAX_VALUE))
      );

      pnlOutputLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {btnCsvOutput, btnTsvOutput});

      pnlOutputLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {txtBlanks, txtMarker});

      pnlOutputLayout.setVerticalGroup(
         pnlOutputLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
         .addGroup(pnlOutputLayout.createSequentialGroup()
            .addGroup(pnlOutputLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
               .addComponent(lblArgumentMarker)
               .addComponent(txtMarker, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
               .addComponent(cbBlanks)
               .addComponent(txtBlanks, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
               .addComponent(btnCsvOutput)
               .addComponent(btnTsvOutput)
               .addComponent(btnSwap)
               .addComponent(btnSave))
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addGroup(pnlOutputLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
               .addComponent(cbTrim)
               .addGroup(pnlOutputLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
                  .addComponent(cbQuotedOut)
                  .addComponent(rbDoubleOut)
                  .addComponent(rbSingleOut)
                  .addComponent(cbCase)
                  .addComponent(rbUpper)
                  .addComponent(rbLower)))
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addGroup(pnlOutputLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
               .addComponent(lblFormat)
               .addComponent(txtFormat, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
               .addComponent(btnFormat))
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(scrollOutput, javax.swing.GroupLayout.DEFAULT_SIZE, 165, Short.MAX_VALUE))
      );

      splitPane.setRightComponent(pnlOutput);

      pnlReplace.setName("pnlReplace"); // NOI18N

      javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
      getContentPane().setLayout(layout);
      layout.setHorizontalGroup(
         layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
         .addGroup(layout.createSequentialGroup()
            .addContainerGap()
            .addComponent(splitPane)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(pnlReplace, javax.swing.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE))
      );
      layout.setVerticalGroup(
         layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
         .addGroup(layout.createSequentialGroup()
            .addContainerGap()
            .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
               .addComponent(pnlReplace, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
               .addComponent(splitPane))
            .addContainerGap())
      );

      pack();
   }// </editor-fold>//GEN-END:initComponents

	private void match() {
		txtOutput.setText("");
		String regex = txtRegex.getText();
		String text = txtInput.getText();

		String[] input = null;

		try {

			Pattern pattern = Pattern.compile(regex);

			int flagsMask = pattern.flags();

			StringBuilder flags = new StringBuilder();

			boolean dotall = false;

			// work out which flags are being used
			if ((flagsMask & Pattern.DOTALL) == Pattern.DOTALL) {
				flags.append("DOTALL ");
				dotall = true;
			}

			if ((flagsMask & Pattern.MULTILINE) == Pattern.MULTILINE) {
				flags.append("MULTILINE ");
			}

			if ((flagsMask & Pattern.CASE_INSENSITIVE) == Pattern.CASE_INSENSITIVE) {
				flags.append("CASE_INSENSITIVE ");
			}

			PivotTableModel model = new PivotTableModel();
			jTable.setModel(model);

			int numMatches = 0;

			// it DOTALL mode is on then grab the entire text rather then seperating by line
			if (dotall) {
				input = new String[1];
				input[0] = text;
			} else {
				input = text.split("\n");
			}

			for (String line : input) {
				Matcher matcher = pattern.matcher(line);
				while (matcher.find()) {

					// group() matches the entire line if there was a match.
					// This if-statment is to suppress extra empty groups from being
					// produced if the regex matches the entire line
					if (matcher.group().length() != line.length()) {
						continue;
					}

					numMatches++;

					txtOutput.append(matcher.group(0) + "\n");

					String[] groups = new String[matcher.groupCount()];
					for (int j = 1; j <= matcher.groupCount(); j++) {
						String s = matcher.group(j);
						groups[j - 1] = s;
					}

					model.addRow(groups);
				}
			}

			model.fireTableStructureChanged();

			txtOutput.append("\nMatched " + numMatches + " line(s) out of "
				+ input.length + " line(s) of input.");

			if (!flags.toString().isEmpty()) {
				txtOutput.append("\n\nFlags: " + flags.toString().trim());
			}

		} catch (PatternSyntaxException ex) {
			txtOutput.setText("");
			txtOutput.append(ex.getMessage());
			txtRegex.setBackground(new Color(1f, 0.3f, 0.3f));
		}

	}

	private void format() {
		String marker = txtMarker.getText();

		txtOutput.setText("");

		TableModel model = jTable.getModel();

		String formatter = this.txtFormat.getText().replaceAll("\\" + marker + "(\\d*)", "%$1\\$s");

		for (int row = 0; row < model.getRowCount(); row++) {

			Object[] values = new Object[model.getColumnCount()];

			for (int col = 0; col < model.getColumnCount(); col++) {
				String val = (String) model.getValueAt(row, col);

				if (cbBlanks.isSelected() && val.isEmpty()) {
					val = txtBlanks.getText();
				}

				if (cbTrim.isSelected()) {
					val = val.trim();
				}

				if (cbCase.isSelected()) {
					if (rbUpper.isSelected()) {
						val = val.toUpperCase();
					} else {
						val = val.toLowerCase();
					}
				}

				values[col] = val;
			}

			String formatted = String.format(formatter, values);
			txtOutput.append(formatted + "\n");
		}

	}

	private void transpose() {
		PivotTableModel model = (PivotTableModel) jTable.getModel();
		model.transpose();
	}

	private void pivotWideToLong() {
		PivotTableModel model = (PivotTableModel) jTable.getModel();
		ListMultimap<String, String> columns = new WideToLongPivotDialog(this).mapColumns(model);
		if (columns != null) {
			model.pivotWideToLong(columns.get("preserved"), columns.get("values"));
		}
	}

	private void pivotLongToWide() {
		PivotTableModel model = (PivotTableModel) jTable.getModel();
		ListMultimap<String, String> columns = new LongToWidePivotDialog(this).mapColumns(model);
		if (columns != null) {
			model.pivotLongToWide(columns.get("repeating"), columns.get("header").get(0), columns.get("value").get(0));
		}
	}

	private void csv() {
		Integer cols = (Integer) spnColumns.getValue();
		Boolean quoted = cbQuotedIn.isSelected();
		String quote = rbDoubleIn.isSelected() ? "\"" : "'";

		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < cols - 1; i++) {
			if (quoted) {
				sb.append(quote).append("(.*?)").append(quote).append(",");
			} else {
				sb.append("(.*?),");
			}
		}
		if (quoted) {

			sb.append(quote).append("(.*?)").append(quote);
		} else {
			sb.append("(.*)");
		}

		txtRegex.setText(sb.toString());
	}

	private void tsv() {
		Integer cols = (Integer) spnColumns.getValue();
		Boolean quoted = cbQuotedIn.isSelected();
		String quote = rbDoubleIn.isSelected() ? "\"" : "'";

		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < cols - 1; i++) {
			if (quoted) {
				sb.append(quote).append("(.*?)").append(quote).append("\\t");
			} else {
				sb.append("(.*?)\\t");
			}
		}
		if (quoted) {
			sb.append(quote).append("(.*?)").append(quote);
		} else {
			sb.append("(.*)");
		}

		txtRegex.setText(sb.toString());
	}

	private void load() {
		JFileChooser jfc = new JFileChooser();
		if (jfc.showOpenDialog(MainFrame.this) == JFileChooser.APPROVE_OPTION) {
			try {
				String data = readFile(jfc.getSelectedFile());

				txtInput.setText(data);

			} catch (Exception ex) {
				JOptionPane.showMessageDialog(this, "Error", ex.getMessage(), JOptionPane.ERROR_MESSAGE);
			}
		}
	}

	private void save() {
		JFileChooser jfc = new JFileChooser();
		int result = jfc.showSaveDialog(this);
		if (result == JFileChooser.APPROVE_OPTION) {
			File f = jfc.getSelectedFile();
			if (f.exists()) {
				int confirm = JOptionPane.showConfirmDialog(this, "Overwrite existing file?");
				if (confirm == JOptionPane.YES_OPTION) {
					try (BufferedWriter writer = new BufferedWriter(new FileWriter(f))) {
						writer.write(txtOutput.getText());
					} catch (Exception e) {
						JOptionPane.showMessageDialog(this, "Error", e.getMessage(), JOptionPane.ERROR_MESSAGE);
					}
				}
			} else {
				try (BufferedWriter writer = new BufferedWriter(new FileWriter(f))) {
					writer.write(txtOutput.getText());
				} catch (Exception e) {
					JOptionPane.showMessageDialog(this, "Error", e.getMessage(), JOptionPane.ERROR_MESSAGE);
				}
			}
		}
	}

	public String readFile(File file) throws Exception {
		StringBuilder fileContents;
		try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
			@SuppressWarnings("UnusedAssignment")
			String line = null;
			fileContents = new StringBuilder();
			while ((line = reader.readLine()) != null) {
				fileContents.append(line);
				fileContents.append(System.getProperty("line.separator"));
			}
		}
		return fileContents.toString();
	}

   private void btnFormatActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnFormatActionPerformed
		format();
   }//GEN-LAST:event_btnFormatActionPerformed

   private void btnTransposeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnTransposeActionPerformed
		transpose();
   }//GEN-LAST:event_btnTransposeActionPerformed

   private void btnMatchActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnMatchActionPerformed
		match();
   }//GEN-LAST:event_btnMatchActionPerformed

   private void btnCsvInputActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnCsvInputActionPerformed
		if (cbGuess.isSelected()) {
			String line = txtInput.getText().split("\n")[0];
			String commas = line.replaceAll("[^,]", "");
			spnColumns.setValue(commas.length() + 1);
		}
		csv();
		match();
   }//GEN-LAST:event_btnCsvInputActionPerformed

   private void btnTsvInputActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnTsvInputActionPerformed
		if (cbGuess.isSelected()) {
			String line = txtInput.getText().split("\n")[0];
			String commas = line.replaceAll("[^\t]", "");
			spnColumns.setValue(commas.length() + 1);
		}
		tsv();
		match();
   }//GEN-LAST:event_btnTsvInputActionPerformed

   private void btnLoadActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnLoadActionPerformed
		load();
   }//GEN-LAST:event_btnLoadActionPerformed

   private void btnClearActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnClearActionPerformed
		txtInput.setText("");
   }//GEN-LAST:event_btnClearActionPerformed

   private void spnColumnsMouseWheelMoved(java.awt.event.MouseWheelEvent evt) {//GEN-FIRST:event_spnColumnsMouseWheelMoved
		Integer value = (Integer) spnColumns.getValue();
		value -= evt.getUnitsToScroll();
		spnColumns.setValue(value > 0 ? value : 0);
   }//GEN-LAST:event_spnColumnsMouseWheelMoved

   private void btnPivotWideToLongActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnPivotWideToLongActionPerformed
		pivotWideToLong();
   }//GEN-LAST:event_btnPivotWideToLongActionPerformed

   private void txtRegexKeyPressed(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_txtRegexKeyPressed
		// restore default color
		txtRegex.setBackground(defaultTextColor);
   }//GEN-LAST:event_txtRegexKeyPressed

   private void cbReplaceActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbReplaceActionPerformed
		pnlReplace.setVisible(cbReplace.isSelected());
   }//GEN-LAST:event_cbReplaceActionPerformed

   private void btnSwapActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnSwapActionPerformed
		txtInput.setText(txtOutput.getText());
   }//GEN-LAST:event_btnSwapActionPerformed

   private void btnCsvOutputActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnCsvOutputActionPerformed
		String marker = txtMarker.getText();

		Integer cols = (Integer) jTable.getColumnCount();

		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < cols; i++) {
			if (cbQuotedOut.isSelected()) {
				String quote = rbDoubleOut.isSelected() ? "\"" : "'";
				sb.append(quote).append(marker).append(i + 1).append(quote).append(",");
			} else {
				sb.append(marker).append(i + 1).append(",");
			}
		}

		String format = sb.toString();

		txtFormat.setText(format.substring(0, format.length() - 1));

		format();
   }//GEN-LAST:event_btnCsvOutputActionPerformed

   private void btnTsvOutputActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnTsvOutputActionPerformed
		String marker = txtMarker.getText();

		Integer cols = (Integer) jTable.getColumnCount();

		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < cols; i++) {
			if (cbQuotedOut.isSelected()) {
				String quote = rbDoubleOut.isSelected() ? "\"" : "'";
				sb.append(quote).append(marker).append(i + 1).append(quote).append("\t");
			} else {
				sb.append(marker).append(i + 1).append("\t");
			}
		}

		String format = sb.toString();

		txtFormat.setText(format.substring(0, format.length() - 1));

		format();
   }//GEN-LAST:event_btnTsvOutputActionPerformed

   private void btnSaveActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnSaveActionPerformed
		save();
   }//GEN-LAST:event_btnSaveActionPerformed

   private void btnPivotLongToWideActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnPivotLongToWideActionPerformed
		pivotLongToWide();
   }//GEN-LAST:event_btnPivotLongToWideActionPerformed

   private void btnSaveExprActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnSaveExprActionPerformed

		Expressions exp = new Expressions();
		exp.setBlanks(txtBlanks.getText());
		exp.setBlanksEnabled(cbBlanks.isSelected());
		exp.setCapture(txtRegex.getText());
		exp.setFormat(txtFormat.getText());
		exp.setPrefix(txtMarker.getText());
		exp.setReplacements(pnlReplace.getModel().getRows());
		String json = new Gson().toJson(exp);

		JFileChooser jfc = new JFileChooser();
		int result = jfc.showSaveDialog(this);
		if (result == JFileChooser.APPROVE_OPTION) {
			File f = jfc.getSelectedFile();
			if (f.exists()) {
				int confirm = JOptionPane.showConfirmDialog(this, "Overwrite existing file?");
				if (confirm == JOptionPane.YES_OPTION) {
					try (BufferedWriter writer = new BufferedWriter(new FileWriter(f))) {
						writer.write(json);
					} catch (Exception e) {
						JOptionPane.showMessageDialog(this, "Error", e.getMessage(), JOptionPane.ERROR_MESSAGE);
					}
				}
			} else {
				try (BufferedWriter writer = new BufferedWriter(new FileWriter(f))) {
					writer.write(json);
				} catch (Exception e) {
					JOptionPane.showMessageDialog(this, "Error", e.getMessage(), JOptionPane.ERROR_MESSAGE);
				}
			}
		}

   }//GEN-LAST:event_btnSaveExprActionPerformed

   private void btnLoadExprActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnLoadExprActionPerformed
		String json = null;

		JFileChooser jfc = new JFileChooser();
		if (jfc.showOpenDialog(MainFrame.this) == JFileChooser.APPROVE_OPTION) {
			try {
				json = readFile(jfc.getSelectedFile());
			} catch (Exception ex) {
				JOptionPane.showMessageDialog(this, "Error", ex.getMessage(), JOptionPane.ERROR_MESSAGE);
			}
		}

		Expressions exp = new Gson().fromJson(json, Expressions.class);
		txtBlanks.setText(exp.getBlanks());
		cbBlanks.setSelected(exp.getBlanksEnabled());
		txtRegex.setText(exp.getCapture());
		txtFormat.setText(exp.getFormat());
		txtMarker.setText(exp.getPrefix());

		List<List<String>> replacements = exp.getReplacements();
		MutableTableModel model = pnlReplace.getModel();
		model.getTable().clear();
		for (List<String> replacement : replacements) {
			model.addRow(replacement);
		}

   }//GEN-LAST:event_btnLoadExprActionPerformed

	public String getPrefix() {
		return txtMarker.getText();
	}


   // Variables declaration - do not modify//GEN-BEGIN:variables
   private javax.swing.ButtonGroup bgCase;
   private javax.swing.ButtonGroup bgQuotedIn;
   private javax.swing.ButtonGroup bgQuotedOut;
   private javax.swing.JButton btnClear;
   private javax.swing.JButton btnCsvInput;
   private javax.swing.JButton btnCsvOutput;
   private javax.swing.JButton btnFormat;
   private javax.swing.JButton btnLoad;
   private javax.swing.JButton btnLoadExpr;
   private javax.swing.JButton btnMatch;
   private javax.swing.JButton btnPivotLongToWide;
   private javax.swing.JButton btnPivotWideToLong;
   private javax.swing.JButton btnSave;
   private javax.swing.JButton btnSaveExpr;
   private javax.swing.JButton btnSwap;
   private javax.swing.JButton btnTranspose;
   private javax.swing.JButton btnTsvInput;
   private javax.swing.JButton btnTsvOutput;
   private javax.swing.JCheckBox cbBlanks;
   private javax.swing.JCheckBox cbCase;
   private javax.swing.JCheckBox cbGuess;
   private javax.swing.JCheckBox cbQuotedIn;
   private javax.swing.JCheckBox cbQuotedOut;
   private javax.swing.JCheckBox cbReplace;
   private javax.swing.JCheckBox cbTrim;
   private javax.swing.JTable jTable;
   private javax.swing.JLabel lblArgumentMarker;
   private javax.swing.JLabel lblColumns;
   private javax.swing.JLabel lblExpression;
   private javax.swing.JLabel lblFormat;
   private javax.swing.JPanel pnlInput;
   private javax.swing.JPanel pnlOutput;
   private jrex.ui.ReplacePanel pnlReplace;
   private javax.swing.JRadioButton rbDoubleIn;
   private javax.swing.JRadioButton rbDoubleOut;
   private javax.swing.JRadioButton rbLower;
   private javax.swing.JRadioButton rbSingleIn;
   private javax.swing.JRadioButton rbSingleOut;
   private javax.swing.JRadioButton rbUpper;
   private javax.swing.JScrollPane scrollInput;
   private javax.swing.JScrollPane scrollOutput;
   private javax.swing.JScrollPane scrollTable;
   private javax.swing.JSplitPane splitPane;
   private javax.swing.JSpinner spnColumns;
   private javax.swing.JTextField txtBlanks;
   private javax.swing.JTextField txtFormat;
   private javax.swing.JTextArea txtInput;
   private javax.swing.JTextField txtMarker;
   private javax.swing.JTextArea txtOutput;
   private javax.swing.JTextField txtRegex;
   // End of variables declaration//GEN-END:variables

}
