/*
 * Copyright 2008 Ayman Al-Sairafi ayman.alsairafi@gmail.com
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License 
 *       at http://www.apache.org/licenses/LICENSE-2.0 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License.  
 */
package jsyntaxpane.actions;

import java.awt.event.ContainerEvent;
import java.awt.event.KeyEvent;
import jsyntaxpane.components.Markers;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.HeadlessException;
import java.awt.event.ContainerListener;
import java.awt.event.KeyListener;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.swing.JOptionPane;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.text.JTextComponent;

/**
 * A Find and Replace Dialog.  The dialog will also act as a listener to
 * Document changes so that all highlights are updated if the document is
 * changed.
 * 
 * @author Ayman Al-Sairafi
 */
public class ReplaceDialog extends javax.swing.JDialog implements CaretListener, KeyListener, ContainerListener {

    private JTextComponent textComponent;
    private FindReplaceActions finder;
    private static Markers.SimpleMarker SEARCH_MARKER = new Markers.SimpleMarker(Color.YELLOW);

    /** Creates new form FindDialog */
    public ReplaceDialog(JTextComponent text,
            FindReplaceActions finderActions) {
        super(ActionUtils.getFrameFor(text), false);
        initComponents();
        registerKeyAction(this);
        textComponent = text;
        finder = finderActions;
        textComponent.addCaretListener(this);
        setLocationRelativeTo(text.getRootPane());
    }

    /**
     * updates the highlights in the document when it is updated.
     * This is called by the DocumentListener methods
     */
    public void updateHighlights() {
        Markers.removeMarkers(textComponent, SEARCH_MARKER);
        if (jTglHighlight.isSelected()) {
            Markers.markAll(textComponent, finder.getPattern(), SEARCH_MARKER);
        }
    }

    private void showRegexpError(PatternSyntaxException ex) throws HeadlessException {
        JOptionPane.showMessageDialog(this, "Regexp error: " + ex.getMessage(),
                "Regular Expression Error", JOptionPane.ERROR_MESSAGE);
        jCmbFind.requestFocus();
    }

    /**
     * update the finder object with data from our UI
     */
    private void updateFinder() {
        int flag = 0;
        if (!jChkRegex.isSelected()) {
            flag |= Pattern.LITERAL;
        }
        flag |= (jChkIgnoreCase.isSelected()) ? Pattern.CASE_INSENSITIVE : 0;
        if (jChkIgnoreCase.isSelected()) {
            flag |= Pattern.CASE_INSENSITIVE;
        }
        String regex = (String) jCmbFind.getSelectedItem();
        if (regex != null && regex.length() > 0) {
            Pattern pattern = Pattern.compile(regex, flag);
            finder.setWrap(jChkWrap.isSelected());
            finder.setPattern(pattern);
            ActionUtils.insertIntoCombo(jCmbFind, regex);
        } else {
            finder.setPattern(null);
        }
    }

    /** 
     * 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() {

        jLabel1 = new javax.swing.JLabel();
        jBtnNext = new javax.swing.JButton();
        jLblStatus = new javax.swing.JLabel();
        jBtnReplaceAll = new javax.swing.JButton();
        jChkWrap = new javax.swing.JCheckBox();
        jChkRegex = new javax.swing.JCheckBox();
        jChkIgnoreCase = new javax.swing.JCheckBox();
        jLabel2 = new javax.swing.JLabel();
        jTglHighlight = new javax.swing.JToggleButton();
        jCmbReplace = new javax.swing.JComboBox();
        jCmbFind = new javax.swing.JComboBox();

        setTitle("Find and Replace");
        setName(""); // NOI18N
        setResizable(false);

        jLabel1.setText("Find");

        jBtnNext.setMnemonic('N');
        jBtnNext.setText("Next");
        jBtnNext.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jBtnNextActionPerformed(evt);
            }
        });

        jBtnReplaceAll.setMnemonic('H');
        jBtnReplaceAll.setText("Replace All");
        jBtnReplaceAll.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jBtnReplaceAllActionPerformed(evt);
            }
        });

        jChkWrap.setMnemonic('W');
        jChkWrap.setText("Wrap around");
        jChkWrap.setToolTipText("Wrap to beginning when end is reached");

        jChkRegex.setMnemonic('R');
        jChkRegex.setText("Regular Expression");

        jChkIgnoreCase.setMnemonic('I');
        jChkIgnoreCase.setText("Ignore Case");

        jLabel2.setText("Replace");

        jTglHighlight.setText("Highlight");
        jTglHighlight.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jTglHighlightActionPerformed(evt);
            }
        });

        jCmbReplace.setEditable(true);

        jCmbFind.setEditable(true);

        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()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                    .addComponent(jLabel2)
                    .addComponent(jLabel1))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addComponent(jChkRegex)
                            .addComponent(jChkIgnoreCase))
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addGroup(layout.createSequentialGroup()
                                .addGap(178, 178, 178)
                                .addComponent(jLblStatus, javax.swing.GroupLayout.DEFAULT_SIZE, 39, Short.MAX_VALUE)
                                .addGap(3, 3, 3))
                            .addGroup(layout.createSequentialGroup()
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(jChkWrap, javax.swing.GroupLayout.PREFERRED_SIZE, 105, javax.swing.GroupLayout.PREFERRED_SIZE))))
                    .addComponent(jCmbFind, 0, 337, Short.MAX_VALUE)
                    .addComponent(jCmbReplace, 0, 337, Short.MAX_VALUE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                    .addComponent(jBtnReplaceAll, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 102, Short.MAX_VALUE)
                    .addComponent(jBtnNext, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 102, Short.MAX_VALUE)
                    .addComponent(jTglHighlight, javax.swing.GroupLayout.DEFAULT_SIZE, 102, Short.MAX_VALUE))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addGap(29, 29, 29)
                        .addComponent(jLblStatus))
                    .addGroup(layout.createSequentialGroup()
                        .addContainerGap()
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                                .addComponent(jLabel1)
                                .addComponent(jCmbFind, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                            .addComponent(jBtnNext))
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                            .addComponent(jLabel2)
                            .addComponent(jBtnReplaceAll)
                            .addComponent(jCmbReplace, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                            .addComponent(jChkRegex)
                            .addComponent(jChkWrap, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
                            .addComponent(jTglHighlight))))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jChkIgnoreCase)
                .addContainerGap())
        );

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

    private void jBtnNextActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jBtnNextActionPerformed
        try {
            updateFinder();
            finder.doFindNext(textComponent);
            textComponent.requestFocusInWindow();
        } catch (PatternSyntaxException ex) {
            showRegexpError(ex);
        }
    }//GEN-LAST:event_jBtnNextActionPerformed

    private void jBtnReplaceAllActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jBtnReplaceAllActionPerformed
        try {
            updateFinder();
            String replacement = (String) jCmbReplace.getSelectedItem();
            ActionUtils.insertIntoCombo(jCmbFind, replacement);
            finder.replaceAll(textComponent, replacement);
            textComponent.requestFocusInWindow();
        } catch (PatternSyntaxException ex) {
            showRegexpError(ex);
        }
}//GEN-LAST:event_jBtnReplaceAllActionPerformed

    private void jTglHighlightActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jTglHighlightActionPerformed
        updateFinder();
        updateHighlights();
    }//GEN-LAST:event_jTglHighlightActionPerformed

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton jBtnNext;
    private javax.swing.JButton jBtnReplaceAll;
    private javax.swing.JCheckBox jChkIgnoreCase;
    private javax.swing.JCheckBox jChkRegex;
    private javax.swing.JCheckBox jChkWrap;
    private javax.swing.JComboBox jCmbFind;
    private javax.swing.JComboBox jCmbReplace;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JLabel jLabel2;
    private javax.swing.JLabel jLblStatus;
    private javax.swing.JToggleButton jTglHighlight;
    // End of variables declaration//GEN-END:variables

    public void caretUpdate(CaretEvent e) {
        updateHighlights();
    }

    public void keyTyped(KeyEvent arg0) {
        
    }

    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
            this.setVisible(false);
        }
    }

    public void keyReleased(KeyEvent arg0) {
        
    }

    public void componentAdded(ContainerEvent e) {
        registerKeyAction(e.getChild());
    }

    public void componentRemoved(ContainerEvent e) {
        registerKeyAction(e.getChild());
    }

    private void registerKeyAction(Component c) {
        if (c instanceof ReplaceDialog == false) {
            c.removeKeyListener(this);
            c.addKeyListener(this);
        }

        if (c instanceof Container) {
            Container cnt = (Container) c;
            cnt.removeContainerListener(this);
            cnt.addContainerListener(this);
            Component[] ch = cnt.getComponents();
            for (int i = 0; i < ch.length; i++) {
                registerKeyAction(ch[i]);
            }
        }
    }
}
