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

 Copyright (C) 2015-2018 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.core.search;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.ArrayList;
import java.util.List;

import org.omegat.core.Core;
import org.omegat.core.data.IProject;
import org.omegat.core.data.ProjectProperties;
import org.omegat.core.data.SourceTextEntry;
import org.omegat.core.data.TMXEntry;
import org.omegat.core.matching.NearString;
import org.omegat.core.matching.external.IExternalMemory;
import org.omegat.core.statistics.FindMatches;
import org.omegat.core.events.IStopped;
import org.omegat.gui.exttrans.IMachineTranslation;
import org.omegat.util.Language;
import org.omegat.util.Preferences;
import org.omegat.gui.search.PreTranslateDialog;
import org.omegat.gui.search.SearchVarExpansion;


/**
 * Search in the project, 
 * 
 * @author Thomas Cordonnier
 */
public class PreTranslateSearcher extends ProjectSearcher implements IStopped {
    
	public static final byte TRANSLATE_ENTIRE = 0, TRANSLATE_WHOLE = 1, TRANSLATE_FOUND = 2;
    
    /**
     * Create new searcher instance.
     * 
     * @param project
     *            Current project
     */
    public PreTranslateSearcher(final IProject project, PreTranslateDialog window,  int numberOfResults, 
        TextExpression searchSource, TranslationStateFilter filter, IMachineTranslation translator, String prefix,
        TextExpression author, long dateAfter, long dateBefore) {

        super (window, project, 
            false, numberOfResults, 
            filter, ProjectSearcher.SEARCH_SCOPE_ONGOING, // Can modify translated segments, but search only ongoing
            searchSource, null, null, false, // search only in source
            author, null, dateAfter, dateBefore);
        
        this.m_prefix = prefix;
		this.m_replaceContent = translator;
    }
    
    public boolean isStopped() { return isInterrupted(); }	
    
	// ---- Factories for translators which only exist for pre-translation
	
	public IMachineTranslation buildTranslationMemoriesPreTranslator(final NearString.SORT_KEY criteria, final int minValue, final String memoryPath) {
		return new IMachineTranslation() {
            FindMatches matches = new FindMatches(m_project.getSourceTokenizer(), new IExternalMemory[0], memoryPath, 1, false, false);

            public String getName() { return "***TranslationMemory***"; }

            public NearString bestTranslation(String text) {
                List<NearString> list = matches.search(m_project, text, true, false, PreTranslateSearcher.this);
                return list.isEmpty() ? null : list.get(0);
            }

            public String getTranslation(Language sLang, Language tLang, String text) {
                NearString best = bestTranslation(text); if (best == null) return null;
                switch (criteria) {
                    case SCORE: if (best.score < minValue) return null; break;
                    case SCORE_NO_STEM: if (best.scoreNoStem < minValue) return null; break;
                    case ADJUSTED_SCORE: if (best.adjustedScore < minValue) return null; break;
                }
                String tra = bestTranslation(text).translation;
                if (Preferences.isPreference(Preferences.CONVERT_NUMBERS))
                    tra = Core.getMatcher().substituteNumbers(text, best.source, tra);
                String prefix = m_prefix; 
                prefix = prefix.replace("${score}", Integer.toString(best.score));
                prefix = prefix.replace("${noStemScore}", Integer.toString(best.scoreNoStem));
                prefix = prefix.replace("${adjustedScore}", Integer.toString(best.adjustedScore));					
                return prefix + tra;
            }
        };		
	}
	
    private class ReplaceTextMachineTranslation implements IMachineTranslation {
        private String replacement;
		private boolean m_replaceVars;
		private byte m_replaceMode;
        public final TextExpression.RegexTextExpression findSubpart;
        private SearchVarExpansion varExpansion;
        
        public ReplaceTextMachineTranslation(String replacement, byte replaceMode, boolean replaceVars) {
            this.m_replaceMode = replaceMode; this.m_replaceVars = replaceVars;
            if (m_replaceVars && (replacement.contains("${"))) this.varExpansion = new SearchVarExpansion (replacement); else this.varExpansion = null;
            this.replacement = replacement; 
            if (m_replaceVars) replacement = replacement.replaceAll("(?<!\\\\)\\$\\{(\\w+)\\}", "!<$1>"); // avoid interpretation by following lines
            // Try to build a searcher for highlighting
            if (! replaceVars) this.findSubpart = new TextExpression.RegexTextExpression(escapeRegexChars(replacement).replace("$", "\\$"), true);
            else if (! replacement.contains("$")) this.findSubpart = new TextExpression.RegexTextExpression(escapeRegexChars(replacement), true);
			else if (m_searchSource instanceof TextExpression.ExactTokenExpression) this.findSubpart = new TextExpression.RegexTextExpression(escapeRegexChars(replacement).replaceAll("(?<!\\\\)\\$\\d+", "(\\\\p{L}+)"), true);
			else if (m_searchSource instanceof TextExpression.RegexTextExpression) {
                String pattern = ((TextExpression.RegexTextExpression) m_searchSource).getPattern().pattern();
                Matcher m = Pattern.compile("\\((.+?)\\)").matcher(pattern);
                List<String> groups = new ArrayList<String>(); while (m.find()) groups.add(m.group(1));
                StringBuffer buf = new StringBuffer(); String copy = escapeRegexChars(replacement);
                while (copy.contains("$")) {
                    buf.append (copy.substring(0, copy.indexOf("$"))); copy = copy.substring(copy.indexOf("$"));
                    m = Pattern.compile("(?<!\\\\)\\$(\\d+)").matcher(copy);
                    if (m.find()) { 
                        int id = Integer.parseInt(m.group(1));
                        if (id == 0) buf.append("(" + pattern + ")");
                        else if (groups.size() > id) buf.append("(").append(groups.get(id - 1)).append(")"); 
                        else buf.append("(\\S+)");
                        copy = copy.substring(m.group(0).length());
                    } else {
                        buf.append ("\\$"); copy = copy.substring(1);
                    }
                }
                buf.append(copy);
                this.findSubpart = new TextExpression.RegexTextExpression(buf.toString(), true);
            }
            else this.findSubpart = new TextExpression.RegexTextExpression(replacement, true);
        }
        
        private final char[] ESCAPE_REGEX = { '*', '+', '[', ']', '(', ')' };
        private String escapeRegexChars (String ori) {			
            for (char ch: ESCAPE_REGEX) ori = ori.replace("" + ch, "\\" + ch);
            return ori;
        }

        public String getName() { return ""; }
                    
        public String getTranslation(Language sLang, Language tLang, String oriText) {
            if (m_replaceMode == TRANSLATE_ENTIRE) 				
				if (! m_replaceVars) return replacement; // trivial case
				else {
					List<SearchMatch> matches = m_searchSource.searchString(oriText);
					String repl = replacement; boolean found = false;
					Matcher replaceMatcher = Pattern.compile("(?<!\\\\)\\$\\^(\\d+)").matcher(repl);
					while (replaceMatcher.find()) {
						int varId = Integer.parseInt(replaceMatcher.group(1));
						repl = replaceMatcher.replaceFirst( oriText.substring(matches.get(varId - 1).getStart(), matches.get(varId - 1).getEnd()) );
						replaceMatcher.reset(repl); found = true;
					}
					if (found) return org.omegat.util.StringUtil.replaceCase(repl, org.omegat.core.Core.getProject().getProjectProperties().getTargetLanguage().getLocale());
					else try { return ((ReplaceMatch) matches.get(0)).getReplacement(); } catch (Exception e) { return replacement; }
					
				}
            else {
				List<SearchMatch> matches = m_searchSource.searchString(oriText);
				if (m_replaceMode == TRANSLATE_WHOLE) {
					List<SearchMatch> englobedList = new ArrayList<> (matches.size());
					for (SearchMatch m: matches) englobedList.add (m.englobeWord(oriText));
					matches = englobedList;
				}
				return ReplaceMatch.buildReplacedString(oriText, matches, replacement);
            } 
        }
        
        public void reset (SourceTextEntry ste) {
            if (varExpansion != null) replacement = varExpansion.apply (new SourceSearchResultEntry(ste, null, null, null));
        }
    }
	
	public IMachineTranslation buildReplacerPreTranslator(String replacement, byte replaceMode, boolean replaceVars) {
		return new ReplaceTextMachineTranslation(replacement, replaceMode, replaceVars);
	}
	
	public void setTranslator (IMachineTranslation tra) { this.m_replaceContent = tra; }
	
	// -------------- Searcher extension
	
	@Override
	protected PreTranslateSearchResultEntry buildOngoingEntry (SourceTextEntry ste, TMXEntry tmxEntry, List<SearchMatch> srcMatches, List<SearchMatch> targetMatches, List<SearchMatch> noteMatches) {
		PreTranslateSearchResultEntry entry = new PreTranslateSearchResultEntry(ste, tmxEntry, srcMatches, targetMatches, noteMatches);        
		try { ((ReplaceTextMachineTranslation) m_replaceContent).reset (ste); } catch (Exception ignored) {}
		try {
            String translation = m_replaceContent.getTranslation (Core.getProject().getProjectProperties().getSourceLanguage(), Core.getProject().getProjectProperties().getTargetLanguage(), ste.getSrcText());
            if (translation == null) return null; // for MT/TM replacers : if no match found, do not even include this entry in the list
			if (! m_replaceContent.getName().equals("***TranslationMemory***")) translation = m_prefix + translation;
            entry.setTranslationResult(translation);
            if (m_replaceContent instanceof ReplaceTextMachineTranslation)
                entry.setHighlights( ((ReplaceTextMachineTranslation) m_replaceContent).findSubpart.searchString(translation) );
            else
                if (m_prefix.contains("$")) entry.setHighlights(  new TextExpression.RegexTextExpression("^\\[(.+?)\\]", true).searchString(translation) );
                else if (m_prefix.length() > 0) entry.addHighlight(0, m_prefix.length(), false); 	
        } catch (Exception e) {			
            e.printStackTrace();
            entry.setTranslationResult("[ERROR] " + e.getMessage());
            entry.addHighlight(0, 7, true);
        }
        return entry;		
	}
	
    
    /** Apply the replacement which is at selected position **/	 
    public String replaceOccurence (String oriText, int pos, SourceTextEntry ste) {
        try {
			ProjectProperties prop = m_project.getProjectProperties();
            return m_prefix + m_replaceContent.getTranslation (prop.getSourceLanguage(), prop.getTargetLanguage(), oriText); 
        } catch (Exception e) {
            return oriText;
        }
    }
    
    private String m_prefix;
    private IMachineTranslation m_replaceContent;

}
