/**************************************************************************
 OmegaT - Computer Assisted Translation (CAT) tool 
          with fuzzy matching, translation memory, keyword search, 
          glossaries, and translation leveraging into updated projects.

 Copyright (C) 2012 Thomas Cordonnier
               2013 Aaron Madlon-Kay, Alex Buloichik
               2014-2020 Thomas Cordonnier
               Home page: http://www.omegat.org/
               Support center: http://groups.yahoo.com/group/OmegaT/

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 **************************************************************************/

package org.omegat.gui.search;

import java.text.MessageFormat;

import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.TreeSet;
import java.util.List;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import javax.swing.AbstractButton;
import javax.swing.Box;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JCheckBox;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JRadioButton;
import javax.swing.JTextField;

import org.openide.awt.Mnemonics;
import javax.swing.KeyStroke;
import javax.swing.text.JTextComponent;

import org.omegat.core.Core;
import org.omegat.core.search.Searcher;
import org.omegat.core.search.ReplaceSearcher;
import org.omegat.core.search.TextExpression;
import org.omegat.core.search.TranslationStateFilter;
import org.omegat.core.search.SearchResultEntry;
import org.omegat.core.search.SearchMatch;
import org.omegat.core.search.ReplaceMatch;
import org.omegat.gui.main.MainWindow;
import org.omegat.gui.editor.filter.ReplaceFilter;
import org.omegat.util.OStrings;
import org.omegat.util.gui.UIThreadsUtil;
import org.omegat.util.Preferences;

/**
 * Dialog which presents text to be searched, some options as in SearchWindow, and replacement.
 * 
 * @author Thomas CORDONNIER
 * @author Aaron Madlon-Kay
 * @author Alex Buloichik
 */
public class ReplaceDialog extends ProjectSearchWindow {
    public ReplaceDialog(MainWindow par, String startText, String replaceText) {
        super(par, startText);
        setTitle(OStrings.getString("SW_TITLE_REPLACE"));

        refreshLists(true); // after all components, in particular the m_modePanel, have been built
        if (startText != null) getMainSearchTextField().setSelectedItem(startText);
        if (replaceText != null) m_replaceField.setSelectedItem(replaceText);
		
		for (JComboBox box: new JComboBox[]{ m_searchField, m_replaceField }) {
            JTextComponent searchFieldArea = (JTextComponent) box.getEditor().getEditorComponent();
            searchFieldArea.addMouseListener(new org.omegat.gui.editor.PopupMenuDisplayer(searchFieldArea));
        }
        m_modePanel.addModeChangeListener(new RegexModeSwitchListener(m_modePanel, ((javax.swing.text.JTextComponent) m_searchField.getEditor().getEditorComponent())));
		m_replaceWithVars.addActionListener (new java.awt.event.ActionListener() {
			private javax.swing.text.JTextComponent receiver = ((javax.swing.text.JTextComponent) m_replaceField.getEditor().getEditorComponent());
			private org.omegat.util.gui.RegexHighlightListener listener = new org.omegat.util.gui.RegexHighlightListener(receiver, org.omegat.util.gui.RegexHighlightListener.MODE_REGEX_REPLACE);
			
			{ actionPerformed(null); }
			
			public void actionPerformed (ActionEvent ev) {
				if (m_replaceWithVars.isSelected()) receiver.getDocument().addDocumentListener (listener); else receiver.getDocument().removeDocumentListener (listener);
			}
		});
    }

    // Build help text
    protected String getScopeText() { return OStrings.getString("SW_HELP_SCOPE_REPLACE"); }
    protected boolean isReplace() { return true; }    
    
    private static final java.util.Map<String,Set<String>> theMap = new java.util.HashMap<String,Set<String>>();
    private SetRef searchRef, replaceRef;
    static {		
        for (String field: new String[] { "SW_SEARCH_TEXT", "SW_REPLACE_BY" })
            for (String mode: new String[] { "EXACT", "KEYWORD", "REGEXP" })
                theMap.put (field + ":" + mode, new TreeSet<String>());
        org.omegat.core.CoreEvents.registerProjectChangeListener(new SearchesLoader<Set<String>>("ReplaceDialog", theMap));
    }

    public void refreshLists(boolean removeContents) {
        String selected = removeContents ? "" : m_searchField.getSelectedItem().toString();
        m_searchField.removeAllItems();
        m_searchField.addItem (selected);
        searchRef.theSet = theMap.get("SW_SEARCH_TEXT:" + m_modePanel.searchTypeString());
        for (String item: searchRef.theSet) m_searchField.addItem(SearchModeBox.MemorizedExpression.forString(item));
        m_searchField.setSelectedItem (selected);
        
        selected = removeContents ? "" : m_replaceField.getSelectedItem().toString();
        m_replaceField.removeAllItems();
        m_replaceField.addItem (selected);
        replaceRef.theSet = theMap.get("SW_REPLACE_BY:" + m_modePanel.searchTypeString());
        for (String item: replaceRef.theSet) m_replaceField.addItem(SearchModeBox.MemorizedExpression.forString(item));
        m_replaceField.setSelectedItem (selected);			
    }
    
    @Override
    protected JComponent textPanel(String startText) {
        JLabel m_searchLabel = new JLabel();
        Mnemonics.setLocalizedText(m_searchLabel, OStrings.getString("SW_SEARCH_TEXT"));

        m_searchField = new JComboBox (); m_searchField.setEditable(true);
		m_searchField.addActionListener (ev -> {
			try {
				SearchModeBox.MemorizedExpression item = (SearchModeBox.MemorizedExpression) m_searchField.getSelectedItem();
				item.applyTo(ReplaceDialog.this.m_modePanel);
			} catch (Exception cce) { // ClassCast | NullPointer
				
			}
		});

        if (startText != null) m_searchField.setSelectedItem(startText);

        JLabel m_replaceLabel = new JLabel();
        Mnemonics.setLocalizedText(m_replaceLabel, OStrings.getString("SW_REPLACE_TEXT"));
        m_replaceField = new JComboBox(); m_replaceField.setEditable(true);
		
        // box Search bSearch
        Box bSearch = Box.createHorizontalBox();
        bSearch.add(m_searchLabel);
        bSearch.add(m_searchField);
        bSearch.add (this.createMemorizeButton(
            getY(), true, m_searchField, null, 
            "SW_SEARCH_TEXT", searchRef = new SetRef(theMap.get("SW_SEARCH_TEXT:EXACT"))
            
        ));

        // box replace bReplace
        Box bReplace = Box.createHorizontalBox();
        bReplace.add(m_replaceLabel);
        bReplace.add(m_replaceField);
        bReplace.add (this.createMemorizeButton(
            getY(), true, m_replaceField, null, 
            "SW_REPLACE_BY", replaceRef = new SetRef(theMap.get("SW_REPLACE_BY:EXACT"))
        ));

        Box bBoth = Box.createVerticalBox();
        bBoth.add (bSearch); bBoth.add (bReplace);
        
        return bBoth;
    }

    public JComboBox getMainSearchTextField() {
        return m_searchField;
    }	

    protected Box buttonsPanel() {
        Box bButtons = super.buttonsPanel();

        bButtons.add(m_replaceInt = new JButton());
        bButtons.add(m_replaceAll = new JButton());
        m_replaceInt.setEnabled(false); m_replaceAll.setEnabled(false);
        Mnemonics.setLocalizedText(m_replaceInt, OStrings.getString("BUTTON_REPLACE_INT"));
        Mnemonics.setLocalizedText(m_replaceAll, OStrings.getString("BUTTON_REPLACE_ALL"));

        // ///////////////////////////////////
        // action listeners
        m_replaceInt.addActionListener(e -> {
                Core.getEditor().commitAndLeave(); // Otherwise, the current segment being edited is lost
                try {
                    Core.getEditor().setFilter(new ReplaceFilter(m_viewer.getEntryList(), buildSearcher(null), this));
                } catch (Exception eMissing) {
                    JOptionPane.showMessageDialog (ReplaceDialog.this, eMissing.getMessage());	// should not happen, button should be grayed
                }
            });
        m_replaceAll.addActionListener(e -> {
                Core.getEditor().commitAndDeactivate(); // Otherwise, the current segment being edited is lost
                final int count = m_viewer.getEntryList().size();
                String msg = MessageFormat.format(OStrings.getString("SW_REPLACE_ALL_CONFIRM"), count);
                int r = JOptionPane.showConfirmDialog(ReplaceDialog.this, msg, OStrings.getString("CONFIRM_DIALOG_TITLE"), JOptionPane.YES_NO_OPTION);
                if (r == JOptionPane.YES_OPTION) {
                    m_replaceAll.setEnabled(false); m_replaceInt.setEnabled(false); 
                    //m_progressBar.setVisible(true); ReplaceDialog.this.repaint();
                    m_resultsLabel.setText (OStrings.getString("SW_REPLACE_WAIT"));
                    javax.swing.SwingUtilities.invokeLater(() -> {
                        try {
                            new ReplaceFilter(m_viewer.getEntryList(), buildSearcher(null), this).replaceAll(m_resultsLabel);
                            Thread.yield(); // wait for editor refresh to finish
                            m_resultsLabel.setText (OStrings.getString("SW_REPLACE_WAIT_FINISHED"));
                            if (count >= (Integer) m_numberOfResults.getValue()) {
                                String msg1 = MessageFormat.format(OStrings.getString("SW_REPLACE_WAIT_COUNT_QUESTION"), count);
                                int r1 = JOptionPane.showConfirmDialog(ReplaceDialog.this, msg1, OStrings.getString("CONFIRM_DIALOG_TITLE"), JOptionPane.YES_NO_OPTION);
                                if (r1 == JOptionPane.YES_OPTION) doSearch(true);
                            } else {
                                String msg2 = MessageFormat.format(OStrings.getString("SW_REPLACE_WAIT_COUNT_OK"), count);
                                JOptionPane.showMessageDialog (ReplaceDialog.this, msg2);
                            }
                        } catch (Exception eMissing) {
                            JOptionPane.showMessageDialog (ReplaceDialog.this, eMissing.getMessage());	// should not happen, button should be grayed
                        }
                    });
                    //m_progressBar.setVisible(false);					
                }
                Core.getEditor().activateEntry();
            });
        
        return bButtons;
    }
	
     protected JComponent modePanel() {	 
        m_modePanel = (SearchModeBox) super.modePanel();
        
        Box bVertical = Box.createVerticalBox();
        bVertical.add (m_modePanel);
        
        Box bRow2 = Box.createHorizontalBox(); bVertical.add (bRow2);
        
        Box bReplace = Box.createHorizontalBox();
        bReplace.add (m_replaceExact = new JRadioButton(OStrings.getString("SW_REPLACE_ACTION_FOUND")));
        bReplace.add (m_replaceWhole = new JRadioButton(OStrings.getString("SW_REPLACE_ACTION_WORDS")));
        bReplace.add (m_replaceSegment = new JRadioButton(OStrings.getString("SW_REPLACE_ACTION_ENTIRE")));
        bReplace.add (m_replaceWithVars = new JCheckBox(OStrings.getString("SW_REPLACE_ACTION_VARS")));
        bReplace.setBorder (BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black), OStrings.getString("SW_REPLACE_ACTION_MODE")));
        bRow2.add (bReplace);
        
        ButtonGroup bgExpr = new ButtonGroup();
        bgExpr.add(m_replaceExact); bgExpr.add(m_replaceWhole); bgExpr.add(m_replaceSegment); m_replaceExact.setSelected(true);
        
        for (AbstractButton component: m_modePanel.getOptionsComponents())
            addFocusToSearchListener(component);
            
        bRow2.add(wherePanel1());
        return bVertical;
    }

    private void doCancel() {
        UIThreadsUtil.mustBeSwingThread();
        dispose();
    }

    @Override
    protected Box optionsPanel() {
        return null; // Always search on translation only	
    }	
    
    @Override
    public Box wherePanel() {
        return null;
    }
    
    public Box wherePanel1() {
        Box bWhere = Box.createHorizontalBox();
        
        m_orphansSearchCB = new JCheckBox();

        Mnemonics.setLocalizedText(m_orphansSearchCB, OStrings.getString("SW_REPLACE_IN_ORPHANS"));
        
        bWhere.add(m_orphansSearchCB);
        bWhere.setBorder (BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black), OStrings.getString("SW_SEARCH_SCOPE")));

        addFocusToSearchListener(m_orphansSearchCB);
        
        return bWhere;	
    }
	
    @Override    
    protected ReplaceSearcher buildSearcher(Searcher prev) throws IllegalArgumentException {
        if ((m_searchField.getSelectedItem() == null) || (m_searchField.getSelectedItem().toString().length() == 0)) throw new IllegalArgumentException(OStrings.getString("SW_ERROR_MANDATORY_FIELD_SEARCH"));
        ReplaceSearcher res = new ReplaceSearcher (this, Core.getProject(), Integer.MAX_VALUE, false,
            m_modePanel.buildReplaceExpression (m_searchField.getSelectedItem().toString(), false,  // search only in target		
				m_replaceField.getSelectedItem().toString().toString(), m_replaceWithVars.isSelected()),  
            m_authorField.isUsed() ? m_modePanel.buildExpression(m_authorField.getValue(), m_authorField.isNot(), false) : null, 
			null, // "Txt::Translator", which is DGT-specific, is not stored in TMXEntry
            m_dateFrom.isUsed() ? m_dateFrom.getValue().getTime() : Long.MAX_VALUE,
            m_dateTo.isUsed() ? m_dateTo.getValue().getTime() : Long.MIN_VALUE
        );
        try { res.startAt( ((ReplaceSearcher)prev).projectEntries() ); } catch (Exception ex){}
        return res;
    }

    @Override
    protected String[] getFormatVariablesList() {
        return new String[] {
            SearchVarExpansion.VAR_PREAMBLE, SearchVarExpansion.VAR_CREATION_ID, SearchVarExpansion.VAR_CREATION_DATE,
            SearchVarExpansion.VAR_SOURCE_TEXT, SearchVarExpansion.VAR_TARGET_TEXT, SearchVarExpansion.VAR_NOTE,
            SearchVarExpansion.VAR_TARGET_REPLACED,
            SearchVarExpansion.VAR_FILE_NAME, SearchVarExpansion.VAR_FILE_NAME_ONLY, SearchVarExpansion.VAR_FILE_PATH, SearchVarExpansion.VAR_FILE_SHORT_PATH
        };
    }
    
    protected String getFormatOptionName() {
        return Preferences.SEARCHWINDOW_TEMPLATE_REPLACE;
    }
    
    protected String getFormatOptionDefaultValue() {
        return SearchVarExpansion.DEFAULT_TEMPLATE_REPLACE;
    }

    protected JComponent[] componentsEnabledWhenResults() {
        if (m_orphansSearchCB.isSelected()) return new JComponent [] { m_replaceAll }; else return new JComponent [] { m_replaceInt, m_replaceAll };
    }
	
	protected JTextComponent[] textFieldsList() {
		return new JTextComponent[] {
			(JTextComponent) m_searchField.getEditor().getEditorComponent(), 
			(JTextComponent) m_replaceField.getEditor().getEditorComponent()  
		};
	}	
	
	public String getReplaceText() { return m_replaceField.getSelectedItem().toString(); }

    public final static String REPL_WORD_WHOLE = "WHOLE", REPL_EXACT = "EXACT", REPL_SEGMENT = "SEGMENT";
    
    public String getReplacementMode() {
        if (m_replaceSegment.isSelected()) return REPL_SEGMENT;
        else if (m_replaceWhole.isSelected()) return REPL_WORD_WHOLE;
        else return REPL_EXACT;		
    }

    @Override
    protected void loadAdvancedOptionPreferences() {
        super.loadAdvancedOptionPreferences();
        m_replaceWithVars.setSelected(Preferences.isPreferenceDefault(Preferences.SEARCHWINDOW_REPLACE_WITH_VARS, true));
        String replMode = Preferences.getPreferenceDefault(Preferences.SEARCHWINDOW_REPLACE_MODE, REPL_SEGMENT);
        m_replaceSegment.setSelected(REPL_SEGMENT.equals(replMode));
        m_replaceWhole.setSelected(REPL_WORD_WHOLE.equals(replMode));
        m_replaceExact.setSelected(REPL_EXACT.equals(replMode));
    }
	
    @Override // Also save replacement mode properties  
    protected void savePreferences() {
        super.savePreferences();
        Preferences.setPreference(Preferences.SEARCHWINDOW_REPLACE_WITH_VARS, Boolean.toString(m_replaceWithVars.isSelected()));
        if (m_replaceSegment.isSelected()) Preferences.setPreference(Preferences.SEARCHWINDOW_REPLACE_MODE, REPL_SEGMENT);
        else if (m_replaceWhole.isSelected()) Preferences.setPreference(Preferences.SEARCHWINDOW_REPLACE_MODE, REPL_WORD_WHOLE);
        else if (m_replaceExact.isSelected()) Preferences.setPreference(Preferences.SEARCHWINDOW_REPLACE_MODE, REPL_EXACT);
        Preferences.save();
    }
    
    /** Replace-mode dependant build of the result string **/
    public String buildReplacedString (String translation, List<SearchMatch> matches) {
        if (m_replaceExact.isSelected()) // trivial case
            return ReplaceMatch.buildReplacedString(translation, matches, getReplaceText());
        else if (m_replaceSegment.isSelected()) 
            if (! m_replaceWithVars.isSelected()) return getReplaceText(); // trivial case
            else {	// warning: here $1, $2, ... mean found substring!
                String repl = getReplaceText();
                Matcher replaceMatcher = Pattern.compile("(?<!\\\\)\\$(\\d+)").matcher(repl);
                while (replaceMatcher.find()) {
                    int varId = Integer.parseInt(replaceMatcher.group(1));
                    repl = replaceMatcher.replaceFirst( translation.substring(matches.get(varId - 1).getStart(), matches.get(varId - 1).getEnd()) );
                    replaceMatcher.reset(repl);
                }
                return org.omegat.util.StringUtil.replaceCase(repl, org.omegat.core.Core.getProject().getProjectProperties().getTargetLanguage().getLocale());
            }
        else if (m_replaceWhole.isSelected()) 
            return ReplaceMatch.buildReplacedString(translation, buildReplacedMatches(translation, matches), getReplaceText());			
        return null;
    }
    
    public List<SearchMatch> buildReplacedMatches (String translation, List<SearchMatch> matches) {
        if (m_replaceExact.isSelected()) // trivial case
            return ReplaceMatch.buildReplacedMatches(translation, matches, getReplaceText());
        else if (m_replaceSegment.isSelected()) 
            return java.util.Collections.singletonList (new SearchMatch (0, buildReplacedString(translation, matches).length()));
        else if (m_replaceWhole.isSelected()) {
            List<SearchMatch> englobedList = new ArrayList<> (matches.size());
            SearchMatch global = null;
            for (SearchMatch m: matches) {
                if (global != null)
                    if (m.getStart() <= global.getEnd()) continue;
                englobedList.add (global = m.englobeWord(translation));
            }
            return englobedList;
        }
        return null;
    }
    
    @Override protected boolean englobeWords() { return m_replaceWhole.isSelected(); }
	
    private JComboBox m_searchField,m_replaceField;
    private JButton m_replaceInt, m_replaceAll;
    private JRadioButton m_replaceSegment, m_replaceWhole, m_replaceExact;
    private JCheckBox m_replaceWithVars;
    private JCheckBox m_orphansSearchCB;
}
