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

               2012 Thomas Cordonnier
 Copyright (C) 2013 Alex Buloichik
               2015 Thomas Cordonnier
               2017 Thomas Cordonnier
               Home page: http://www.omegat.org/
               Support center: http://groups.yahoo.com/group/OmegaT/

 This file is part of OmegaT.

 OmegaT 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 3 of the License, or
 (at your option) any later version.

 OmegaT 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, see <http://www.gnu.org/licenses/>.
 **************************************************************************/

package org.omegat.gui.editor.filter;

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.omegat.core.Core;
import org.omegat.core.data.PrepareTMXEntry;
import org.omegat.core.data.SourceTextEntry;
import org.omegat.core.data.TMXEntry;
import org.omegat.core.search.SearchMatch;
import org.omegat.core.search.ReplaceMatch;
import org.omegat.core.search.ProjectSearcher;
import org.omegat.core.search.TranslationStateFilter;
import org.omegat.gui.editor.EditorController;
import org.omegat.gui.editor.IEditor;
import org.omegat.gui.editor.IEditor.CaretPosition;
import org.omegat.gui.editor.IEditorFilter;
import org.omegat.gui.search.ReplaceDialog;

/**
 * Editor filter implementation.
 * 
 * @author Alex Buloichik (alex73mail@gmail.com)
 * @author Thomas Cordonnier
 */
public class ReplaceFilter implements IEditorFilter {
    private final Map<Integer, SourceTextEntry> entries = new HashMap<Integer, SourceTextEntry>();
    private FilterBarReplace controlComponent;
    private ProjectSearcher searcher;
    private ReplaceDialog dialog;
    private int minEntryNum, maxEntryNum;

    public ReplaceFilter(List<Integer> entriesList, ProjectSearcher searcher, ReplaceDialog dialog) {
        this.searcher = searcher;
        this.dialog = dialog;

        minEntryNum = Integer.MAX_VALUE;
        maxEntryNum = Integer.MIN_VALUE;
        Set<Integer> display = new HashSet<Integer>(entriesList);
        for (SourceTextEntry ste : Core.getProject().getAllEntries()) {
            minEntryNum = Math.min(minEntryNum, ste.entryNum());
            maxEntryNum = Math.max(maxEntryNum, ste.entryNum());
            if (display.contains(ste.entryNum())) {
                entries.put(ste.entryNum(), ste);
            }
        }

        controlComponent = new FilterBarReplace();

        controlComponent.btnCancel.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                Core.getEditor().commitAndDeactivate(); // Make sure that any change done in the current segment is not lost
                Core.getEditor().removeFilter();
            }
        });
        controlComponent.btnSkip.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                skip();
            }
        });
        controlComponent.btnReplaceNext.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                replace();
            }
        });
    }

    @Override
    public boolean isSourceAsEmptyTranslation() {
        return true;
    }

    /**
     * Replace all occurrences in all entries.
     */
    public void replaceAll(javax.swing.JLabel label) {
        EditorController ec = (EditorController) Core.getEditor(); ec.commitAndDeactivate(); // Make sure that any change done in the current segment is not lost
        int i = 0;
        for (SourceTextEntry ste : entries.values()) {
            TMXEntry en = Core.getProject().getTranslationInfo(ste);
            String trans = getEntryText(ste, en);
            if (trans == null) {
                continue;
            }
            // Avoid to replace more than once with variables when entries have duplicates
            if ((en.defaultTranslation) && (ste.getDuplicate() == SourceTextEntry.DUPLICATE.NEXT)) {
                continue; // Already replaced when we parsed the first entry
            }
            List<SearchMatch> found = getReplacementsForEntry(trans);
            if (found != null) {
                PrepareTMXEntry prepare = new PrepareTMXEntry(en);
                prepare.translation = dialog.buildReplacedString(trans, found);
                Core.getProject().setTranslation(ste, prepare, en.defaultTranslation, null);
            }
            
            if (label != null) label.setText ("" + (++i / entries.size()) + "/" + entries.size() + " replaced."); 
        }
        ec.refreshEntries(entries.keySet()); ec.activateEntry();
    }

    @Override
    public boolean allowed(SourceTextEntry ste) {
        return entries.containsKey(ste.entryNum());
    }

    @Override
    public Component getControlComponent() {
        return controlComponent;
    }

    public List<SearchMatch> getReplacementsForEntry(String translationText) {
        return searcher.searchInTranslation(translationText);    
    }

    private void skip() {
        EditorController ec = (EditorController) Core.getEditor();

        // try to find in current entry
        int pos = ec.getCurrentPositionInEntryTranslation();
        String str = ec.getCurrentTranslation();
        List<SearchMatch> found = getReplacementsForEntry(str);
        if (found != null) {
            for (SearchMatch m : found) {
                if (m.getStart() >= pos) {
                    ec.setCaretPosition(new IEditor.CaretPosition(m.getStart(), m.getEnd()));
                    ec.requestFocus();
                    return;
                }
            }
        }
        // not found in current entry - find next entry
        int currentEntryNumber = ec.getCurrentEntryNumber();
        ec.commitAndDeactivate();

        // find to the end of project
        for (int i = currentEntryNumber + 1; i <= maxEntryNum; i++) {
            SourceTextEntry ste = entries.get(i);
            if (ste == null) {
                continue; // entry not filtered
            }
            TMXEntry en = Core.getProject().getTranslationInfo(ste);
            String trans = getEntryText(ste, en);
            if (trans == null) {
                continue;
            }
            found = getReplacementsForEntry(trans);
            if (found == null) {
                continue; // no replacements
            }
            for (SearchMatch m : found) {
                ec.gotoEntry(i, new CaretPosition(m.getStart(), m.getEnd()));
                ec.requestFocus();
                return;
            }
        }
        // find from the beginning of project
        for (int i = minEntryNum; i < currentEntryNumber; i++) {
            SourceTextEntry ste = entries.get(i);
            if (ste == null) {
                continue; // entry not filtered
            }
            TMXEntry en = Core.getProject().getTranslationInfo(ste);
            String trans = getEntryText(ste, en);
            if (trans == null) {
                continue;
            }
            found = getReplacementsForEntry(trans);
            if (found == null) {
                continue; // no replacements
            }
            for (SearchMatch m : found) {
                ec.gotoEntry(i, new CaretPosition(m.getStart(), m.getEnd()));
                ec.requestFocus();
                return;
            }
        }
        // not found
        ec.activateEntry();
    }

    private void replace() {
        EditorController ec = (EditorController) Core.getEditor();

        // is caret inside match ?
        int pos = ec.getCurrentPositionInEntryTranslation();
        String str = ec.getCurrentTranslation();
        List<SearchMatch> found = getReplacementsForEntry(str);
        if (found != null) {
            for (SearchMatch m : found) {
                if (m.getStart() <= pos && pos <= m.getEnd()) {
                    // yes - replace
                    String repl;
                    try {
                        repl = ((ReplaceMatch) m).getReplacement();
                    } catch (ClassCastException cce) { // non-regex replacement
                        repl = dialog.getReplaceText();
                    }
					if (ReplaceDialog.REPL_SEGMENT.equals(dialog.getReplacementMode())) ec.replacePartOfText(repl, 0, str.length());
					else {
						SearchMatch eng = m;
						if (ReplaceDialog.REPL_WORD_WHOLE.equals(dialog.getReplacementMode())) eng = m.englobeWord(str);
						ec.replacePartOfText(repl, m.getStart(), m.getEnd());
					}
                    break;
                }
            }
        }
        // skip to next
        skip();
    }

    /**
     * Returns text of entry where replacement should be found. It can be translation or source text depends
     * on settings, or null if entry should be skipped.
     */
    private String getEntryText(SourceTextEntry ste, TMXEntry en) {
        if (en.isTranslated()) {
            return en.translation;
        } else if ((searcher.getTranslationStateFilter().value & TranslationStateFilter.FLAG_UNTRANSLATED) > 0) {
            return ste.getSrcText();
        } else {
            return null;
        }
    }
}
