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

 Copyright (C) 2015-2023 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.awt.Color;
import java.awt.Container;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import java.text.MessageFormat;

import java.util.Map;
import java.util.Set;
import java.util.List;
import java.util.HashMap;
import java.util.TreeSet;
import java.util.Collection;

import javax.swing.AbstractButton;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.Box;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JRadioButton;
import javax.swing.JTextField;
import javax.swing.text.JTextComponent;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;

import org.omegat.core.Core;
import org.omegat.gui.main.MainWindow;
import org.omegat.gui.editor.filter.CrossSourceFilter;
import org.omegat.gui.editor.filter.PreTranslateFilter;
import org.omegat.gui.exttrans.IMachineTranslation;
import org.omegat.util.OStrings;
import org.omegat.util.gui.UIThreadsUtil;
import org.openide.awt.Mnemonics;
import org.omegat.util.Preferences;
import org.omegat.core.search.CrossSourceSearcher;
import org.omegat.core.search.PreTranslateSearcher;
import org.omegat.core.search.ProjectSearcher;
import org.omegat.core.search.Searcher;
import org.omegat.core.search.TranslationStateFilter;
import org.omegat.core.search.SearchResultEntry;
import org.omegat.core.search.CrossSourceSearchResultEntry;
import org.omegat.core.matching.NearString.SORT_KEY;

import static org.omegat.util.OConsts.FUZZY_MATCH_THRESHOLD;

/**
 * Dialog which presents text to be searched, some options as in SearchWindow, and replacement.
 * 
 * @author Thomas CORDONNIER
 */
public class PreTranslateDialog extends ProjectSearchWindow {
    public PreTranslateDialog(MainWindow par, String startText) {
        super(par, "");
        updateUIText();

        refreshLists(true); // after all components, in particular the m_modePanel, have been built
        for (JComboBox box: new JComboBox[]{ m_searchField, m_translateField }) {
            JTextComponent searchFieldArea = (JTextComponent) box.getEditor().getEditorComponent();
            searchFieldArea.addMouseListener(new PopupFactory(searchFieldArea));
        }
        if (startText != null) getMainSearchTextField().setSelectedItem(startText);
        m_modePanel.addModeChangeListener(new RegexModeSwitchListener(m_modePanel, ((javax.swing.text.JTextComponent) m_searchField.getEditor().getEditorComponent())));
		m_translateTxtRegex.addActionListener (new ActionListener() {
			private javax.swing.text.JTextComponent receiver = ((javax.swing.text.JTextComponent) m_translateField.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_translateTxtRegex.isSelected()) receiver.getDocument().addDocumentListener (listener); else receiver.getDocument().removeDocumentListener (listener);
			}
		});  
        m_viewer.addMouseListener (groupPopupMenuListener);
	}
    
    // Build help text
    protected String getScopeText() { return OStrings.getString("SW_HELP_SCOPE_PRETRA"); }
    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>>("PreTranslateDialog", 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_translateField.getSelectedItem().toString();
        m_translateField.removeAllItems();
        m_translateField.addItem (selected);
        replaceRef.theSet = theMap.get("SW_REPLACE_BY:" + m_modePanel.searchTypeString());
        for (String item: replaceRef.theSet) m_translateField.addItem(SearchModeBox.MemorizedExpression.forString(item));
        m_translateField.setSelectedItem (selected);            
    }
    
    @Override
    protected JComponent textPanel(String startText) {
        m_translateLabel = new JLabel();
        m_translateField = new JComboBox(); m_translateField.setEditable(true); // Do not add listener for translate label: memorized, but do not change word mode when this changes

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

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

        // 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.createVerticalBox();

        m_translateLabel = new JLabel();
        m_translateField = new JComboBox(); m_translateField.setEditable(true);
        
        Box bReplaceLine1 = Box.createHorizontalBox(); bReplace.add(bReplaceLine1);
        bReplaceLine1.add(m_translateLabel);
        bReplaceLine1.add(m_translateSource = new JRadioButton(OStrings.getString("SW_PRETRA_REPLACEBY_SOURCE")));
        bReplaceLine1.add(m_translateMatch = new JRadioButton(OStrings.getString("SW_PRETRA_REPLACEBY_MATCH")));
        bReplaceLine1.add(new JLabel(OStrings.getString("SW_PRETRA_REPLACEBY_MATCH_MIN_SCORE")));
        bReplaceLine1.add(m_scoreSelector = new JComboBox());
        m_scoreSelector.setModel(new javax.swing.DefaultComboBoxModel(
            new SORT_KEY[] {SORT_KEY.SCORE, SORT_KEY.SCORE_NO_STEM, SORT_KEY.ADJUSTED_SCORE}));
        m_scoreSelector.setRenderer(new org.omegat.util.gui.DelegatingComboBoxRenderer<SORT_KEY>() {
            @Override
            protected Object getDisplayText(SORT_KEY value) {
                return OStrings.getString("EXT_TMX_SORT_KEY_" + value.name());
            }
        });        
        class ScoreKeyListener implements ItemListener {
            private String unselectedDefault = defaultText((SORT_KEY) m_scoreSelector.getSelectedItem());
            
            private String defaultText(SORT_KEY key) {
                switch(key) {
                    case SCORE: return "[${score} %]";
                    case SCORE_NO_STEM: return "[${noStemScore} %]";
                    case ADJUSTED_SCORE: return "[${adjustedScore} %]";
                    default: return null;
                }
            }
            
            public void itemStateChanged(ItemEvent ev) {
                if (ev.getStateChange() == ItemEvent.DESELECTED) unselectedDefault = defaultText((SORT_KEY) ev.getItem());
                if (ev.getStateChange() == ItemEvent.SELECTED) {
                    if (m_prefixField.getText().equals(unselectedDefault)) m_prefixField.setText(defaultText((SORT_KEY) ev.getItem()));
                    if (m_prefixField.getText().equals("")) m_prefixField.setText(defaultText((SORT_KEY) ev.getItem()));
                }
            }
        }
        final ScoreKeyListener skListener = new ScoreKeyListener(); m_scoreSelector.addItemListener(skListener);
        bReplaceLine1.add(m_minScore = new javax.swing.JSpinner());
        m_minScore.setModel(new javax.swing.SpinnerNumberModel(80, 0, 100, 1));        
        bReplaceLine1.add(m_translateAuto = new JRadioButton(OStrings.getString("SW_PRETRA_REPLACEBY_MT")));
        bReplaceLine1.add(m_preferredMt = new JComboBox());
        m_preferredMt.setModel(new javax.swing.DefaultComboBoxModel(Core.getMachineTranslatePane().translators));
        m_preferredMt.setRenderer(new org.omegat.util.gui.DelegatingComboBoxRenderer<IMachineTranslation>() {
            @Override
            protected Object getDisplayText(IMachineTranslation value) {
                return value.getName();
            }
        });
        for (IMachineTranslation tra: Core.getMachineTranslatePane().translators)
            if (tra.getClass().getName().equals (Preferences.getPreference(Preferences.MT_PREFERRED)))
                m_preferredMt.setSelectedItem(tra);
        if (Core.getMachineTranslatePane().translators.length == 1) m_preferredMt.setVisible(false);
        bReplaceLine1.add(Box.createHorizontalStrut(10));
        bReplaceLine1.add(new JLabel(OStrings.getString("WF_OPTION_INSERT_FUZZY_PREFIX").replace("&","")));
        bReplaceLine1.add(m_prefixField = new JTextField());
        
        Box bReplaceLine2 = Box.createHorizontalBox(); bReplace.add(bReplaceLine2);    
        bReplaceLine2.add(m_translateText = new JRadioButton("\t" + OStrings.getString("SW_PRETRA_REPLACEBY_TEXT")));
        bReplaceLine2.add(m_translateField);
        memTraBtn =  this.createMemorizeButton(
            getY(), true, m_translateField, null, 
            "SW_REPLACE_BY", replaceRef = new SetRef(theMap.get("SW_REPLACE_BY:EXACT"))
        );
        bReplaceLine2.add (memTraBtn);
                
        Box bBoth = Box.createVerticalBox();
        bBoth.add (bSearch); bBoth.add (bReplace);
        
        m_translateSource.addActionListener (e -> { m_prefixField.setText (""); setMatchEnabled(false); });
        m_translateText.addActionListener (e -> { m_prefixField.setText (""); setMatchEnabled(false); });
        m_translateAuto.addActionListener (e -> { m_prefixField.setText ("[MT]"); setMatchEnabled(false); });
        m_translateMatch.addActionListener (e -> { m_prefixField.setText (skListener.defaultText((SORT_KEY) m_scoreSelector.getSelectedItem())); setMatchEnabled(true); });
        
        return bBoth;
    }

    public JComboBox getMainSearchTextField() {
        return m_searchField;
    }
    
     protected JComponent modePanel() {     
        m_modePanel = (SearchModeBox) super.modePanel();
        
        Box bVertical = Box.createVerticalBox();
        bVertical.add (m_modePanel);
        
        Box bReplace = Box.createHorizontalBox();
        bReplace.add (m_translateTxtEntire = new JRadioButton(OStrings.getString("SW_PRETRA_ACTION_TXT_ENTIRE")));
        bReplace.add (m_translateTxtFound = new JRadioButton(OStrings.getString("SW_PRETRA_ACTION_TXT_FOUND")));
        bReplace.add (m_translateTxtWhole = new JRadioButton(OStrings.getString("SW_PRETRA_ACTION_TXT_WORDS")));
        bReplace.add (m_translateTxtRegex = new JCheckBox(OStrings.getString("SW_PRETRA_ACTION_TXT_REGEX")));
        bReplace.add (m_translateSrcEntire = new JRadioButton(OStrings.getString("SW_PRETRA_ACTION_SRC_ENTIRE")));
        bReplace.add (m_translateSrcFound = new JRadioButton(OStrings.getString("SW_PRETRA_ACTION_SRC_FOUND")));
        bReplace.add (m_translateSrcGlos = new JCheckBox(OStrings.getString("SW_PRETRA_ACTION_SRC_GLOS")));
        bReplace.add (m_translateSrcGlosOnly = new JCheckBox(OStrings.getString("SW_PRETRA_ACTION_SRC_WITH_GLOS_ONLY")));
		m_translateSrcGlos.addChangeListener (ev -> { m_translateSrcGlosOnly.setEnabled(m_translateSrcGlos.isSelected()); });
        bReplace.add (new JLabel("   ")); bReplace.add(m_transAsAlternative = new JCheckBox(OStrings.getString("SW_PRETRA_ACTION_AS_ALTERNATIVE")));
        m_transAsAlternative.setEnabled(Core.getProject().getProjectProperties().isSupportDefaultTranslations());
        m_transAsAlternative.setSelected(! Core.getProject().getProjectProperties().isSupportDefaultTranslations());
        
        bReplace.setBorder (BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black), OStrings.getString("SW_PRETRA_MODE")));
        bVertical.add (bReplace);
        
        ButtonGroup bgExpr = new ButtonGroup();
        bgExpr.add(m_translateTxtEntire); bgExpr.add(m_translateTxtFound); bgExpr.add(m_translateTxtWhole); m_translateTxtFound.setSelected(true);
        bgExpr = new ButtonGroup(); bgExpr.add(m_translateSrcEntire); bgExpr.add(m_translateSrcFound); 
		
        for (AbstractButton component: m_modePanel.getOptionsComponents()) addFocusToSearchListener(component);

        final JComponent[] boxReplaceTxt = { m_translateTxtEntire, m_translateTxtFound, m_translateTxtWhole, m_translateTxtRegex };
        final JComponent[] boxReplaceSrc = { m_translateSrcEntire, m_translateSrcFound, m_translateSrcGlos, m_translateSrcGlosOnly };
        ActionListener disableButtons = e -> {
            m_translateField.setEnabled (m_translateText.isSelected());
            memTraBtn.setEnabled (m_translateText.isSelected());
            for (JComponent comp: boxReplaceTxt) { comp.setEnabled (m_translateText.isSelected()); if (m_translateSource.isSelected()) comp.setVisible(false); if (m_translateText.isSelected()) comp.setVisible(true); }
            for (JComponent comp: boxReplaceSrc) { comp.setEnabled (m_translateSource.isSelected()); if (m_translateText.isSelected()) comp.setVisible(false); if (m_translateSource.isSelected()) comp.setVisible(true); }
            m_prefixField.setEnabled (! m_translateText.isSelected());
            m_minScore.setEnabled (m_translateMatch.isSelected()); m_scoreSelector.setEnabled (m_translateMatch.isSelected());
        };
        ButtonGroup group = new ButtonGroup();
        for (JRadioButton btn: new JRadioButton[] { m_translateSource, m_translateMatch, m_translateAuto, m_translateText }) { group.add (btn);  btn.addActionListener(disableButtons); }
        m_translateSource.setSelected(true); disableButtons.actionPerformed(null);
        
        return bVertical;
    }

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

        bButtons.add(m_translateInt = new JButton());
        bButtons.add(m_translateAll = new JButton());
        m_translateInt.setEnabled(false); m_translateAll.setEnabled(false);

        // ///////////////////////////////////
        // action listeners
        m_translateInt.addActionListener(e -> {
                Core.getEditor().commitAndLeave(); // Otherwise, the current segment being edited is lost
                Core.getEditor().setFilter(new PreTranslateFilter((PreTranslateSearcher) m_thread));
            });
        m_translateAll.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(PreTranslateDialog.this, msg, OStrings.getString("CONFIRM_DIALOG_TITLE"), JOptionPane.YES_NO_OPTION);
                if (r == JOptionPane.YES_OPTION) {
                    m_resultsLabel.setText (OStrings.getString("SW_PRETRA_WAIT_FINISHED"));
                    javax.swing.SwingUtilities.invokeLater(() -> {
                            new PreTranslateFilter((PreTranslateSearcher) m_thread).translateAll(m_resultsLabel);
                            Thread.yield(); // wait for editor refresh to finish
                            m_resultsLabel.setText (OStrings.getString("SW_PRETRA_WAIT_FINISHED"));
                            if (count >= (Integer) m_numberOfResults.getValue()) {
                                String msg1 = MessageFormat.format(OStrings.getString("SW_PRETRA_WAIT_COUNT_QUESTION"), count);
                                int r1 = JOptionPane.showConfirmDialog(PreTranslateDialog.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_PRETRA_WAIT_COUNT_OK"), count);
                                JOptionPane.showMessageDialog (PreTranslateDialog.this, msg2);
                            }
                        });
                }
                Core.getEditor().activateEntry();
            });
        
        return bButtons;
    }

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

    private void updateUIText() {
        setTitle("Search and Pre-translate");

        Mnemonics.setLocalizedText(m_searchLabel, OStrings.getString("SW_SEARCH_TEXT"));
        Mnemonics.setLocalizedText(m_translateLabel, OStrings.getString("SW_PRETRANSLATE_TEXT"));

        Mnemonics.setLocalizedText(m_translateInt, OStrings.getString("BUTTON_TRANSLATE_INT"));
        Mnemonics.setLocalizedText(m_translateAll, OStrings.getString("BUTTON_TRANSLATE_ALL"));
    }

    @Override
    protected Box optionsPanel() {
        // <Box 3 - Search options>
        m_searchTranslated = new JRadioButton(); 
        m_searchUntranslated = new JRadioButton(); 
        m_searchTranslatedUntranslated = new JRadioButton();
        Mnemonics.setLocalizedText(m_searchTranslated, OStrings.getString("SW_SEARCH_TRANSLATED"));
        Mnemonics.setLocalizedText(m_searchUntranslated, OStrings.getString("SW_SEARCH_UNTRANSLATED"));
        Mnemonics.setLocalizedText(m_searchTranslatedUntranslated, OStrings.getString("SW_SEARCH_TRANSLATEDUNTRANSLATED"));

        // box OptionsBox bOB
        Box bOB = Box.createHorizontalBox();
        bOB.setBorder (BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black), OStrings.getString("SW_SEARCH_IN_BOX") + "..."));
        bOB.add(m_searchTranslated); bOB.add(m_searchUntranslated); bOB.add(m_searchTranslatedUntranslated); 

        addFocusToSearchListener(m_searchTranslated); addFocusToSearchListener(m_searchUntranslated); addFocusToSearchListener(m_searchTranslatedUntranslated); 

        ButtonGroup gOptions = new ButtonGroup();
        gOptions.add (m_searchTranslated); gOptions.add (m_searchUntranslated); gOptions.add (m_searchTranslatedUntranslated);
                
        m_searchUntranslated.setSelected(true);
        
        // <Box 4 - Match options
        bMatch = Box.createHorizontalBox();
        m_matchTM = new JRadioButton(); 
        m_matchSource = new JRadioButton();
        Mnemonics.setLocalizedText(m_matchTM, OStrings.getString("SW_PRETRA_MATCH_IN_TM"));
        Mnemonics.setLocalizedText(m_matchSource, OStrings.getString("SW_PRETRA_MATCH_IN_SRC"));
        bMatch.setBorder (BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black), OStrings.getString("SW_PRETRA_MATCH_IN") + "..."));
        bMatch.add(m_matchTM); bMatch.add(m_TMpath = new JTextField());
        bMatch.add(m_matchSource); bMatch.add(new JLabel(OStrings.getString("SW_PRETRA_MATCH_GROUP_SIZE"))); bMatch.add(m_groupSize = new javax.swing.JSpinner());
        m_groupSize.setModel(new javax.swing.SpinnerNumberModel(5, 0, 100, 1));
        m_matchTM.addActionListener (e -> { m_TMpath.setEnabled(true); m_groupSize.setEnabled(false); m_minScore.setValue(Preferences.getPreferenceDefault(Preferences.SEARCHWINDOW_PRETRA_MATCH_SCORE, 80)); }); 
		m_matchSource.addActionListener (e -> { m_TMpath.setEnabled(false); m_groupSize.setEnabled(true); m_minScore.setValue(Preferences.getPreferenceDefault(Preferences.SEARCHWINDOW_PRETRA_MATCH_SCORE_XSRC, FUZZY_MATCH_THRESHOLD)); });
        gOptions = new ButtonGroup(); gOptions.add(m_matchTM); gOptions.add(m_matchSource);
        
        Box bAll = Box.createHorizontalBox(); bAll.add(bOB); bAll.add(bMatch); return bAll;
    }
    
    private void setMatchEnabled(boolean enabled) {
        bMatch.setEnabled(enabled); m_matchTM.setEnabled(enabled); m_matchSource.setEnabled(enabled); 
        m_groupSize.setEnabled(enabled && m_matchSource.isSelected()); m_TMpath.setEnabled(enabled && m_matchTM.isSelected());
    }
    
    private GroupPopupMenuListener groupPopupMenuListener = new GroupPopupMenuListener();
    class GroupPopupMenuListener extends MouseAdapter {
        public List<SearchResultEntry> entries;
        
        @Override
        public void mouseClicked(MouseEvent e) {
            if (! m_matchSource.isSelected()) { displaySaveMenu(e); return; }
            List<Integer> offset = m_viewer.getOffsetList();
            if (offset == null) return; if (offset.size() == 0) return;
            if (e.isPopupTrigger() || e.getButton() == MouseEvent.BUTTON3) {
                int mousepos = m_viewer.viewToModel(e.getPoint());
                for (int i = 0; i < offset.size(); i++) 
                    if (mousepos < offset.get(i)) {
                        displayMatchMenu(i,e); return;
                    }
            }
        }
        
        private void displayMatchMenu (final int entryNum, MouseEvent event) {
            JPopupMenu popup = new JPopupMenu();
            JMenuItem menuItem = new JMenuItem(OStrings.getString("SW_PRETRA_MATCH_MENU_FILTER"));
            menuItem.addActionListener(e -> {
                Core.getEditor().commitAndLeave(); // Otherwise, the current segment being edited is lost
                Core.getEditor().setFilter(new CrossSourceFilter((CrossSourceSearcher) m_thread, entryNum));
            });
            popup.add(menuItem);
            popup.show(m_viewer, event.getX(), event.getY());
        }
        
        private void displaySaveMenu(MouseEvent event) {
            JPopupMenu popup = new JPopupMenu();
            JMenuItem menuItem = new JMenuItem(OStrings.getString("SW_PRETRA_MENU_SAVE"));
            menuItem.addActionListener(e -> {
                javax.swing.JFileChooser chooser = new javax.swing.JFileChooser();
                chooser.setFileFilter(new javax.swing.filechooser.FileNameExtensionFilter ("TMX", "tmx"));
                if (chooser.showSaveDialog(Core.getMainWindow().getApplicationFrame()) == javax.swing.JFileChooser.APPROVE_OPTION) 
                    try {
                        SearchVarExpansion sve = new SearchVarExpansion(Core.getProject().getProjectProperties(), SearchVarExpansion.VAR_PRE_TRANSLATION);
                        org.omegat.util.TMXWriter2 writer = new org.omegat.util.TMXWriter2(chooser.getSelectedFile(),
                            Core.getProject().getProjectProperties().getSourceLanguage(),
                            Core.getProject().getProjectProperties().getTargetLanguage(),
                            true, true, true);
                        for (SearchResultEntry entry: entries) {
                            List<String> props = new java.util.ArrayList<>(entry.getProperties() == null ? 0 : entry.getProperties().size() * 2);
                            for (org.omegat.util.TMXProp p: entry.getProperties()) { props.add(p.getType()); props.add(p.getValue()); }
                            writer.writeEntry(entry.getSrcText(), sve.apply(entry), entry.getNote(),
                                entry.getAuthor(), entry.getDate().getTime(), "", 0, props);
                        }
                        writer.close();
                    }
                    catch (Exception ex) {
                        ex.printStackTrace();
                    }
            });
            popup.add(menuItem);
            popup.show(m_viewer, event.getX(), event.getY());
        }
    };

    @Override
    public void displaySearchResult(final Collection<SearchResultEntry> entries) {
        groupPopupMenuListener.entries = new java.util.ArrayList<SearchResultEntry>(entries);
        super.displaySearchResult(entries);
    }
    
    @Override
    public Box wherePanel() {
        return null; // Always search on source only    
    }
    
    protected String[] getFormatVariablesList() {
        if (m_translateMatch.isSelected() && m_matchSource.isSelected()) 
            return new String[] { SearchVarExpansion.VAR_PREAMBLE, SearchVarExpansion.VAR_GROUP_COUNT, SearchVarExpansion.VAR_XSRC_SOURCES };
        else
            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_PRE_TRANSLATION,
                SearchVarExpansion.VAR_FILE_NAME, SearchVarExpansion.VAR_FILE_NAME_ONLY, SearchVarExpansion.VAR_FILE_PATH, SearchVarExpansion.VAR_FILE_SHORT_PATH
            };
    }
    
    protected String getFormatOptionName() {
        if (m_translateMatch.isSelected() && m_matchSource.isSelected()) return Preferences.SEARCHWINDOW_TEMPLATE_PRETRA_SRC;
        else return Preferences.SEARCHWINDOW_TEMPLATE_PRETRA;
    }
    protected String getFormatOptionDefaultValue() {
        if (m_translateMatch.isSelected() && m_matchSource.isSelected()) return SearchVarExpansion.DEFAULT_TEMPLATE_PRETRA_SRC;
        else return SearchVarExpansion.DEFAULT_TEMPLATE_PRETRA;
    }
    
    
    @Override    
    protected ProjectSearcher buildSearcher(Searcher prev) {
        org.omegat.core.search.TextExpression sourceExpression; 
        if (m_searchField.getSelectedItem().toString().trim().length() == 0)
            sourceExpression = new org.omegat.core.search.TextExpression() {
                public List<org.omegat.core.search.SearchMatch> searchString (String text) { return new java.util.ArrayList<org.omegat.core.search.SearchMatch>(); }
                public boolean matchesString (String text) { return true; }
				
				public org.omegat.core.search.TextExpression asVariableKeeper() { return this; }
				public boolean isVariableKeeper() { return true; }
				public org.omegat.core.search.TextExpression rebuildForVariables (String[] vars) { return this; }
				public boolean hasVariables() { return false; }
            };
        else if (m_translateText.isSelected())
            sourceExpression = m_modePanel.buildReplaceExpression (m_searchField.getSelectedItem().toString(), true, 
                m_translateField.getSelectedItem().toString(), m_translateTxtRegex.isSelected());
        else
            sourceExpression = m_modePanel.buildExpression (m_searchField.getSelectedItem().toString(), false, true);
            
        if (m_translateMatch.isSelected() && m_matchSource.isSelected()) {
            return new CrossSourceSearcher(Core.getProject(), this, ((Integer) m_numberOfResults.getValue()), 
                Integer.parseInt(m_groupSize.getValue().toString()),
                sourceExpression, buildTranslationStateFilter(), // search only in source
                (SORT_KEY) m_scoreSelector.getSelectedItem(), Integer.parseInt(m_minScore.getValue().toString()),
                m_authorField.isUsed() ? m_modePanel.buildExpression(m_authorField.getValue(), m_authorField.isNot(), false) : null, 
                m_dateFrom.isUsed() ? m_dateFrom.getValue().getTime() : Long.MAX_VALUE,
                m_dateTo.isUsed() ? m_dateTo.getValue().getTime() : Long.MIN_VALUE
            );
        } else {
            IMachineTranslation translator = null;
            // Cases which are independant from PreTranslateSearcher
            if (m_translateSource.isSelected()) translator = new org.omegat.core.machinetranslators.dummy.SourceTranslate(m_translateSrcGlos.isSelected(), m_translateSrcGlosOnly.isSelected());
            if (m_translateAuto.isSelected()) translator = (IMachineTranslation) m_preferredMt.getSelectedItem();
                
            PreTranslateSearcher res = new PreTranslateSearcher (Core.getProject(), this, ((Integer) m_numberOfResults.getValue()), m_transAsAlternative.isSelected(),
                sourceExpression, buildTranslationStateFilter(), translator, m_prefixField.getText(),	 // search only in source
                m_authorField.isUsed() ? m_modePanel.buildExpression(m_authorField.getValue(), m_authorField.isNot(), false) : null, 
                m_dateFrom.isUsed() ? m_dateFrom.getValue().getTime() : Long.MAX_VALUE,
                m_dateTo.isUsed() ? m_dateTo.getValue().getTime() : Long.MIN_VALUE
            );
            try { res.startAt( ((PreTranslateSearcher)prev).projectEntries() ); } catch (Exception ex){}
            // Cases which need an already built PreTranslateSearcher
            if (m_translateMatch.isSelected()) 
                res.setTranslator(res.buildTranslationMemoriesPreTranslator(
                    (SORT_KEY) m_scoreSelector.getSelectedItem(), Integer.parseInt(m_minScore.getValue().toString()),
                    m_TMpath.getText().trim().length() == 0 ? null : m_TMpath.getText().trim()
                ));
            if (m_translateText.isSelected()) {
                byte mode = m_translateTxtEntire.isSelected() ? PreTranslateSearcher.TRANSLATE_ENTIRE
                    : m_translateTxtWhole.isSelected() ? PreTranslateSearcher.TRANSLATE_WHOLE
                    : PreTranslateSearcher.TRANSLATE_FOUND;
                res.setTranslator(res.buildReplacerPreTranslator(m_translateField.getSelectedItem().toString(), mode, m_translateTxtRegex.isSelected()));
            }
			if (m_translateSource.isSelected() && m_translateSrcFound.isSelected())
				res.setTranslator(res.buildReplacerPreTranslator("$^1", PreTranslateSearcher.TRANSLATE_ENTIRE, true));
            return res;
        }
    }

    private TranslationStateFilter buildTranslationStateFilter() {
        return    m_searchTranslated.isSelected() ? TranslationStateFilter.TRANSLATED_ONLY
            : m_searchUntranslated.isSelected() ? TranslationStateFilter.UNTRANSLATED_ONLY
            : TranslationStateFilter.TRANSLATED_AND_UNTRANSLATED;    
    }
    
    protected JComponent[] componentsEnabledWhenResults() {
        if (m_translateMatch.isSelected() && m_matchSource.isSelected()) return new JComponent[0];
        else return new JComponent [] { m_translateInt, m_translateAll };
    }
	
	protected JTextComponent[] textFieldsList() {
		return new JTextComponent[] {
			(JTextComponent) m_searchField.getEditor().getEditorComponent(), 
			(JTextComponent) m_translateField.getEditor().getEditorComponent()  
		};
	}
    
    public String getIntroText() {
        return OStrings.getString("SW_VIEWER_TEXT_PRETRA_1");
    }
    
    private final static String PRETRA_COPY_WHOLE = "WHOLE";
    private final static String PRETRA_COPY_EXACT = "EXACT";
    private final static String PRETRA_ONLY_TEXT = "ONLY_TEXT";
    private final static String PRETRA_SRC_FULL = "FULL";
    private final static String PRETRA_SRC_FOUND = "FOUND";

    private final static String PRETRA_TYPE_SOURCE = "SOURCE";
    private final static String PRETRA_TYPE_TEXT = "TEXT";
    private final static String PRETRA_TYPE_MATCH = "MATCH";
    private final static String PRETRA_TYPE_AUTO = "MT";
    
    @Override
    protected void loadAdvancedOptionPreferences() {
        super.loadAdvancedOptionPreferences();
        m_translateTxtRegex.setSelected(Preferences.isPreferenceDefault(Preferences.SEARCHWINDOW_PRETRA_WITH_VARS, true));
        String replMode = Preferences.getPreferenceDefault(Preferences.SEARCHWINDOW_PRETRA_TXT_MODE, PRETRA_ONLY_TEXT);
        m_translateTxtEntire.setSelected(replMode.equals(PRETRA_ONLY_TEXT));
        m_translateTxtFound.setSelected(replMode.equals(PRETRA_COPY_EXACT));
        m_translateTxtWhole.setSelected(replMode.equals(PRETRA_COPY_WHOLE));
		replMode = Preferences.getPreferenceDefault(Preferences.SEARCHWINDOW_PRETRA_SRC_MODE, PRETRA_SRC_FULL);
        m_translateSrcEntire.setSelected(! replMode.equals(PRETRA_SRC_FOUND));
        m_translateSrcFound.setSelected(replMode.equals(PRETRA_SRC_FOUND));
        m_translateSrcGlos.setSelected(Preferences.isPreferenceDefault(Preferences.SEARCHWINDOW_PRETRA_SRC_GLOS, false));
        m_translateSrcGlosOnly.setSelected(Preferences.isPreferenceDefault(Preferences.SEARCHWINDOW_PRETRA_SRC_GLOS_ONLY, false));
		m_translateSrcGlosOnly.setEnabled(m_translateSrcGlos.isSelected());
        String searchState = Preferences.getPreferenceDefault(Preferences.SEARCHWINDOW_PRETRA_STATE, "UNTRANSLATED");
        m_searchTranslated.setSelected(TranslationStateFilter.TRANSLATED_ONLY.toString().equals(searchState)); 
        m_searchUntranslated.setSelected(TranslationStateFilter.UNTRANSLATED_ONLY.toString().equals(searchState));  
        m_searchTranslatedUntranslated.setSelected(TranslationStateFilter.TRANSLATED_AND_UNTRANSLATED.toString().equals(searchState));
        String replaceType = Preferences.getPreferenceDefault(Preferences.SEARCHWINDOW_PRETRA_TYPE, PRETRA_TYPE_TEXT);
        m_translateSource.setSelected(replaceType.equals(PRETRA_TYPE_SOURCE));
        m_translateText.setSelected(replaceType.equals(PRETRA_TYPE_TEXT)); 
        m_translateMatch.setSelected(replaceType.equals(PRETRA_TYPE_MATCH));
        m_translateAuto.setSelected(replaceType.equals(PRETRA_TYPE_AUTO));
        for (ActionListener al : m_translateText.getActionListeners()) al.actionPerformed(null);
        /*if (m_translateMatch.isSelected()) m_scoreSelector.setSelectedItem(SORT_KEY.SCORE); else*/ if (m_translateAuto.isSelected()) m_prefixField.setText("[MT]"); else m_prefixField.setText(""); 
        switch (Preferences.getPreferenceDefault(Preferences.SEARCHWINDOW_PRETRA_MATCH_CRITERIA, SORT_KEY.SCORE.toString())) {
            case "SCORE": m_scoreSelector.setSelectedItem(SORT_KEY.SCORE); break;
            case "SCORE_NO_STEM": m_scoreSelector.setSelectedItem(SORT_KEY.SCORE_NO_STEM); break;
            case "ADJUSTED_SCORE": m_scoreSelector.setSelectedItem(SORT_KEY.ADJUSTED_SCORE); break;
        }
        m_matchTM.setSelected ("TM".equals(Preferences.getPreferenceDefault(Preferences.SEARCHWINDOW_PRETRA_MATCH_IN, "TM")));
        m_matchSource.setSelected ("SRC".equals(Preferences.getPreferenceDefault(Preferences.SEARCHWINDOW_PRETRA_MATCH_IN, "TM")));
        setMatchEnabled (m_translateMatch.isSelected());
		m_minScore.setEnabled (m_translateMatch.isSelected()); m_scoreSelector.setEnabled (m_translateMatch.isSelected());		
        try {	// treat min_score at the end, because it depends from others!
			m_minScore.setValue(
				m_matchTM.isSelected() ? Preferences.getPreferenceDefault(Preferences.SEARCHWINDOW_PRETRA_MATCH_SCORE, 80)
				: Preferences.getPreferenceDefault(Preferences.SEARCHWINDOW_PRETRA_MATCH_SCORE_XSRC, FUZZY_MATCH_THRESHOLD)
			);
		} catch (Exception ignored) {}
    }
    
    @Override // Also save replacement mode properties  
    protected void savePreferences() {
        super.savePreferences();
        Preferences.setPreference(Preferences.SEARCHWINDOW_PRETRA_WITH_VARS, Boolean.toString(m_translateTxtRegex.isSelected()));
        if (m_translateTxtEntire.isSelected()) Preferences.setPreference(Preferences.SEARCHWINDOW_PRETRA_TXT_MODE, PRETRA_ONLY_TEXT);
        else if (m_translateTxtFound.isSelected()) Preferences.setPreference(Preferences.SEARCHWINDOW_PRETRA_TXT_MODE, PRETRA_COPY_EXACT);
        else if (m_translateTxtWhole.isSelected()) Preferences.setPreference(Preferences.SEARCHWINDOW_PRETRA_TXT_MODE, PRETRA_COPY_WHOLE);
        Preferences.setPreference(Preferences.SEARCHWINDOW_PRETRA_SRC_GLOS, Boolean.toString(m_translateSrcGlos.isSelected()));
        Preferences.setPreference(Preferences.SEARCHWINDOW_PRETRA_SRC_GLOS_ONLY, Boolean.toString(m_translateSrcGlosOnly.isSelected()));
        if (m_translateSrcFound.isSelected()) Preferences.setPreference(Preferences.SEARCHWINDOW_PRETRA_SRC_MODE, PRETRA_SRC_FOUND);
        else /*if (m_translateSrcEntire.isSelected())*/ Preferences.setPreference(Preferences.SEARCHWINDOW_PRETRA_SRC_MODE, PRETRA_SRC_FULL);
        if (m_translateSource.isSelected()) Preferences.setPreference(Preferences.SEARCHWINDOW_PRETRA_TYPE, PRETRA_TYPE_SOURCE);
        else if (m_translateText.isSelected()) Preferences.setPreference(Preferences.SEARCHWINDOW_PRETRA_TYPE, PRETRA_TYPE_TEXT);
        else if (m_translateMatch.isSelected()) Preferences.setPreference(Preferences.SEARCHWINDOW_PRETRA_TYPE, PRETRA_TYPE_MATCH);
        else if (m_translateAuto.isSelected()) Preferences.setPreference(Preferences.SEARCHWINDOW_PRETRA_TYPE, PRETRA_TYPE_AUTO);
        Preferences.setPreference(Preferences.SEARCHWINDOW_PRETRA_STATE,buildTranslationStateFilter().toString());
        Preferences.setPreference(m_matchSource.isSelected() ? Preferences.SEARCHWINDOW_PRETRA_MATCH_SCORE_XSRC : Preferences.SEARCHWINDOW_PRETRA_MATCH_SCORE, m_minScore.getValue().toString());
        Preferences.setPreference(Preferences.SEARCHWINDOW_PRETRA_MATCH_CRITERIA,m_scoreSelector.getSelectedItem().toString());
        Preferences.setPreference(Preferences.SEARCHWINDOW_PRETRA_MATCH_IN,m_matchSource.isSelected() ? "SRC" : "TM");
        Preferences.save();
    }

    private JLabel m_searchLabel;
    private JComboBox m_searchField;
    private JLabel m_translateLabel;
    private JComboBox m_translateField;
    private JTextField m_prefixField;
    private JRadioButton m_translateTxtEntire, m_translateTxtWhole, m_translateTxtFound;
    private JCheckBox m_translateTxtRegex;
    private JRadioButton m_translateSrcEntire, m_translateSrcFound;
    private JCheckBox m_translateSrcGlos, m_translateSrcGlosOnly;
    private JCheckBox m_transAsAlternative;
    private JButton m_translateInt, m_translateAll;
    private JRadioButton m_translateSource, m_translateText, m_translateMatch, m_translateAuto;
    private JRadioButton m_searchTranslated, m_searchUntranslated, m_searchTranslatedUntranslated;
    private JRadioButton m_matchTM, m_matchSource;
    private JTextField m_TMpath;
    private javax.swing.JSpinner m_groupSize;
    private Box bMatch;
    private JButton memTraBtn;
    private JComboBox<SORT_KEY> m_scoreSelector;
    private javax.swing.JSpinner m_minScore;
    private JComboBox m_preferredMt;
}
