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

 Copyright (C) 2000-2006 Keith Godfrey and Maxym Mykhalchuk
               2006 Henry Pijffers
               2009 Didier Briel
               2010 Martin Fleurke, Antonio Vilei, Didier Briel
               2012 Thomas Cordonnier
               2014 Thomas Cordonnier
               2015 Thomas Cordonnier
               2017 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.BoxLayout;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JCheckBox;
import javax.swing.JButton;
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.ProjectSearcher;
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
 */
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 PopupFactory(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(), 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(), 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_numberModel.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();
                            } 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 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")));
        bVertical.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);
            
        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; // Always search on translation only	
    }
	
    @Override    
    protected ProjectSearcher buildSearcher() throws Exception {
        if ((m_searchField.getSelectedItem() == null) || (m_searchField.getSelectedItem().toString().length() == 0)) throw new Exception(OStrings.getString("SW_ERROR_MANDATORY_FIELD_SEARCH"));
		return new ProjectSearcher (this, Core.getProject(), false, Integer.MAX_VALUE, 
            TranslationStateFilter.TRANSLATED_ONLY, ProjectSearcher.SEARCH_SCOPE_ONGOING, // Force these two values
            null, m_modePanel.buildReplaceExpression (m_searchField.getSelectedItem().toString(), false, 
                m_replaceField.getSelectedItem().toString(), m_replaceWithVars.isSelected()), 
            null, false,  // search only in target		
            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
        );
    }

    @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() {
        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;
	}	
	
    private JComboBox m_searchField,m_replaceField;
    private JButton m_replaceInt, m_replaceAll;
    private JRadioButton m_replaceSegment, m_replaceWhole, m_replaceExact;
    private JCheckBox m_replaceWithVars;
}
