/**************************************************************************
 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 Didier Briel, Thomas Cordonnier
               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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 **************************************************************************/

package org.omegat.gui.search;

import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JRadioButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JTextField;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.text.JTextComponent;

import java.util.Set;
import java.util.TreeSet;

import org.openide.awt.Mnemonics;

import org.omegat.gui.main.MainWindow;
import org.omegat.gui.editor.filter.SearchFilter;
import org.omegat.gui.editor.EditorPopups;
import org.omegat.util.OStrings;
import org.omegat.util.StringUtil;
import org.omegat.util.Preferences;
import org.omegat.core.search.Searcher;
import org.omegat.core.search.ProjectSearcher;
import org.omegat.core.search.FullProjectSearcher;
import org.omegat.core.search.TextExpression;
import org.omegat.core.search.TranslationStateFilter;
import org.omegat.core.Core;

/**
 * This is a window that appears when user'd like to search for something. For
 * each new user's request new window is created. Actual search is done by
 * FullProjectSearcher.
 * 
 * @author Keith Godfrey
 * @author Henry Pijffers (henry.pijffers@saxnot.com)
 * @author Didier Briel
 * @author Martin Fleurke
 * @author Antonio Vilei
 * @author Thomas Cordonnier
 */
@SuppressWarnings("serial")
public class FullProjectSearchWindow extends ProjectSearchWindow {
    private static final java.util.Map<String,Set<String>> theMap = new java.util.HashMap<String,Set<String>>();
    static {		
        for (String field: new String[] { "SW_SEARCH_SOURCE", "SW_SEARCH_TRANSLATION", "SW_SEARCH_NOTES" })
            for (String mode: new String[] { "EXACT", "KEYWORD", "REGEXP" })
                theMap.put (field + ":" + mode, new TreeSet<String>());
        TreeSet<String> fileSet = new TreeSet<String>();
        theMap.put("SW_SEARCH_FILE:EXACT", fileSet); theMap.put("SW_SEARCH_FILE:KEYWORD", fileSet); theMap.put("SW_SEARCH_FILE:REGEXP", fileSet);
        org.omegat.core.CoreEvents.registerProjectChangeListener(new SearchesLoader<Set<String>>("FullProjectSearchWindow", theMap));
    }

    public void refreshLists(boolean removeContents) {
        m_searchSource.changeValuesSet(theMap.get("SW_SEARCH_SOURCE:" + m_modePanel.searchTypeString()), removeContents);
        m_searchTarget.changeValuesSet(theMap.get("SW_SEARCH_TRANSLATION:" + m_modePanel.searchTypeString()), removeContents);
        m_searchNotes.changeValuesSet(theMap.get("SW_SEARCH_NOTES:" + m_modePanel.searchTypeString()), removeContents);
        m_FileNameField.changeValuesSet(theMap.get("SW_SEARCH_FILE:" + m_modePanel.searchTypeString()), removeContents);
    }

    public FullProjectSearchWindow(MainWindow par, String startText) {
        super(par, startText);
        refreshLists(true); // after all components, in particular the m_modePanel, have been built
        java.util.Map<MemoryTextField,org.omegat.gui.editor.PopupMenuDisplayer> factories = new java.util.HashMap<>();
        for (MemoryTextField box: new MemoryTextField[]{ m_searchSource, m_searchTarget, m_searchNotes }) {
            JTextComponent searchFieldArea = (JTextComponent) box.getTextField().getEditor().getEditorComponent();
            factories.put(box, getPopupFactory(searchFieldArea)); searchFieldArea.addMouseListener(factories.get(box));
        }
        factories.get(m_searchSource).popupConstructors.put(600, 
            (javax.swing.JPopupMenu menu, JTextComponent comp, int mousepos, boolean isInActiveEntry, boolean isInActiveTranslation, org.omegat.gui.editor.SegmentBuilder sb) -> {
                if ((m_searchTarget.getValue() != null) && (m_searchTarget.getValue().trim().length() > 0)) {
                    Set<String> entries = new TreeSet<>();
                    for (org.omegat.core.glossaries.GlossaryEntryStore entry: Core.getGlossaryManager().getGlossaryEntries(m_searchTarget.getValue())) 
                        if (m_searchTarget.getValue().equals(entry.getLocText())) entries.add(entry.getSrcText());                    
                    EditorPopups.IMenuReceiver receiver = EditorPopups.createReceiver(menu, entries.size(), "Insert from glossary");
                    for (String entry: entries) receiver.addItem(entry, ev -> m_searchTarget.getTextField().setSelectedItem(entry));
                    menu.addSeparator();
                }
            });
        factories.get(m_searchTarget).popupConstructors.put(600, 
            (javax.swing.JPopupMenu menu, JTextComponent comp, int mousepos, boolean isInActiveEntry, boolean isInActiveTranslation, org.omegat.gui.editor.SegmentBuilder sb) -> {
                if ((m_searchSource.getValue() != null) && (m_searchSource.getValue().trim().length() > 0)) {
                    Set<String> entries = new TreeSet<>();
                    for (org.omegat.core.glossaries.GlossaryEntryStore entry: Core.getGlossaryManager().getGlossaryEntries(m_searchSource.getValue()))
                        if (m_searchSource.getValue().equals(entry.getSrcText()))  entries.add(entry.getLocText()); 
                    EditorPopups.IMenuReceiver receiver = EditorPopups.createReceiver(menu, entries.size(), "Insert from glossary");
                    for (String entry: entries) receiver.addItem(entry, ev -> m_searchTarget.getTextField().setSelectedItem(entry));
                    menu.addSeparator();
                }  
            });
		
        if (startText != null) getMainSearchTextField().setSelectedItem(startText);
        m_modePanel.addModeChangeListener(new RegexModeSwitchListener(m_modePanel, ((JTextComponent) m_searchSource.getTextField().getEditor().getEditorComponent())));
        m_modePanel.addModeChangeListener(new RegexModeSwitchListener(m_modePanel, ((JTextComponent) m_searchTarget.getTextField().getEditor().getEditorComponent())));
        m_modePanel.addModeChangeListener(new RegexModeSwitchListener(m_modePanel, ((JTextComponent) m_searchNotes.getTextField().getEditor().getEditorComponent())));
        getMainSearchTextField().requestFocus();
    }
    
    // Build help text
    protected String getScopeText() { return OStrings.getString("SW_HELP_SCOPE_PROJECT"); }
    protected boolean isReplace() { return false; }    
    
    
    @Override
    protected JComponent textPanel(String startText) {
        Box bSearch = Box.createVerticalBox();
        bSearch.setBorder (BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black), OStrings.getString("SW_ZONE_TEXT")));

        m_searchNotes = new MemoryTextField ("SW_SEARCH_NOTES|SW_SEARCH_PROPS", true, theMap.get("SW_SEARCH_NOTES:EXACT"));
        m_searchTarget = new MemoryTextField ("SW_SEARCH_TRANSLATION", true, theMap.get("SW_SEARCH_TRANSLATION:EXACT"));
        m_searchSource = new MemoryTextField ("SW_SEARCH_SOURCE", true, theMap.get("SW_SEARCH_SOURCE:EXACT"));
        m_searchAllSame = new JCheckBox();
        Mnemonics.setLocalizedText(m_searchAllSame, OStrings.getString("SW_ZONE_TEXT_ALL"));
        m_AND = new JRadioButton (OStrings.getString("SW_SEARCH_TEXT_AND")); m_OR = new JRadioButton (OStrings.getString("SW_SEARCH_TEXT_OR"));
        m_ANDTHEN = new JRadioButton (OStrings.getString("SW_SEARCH_TEXT_AND_THEN")); 

        ButtonGroup gOptions = new ButtonGroup();
        gOptions.add (m_AND); gOptions.add(m_OR); gOptions.add(m_ANDTHEN);
        
        bSearch.add(m_searchSource);
        bSearch.add(Box.createHorizontalStrut(H_MARGIN));
        bSearch.add(m_searchTarget);
        bSearch.add(Box.createHorizontalStrut(H_MARGIN));
        bSearch.add(m_searchNotes);
        bSearch.add(Box.createHorizontalStrut(H_MARGIN));
        
        Box bOptions = Box.createHorizontalBox();
        bSearch.add (bOptions);		
        bOptions.add(m_searchAllSame);
        bOptions.add(m_AND); bOptions.add(m_OR); bOptions.add(m_ANDTHEN); 
        m_AND.setSelected(true);
        
        m_searchAllSame.addActionListener (new ActionListener() {
            private final KeyListener dispatch = new KeyListener() {
                public void keyPressed(KeyEvent e) { }
                public void keyTyped(KeyEvent e) { }
                public void keyReleased(KeyEvent e) { 
                    m_searchTarget.setValue(m_searchSource.getValue()); 
                    m_searchNotes.setValue(m_searchSource.getValue()); 
                }
            };
            
            public void actionPerformed (ActionEvent e) {
                if (m_searchAllSame.isSelected()) {
                    m_searchTarget.setValue (m_searchSource.getValue());
                    m_searchNotes.setValue (m_searchSource.getValue());
                    
                    m_searchTarget.getTextField().setEnabled(false);
                    m_searchNotes.getTextField().setEnabled (false);
                    
                    m_searchSource.getTextField().addKeyListener (dispatch);
					m_ANDTHEN.setEnabled(false); m_AND.setSelected(true);
                } else {
                    m_searchTarget.getTextField().setEnabled (m_searchTarget.isOpen());
                    m_searchNotes.getTextField().setEnabled (m_searchNotes.isOpen());
                    m_searchSource.getTextField().removeKeyListener (dispatch);
					
					m_ANDTHEN.setEnabled(true);
                }
            }
        });
        
        return bSearch;
    }
    
    public javax.swing.JComboBox getMainSearchTextField() {
        for (MemoryTextField field: new MemoryTextField[] { m_searchSource, m_searchTarget, m_searchNotes, m_authorField })
            if (field.isOpen()) return field.getTextField();
        return m_searchSource.getTextField();
    }		
    
    @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 = super.optionsPanel();
        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);
        
        return bOB;
    }	
    
    public static final Set<String> L_FILES = new java.util.TreeSet<String>();
    
    @Override
    public Box wherePanel() {
        Box bWhere = Box.createHorizontalBox();
        
        m_sourceSearchCB = new JCheckBox();
        m_tmSearchCB = new JCheckBox();
        m_ongoingSearchCB = new JCheckBox();
        m_orphansSearchCB = new JCheckBox();
        m_glossaryCB = new JCheckBox();

        Mnemonics.setLocalizedText(m_sourceSearchCB, OStrings.getString("SW_SEARCH_IN_SOURCE"));
        Mnemonics.setLocalizedText(m_tmSearchCB, OStrings.getString("SW_SEARCH_IN_TMS"));
        Mnemonics.setLocalizedText(m_ongoingSearchCB, OStrings.getString("SW_SEARCH_IN_ONGOING"));
        Mnemonics.setLocalizedText(m_orphansSearchCB, OStrings.getString("SW_SEARCH_IN_ORPHANS"));
        Mnemonics.setLocalizedText(m_glossaryCB, OStrings.getString("SW_SEARCH_IN_GLOSSARIES"));
        
        bWhere.add(m_sourceSearchCB); bWhere.add(m_ongoingSearchCB); bWhere.add(m_orphansSearchCB);  bWhere.add(m_tmSearchCB); bWhere.add(m_glossaryCB); 
        bWhere.setBorder (BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black), OStrings.getString("SW_SEARCH_SCOPE")));

        bWhere.add(Box.createHorizontalStrut(H_MARGIN));
        bWhere.add (m_FileNameField = new MemoryTextField("SW_SEARCH_FILE", false, L_FILES));
        
        addFocusToSearchListener(m_sourceSearchCB);
        addFocusToSearchListener(m_tmSearchCB);
        addFocusToSearchListener(m_ongoingSearchCB);
        addFocusToSearchListener(m_orphansSearchCB);
        addFocusToSearchListener(m_glossaryCB);
        
        return bWhere;	
    }
        
    protected Box buttonsPanel() {
        Box bCB = super.buttonsPanel();

        m_removeAttachedFilterButton = new JButton();
        m_removeAttachedFilterButton.setEnabled(false);
        m_filterButton = new JButton();
        m_filterButton.setEnabled(false);

        Mnemonics.setLocalizedText(m_filterButton, OStrings.getString("BUTTON_FILTER"));
        Mnemonics.setLocalizedText(m_removeAttachedFilterButton, OStrings.getString("BUTTON_REMOVEFILTER"));
        
        bCB.add(m_removeAttachedFilterButton);
        bCB.add(m_filterButton);
            
        m_filterButton.addActionListener(e -> {
                Core.getEditor().setFilter(new SearchFilter(m_viewer.getEntryList()));
                m_removeAttachedFilterButton.setEnabled(true);
            });
        m_removeAttachedFilterButton.addActionListener(e -> {
                m_removeAttachedFilterButton.setEnabled(false);
                Core.getEditor().removeAttachedFilter();
            });		

        return bCB;
    }
    
    /**
     * Loads the position and size of the search window and the button selection
     * state.
     */
    @Override
    protected void loadPreferences() {
        super.loadPreferences();

        // search boxes
        m_searchSource.setOpen(Preferences.isPreferenceDefault(Preferences.SEARCHWINDOW_SEARCH_SOURCE, true));
        m_searchTarget.setOpen(Preferences.isPreferenceDefault(Preferences.SEARCHWINDOW_SEARCH_TRANSLATION, false));
        m_searchNotes.setOpen(Preferences.isPreferenceDefault(Preferences.SEARCHWINDOW_SEARCH_NOTES, false));

        m_FileNameField.setOpen(Preferences.isPreferenceDefault(Preferences.SEARCHWINDOW_SEARCH_FOLDERNAME, false));

        // Search location		
        m_sourceSearchCB.setSelected(Preferences.isPreferenceDefault(Preferences.SEARCHWINDOW_SOURCE_FILES, false));
        m_ongoingSearchCB.setSelected(Preferences.isPreferenceDefault(Preferences.SEARCHWINDOW_ONGOING, true));
        m_orphansSearchCB.setSelected(Preferences.isPreferenceDefault(Preferences.SEARCHWINDOW_ORPHANS, true));
        m_tmSearchCB.setSelected(Preferences.isPreferenceDefault(Preferences.SEARCHWINDOW_TM_SEARCH, true));
        m_glossaryCB.setSelected(Preferences.isPreferenceDefault(Preferences.SEARCHWINDOW_GLOSSARY_SEARCH, true));
        
        String searchState = Preferences.getPreference(Preferences.SEARCHWINDOW_SEARCH_STATE);
        if (StringUtil.isEmpty(searchState)) searchState = "TRANSLATED_AND_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));
    }
    
    /**
     * Saves the size and position of the search window and the button selection
     * state
     */
    protected void savePreferences() {
        Preferences.setPreference(Preferences.SEARCHWINDOW_SEARCH_SOURCE, Boolean.toString(m_searchSource.isOpen()));
        Preferences.setPreference(Preferences.SEARCHWINDOW_SEARCH_TRANSLATION, Boolean.toString(m_searchTarget.isOpen()));
        Preferences.setPreference(Preferences.SEARCHWINDOW_SEARCH_NOTES, Boolean.toString(m_searchNotes.isOpen()));
        Preferences.setPreference(Preferences.SEARCHWINDOW_SOURCE_FILES, Boolean.toString(m_sourceSearchCB.isSelected())); 		
        Preferences.setPreference(Preferences.SEARCHWINDOW_ONGOING, Boolean.toString(m_ongoingSearchCB.isSelected())); 
        Preferences.setPreference(Preferences.SEARCHWINDOW_ORPHANS, Boolean.toString(m_orphansSearchCB.isSelected())); 
        Preferences.setPreference(Preferences.SEARCHWINDOW_TM_SEARCH, Boolean.toString(m_tmSearchCB.isSelected())); 
        Preferences.setPreference(Preferences.SEARCHWINDOW_GLOSSARY_SEARCH, Boolean.toString(m_glossaryCB.isSelected())); 
        Preferences.setPreference(Preferences.SEARCHWINDOW_SEARCH_STATE,buildTranslationStateFilter().toString());
        super.savePreferences();
    }
    
    @Override
    protected ProjectSearcher buildSearcher(Searcher previous) throws IllegalArgumentException {
        TextExpression exprSource = null, exprTarget = null, exprNote = null;
        if (m_searchSource.isUsed()) exprSource = m_modePanel.buildExpression (m_searchSource.getValue(), m_searchSource.isNot(), true);
        if (m_searchTarget.isUsed()) exprTarget = m_modePanel.buildExpression (m_searchTarget.getValue(), m_searchTarget.isNot(), false);
        if (m_searchNotes.isUsed()) exprNote = m_modePanel.buildExpression (m_searchNotes.getValue(), m_searchNotes.isNot(), false);
    
        if ((exprSource == null) && (exprTarget == null) && (exprNote == null)) throw new IllegalArgumentException(OStrings.getString("SW_ERROR_ALMOST_ONE_FIELD"));
        int whereFlags = (m_sourceSearchCB.isSelected() ? ProjectSearcher.SEARCH_SCOPE_SOURCE_FILES : 0)
                + (m_ongoingSearchCB.isSelected() ? ProjectSearcher.SEARCH_SCOPE_ONGOING : 0)
                + (m_orphansSearchCB.isSelected() ? FullProjectSearcher.SEARCH_SCOPE_ORPHANS : 0)
                + (m_tmSearchCB.isSelected() ? FullProjectSearcher.SEARCH_SCOPE_TM : 0)
                + (m_glossaryCB.isSelected() ? FullProjectSearcher.SEARCH_SCOPE_GLOSSARIES : 0);
        if (whereFlags == 0) throw new IllegalArgumentException (OStrings.getString("SW_ERROR_ALMOST_ONE_SCOPE"));
    
        return new FullProjectSearcher (this, Core.getProject(), 
            m_removeDupCB.isSelected(), m_trueMissingCB.isSelected(), ((Integer) m_numberOfResults.getValue()), 
            buildTranslationStateFilter(), whereFlags, m_FileNameField.isUsed() ? m_FileNameField.getValue() : null,
            exprSource, exprTarget, exprNote, m_searchNotes.getSelectedField(), m_AND.isSelected(), m_ANDTHEN.isSelected(), 
            m_authorField.isUsed() ? m_modePanel.buildExpression(m_authorField.getValue(), m_authorField.isNot(), false) : null, 
            m_translatorField.isUsed() ? m_modePanel.buildExpression(m_translatorField.getValue(), m_translatorField.isNot(), false) : null, 
            m_dateFrom.isUsed() ? m_dateFrom.getValue().getTime() : Long.MAX_VALUE,
            m_dateTo.isUsed() ? m_dateTo.getValue().getTime() : Long.MIN_VALUE
        );
    }
    
    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_viewer.getEntryList().size() > 0)
            return new JComponent[] { m_filterButton };
        else
            // do not enable filtering, because it may throw an exception
            return new JComponent[0];
    }

	protected JTextComponent[] textFieldsList() {
		return new JTextComponent [0];
	}
	
    protected boolean isSearchPossible() {
        return (m_tmSearchCB.isSelected() || m_ongoingSearchCB.isSelected() || m_orphansSearchCB.isSelected() || m_sourceSearchCB.isSelected() || m_glossaryCB.isSelected())
            // && (m_searchSource.isUsed() || m_searchTarget.isUsed() || m_searchNotes.isUsed()) 
            && super.isSearchPossible();
    }

    private JCheckBox m_sourceSearchCB;
    private JCheckBox m_tmSearchCB;
    private JCheckBox m_ongoingSearchCB, m_orphansSearchCB;
    private JCheckBox m_glossaryCB;
    private MemoryTextField m_FileNameField;
    
    private JRadioButton m_searchTranslated, m_searchUntranslated, m_searchTranslatedUntranslated;
    
    private MemoryTextField m_searchSource, m_searchTarget, m_searchNotes;
    private JCheckBox m_searchAllSame;
    private JRadioButton m_AND, m_OR, m_ANDTHEN;

    private JButton m_filterButton;
    private JButton m_removeAttachedFilterButton;
}
