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

               2013 Thomas Cordonnier
 Copyright (C) 2014 Alex Buloichik, Didier Briel
               2019-2023 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.core.data;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.omegat.core.matching.external.IBrowsableMemory;
import org.omegat.util.Preferences;
import org.omegat.core.matching.external.IEntryCursor;
import org.omegat.util.StringUtil;
import org.omegat.util.TMXProp;

/**
 * Utility class for import translations from tm/auto/ files.
 * 
 * @author Alex Buloichik (alex73mail@gmail.com)
 * @author Didier Briel
 * @author Thomas Cordonnier
 */
public class ImportFromAutoTMX {
    final RealProject project;
    /** Map of all segments in project by source text. Just for optimize some processes. */
    Map<String, List<SourceTextEntry>> existEntries = new HashMap<String, List<SourceTextEntry>>();
    // initially false, this variable becomes true if process() did almost one change
    boolean didAnyChange = false;

    public ImportFromAutoTMX(RealProject project, List<SourceTextEntry> allProjectEntries) {
        this.project = project;
        for (SourceTextEntry ste : allProjectEntries) {
            List<SourceTextEntry> list = existEntries.get(ste.getSrcText());
            if (list == null) {
                list = new ArrayList<SourceTextEntry>();
                existEntries.put(ste.getSrcText(), list);
            }
            list.add(ste);
        }
    }
    
    // ------------------------------ build link -------------------
    
    protected void findNonUniqueSegments() {
        for (List<SourceTextEntry> L: existEntries.values())  
            if (L.size() >= 1) {
                L.get(0).duplicates = L.subList(1, L.size());
                for (int i = 1; i < L.size(); i++) L.get(i).firstInstance = L.get(0);
            }
    }
    
    // ------------------------------ Auto TMX -------------------

    /**
     * Process a TMX from an automatic folder
     * @param mem The memory to process
     * @param isEnforcedTMX If true, existing default translations will be overwritten in all cases
     */
    void process(IBrowsableMemory mem, boolean isEnforcedTMX) throws Exception {
        IEntryCursor cursor = mem.browseAllEntries();
    MEM_LOOP:
        while (cursor.next()) { // iterate by all entries in TMX
            List<SourceTextEntry> list = existEntries.get(cursor.getEntrySource());
            if (list == null) continue; // there is no entries for this source
            for (SourceTextEntry ste : list) { // for each TMX entry - get all sources in project
                TMXEntry existTranslation = project.getTranslationInfo(ste.getKey());
                PrepareTMXEntry e = cursor.toPrepareTMXEntry();

                String id = ste.getKey().id;
                boolean hasICE = id != null && e.hasPropValue(ProjectTMX.PROP_XICE, id);
                boolean has100PC = id != null && e.hasPropValue(ProjectTMX.PROP_X100PC, id);
                
                if (e.getPropValue("x-target-lang") != null) { // Entry from other language: apply same rule as for editor
                    String traLang1 = e.getPropValue("x-target-lang");
                    java.util.regex.Matcher matcher = org.omegat.util.PatternConsts.LANG_AND_COUNTRY.matcher(traLang1); if (! matcher.find()) continue;
                    traLang1 = matcher.group(1);
                    if (! traLang1.equalsIgnoreCase(project.getProjectProperties().getTargetLanguage().getLanguageCode())) {
                        String prefForeign = Preferences.getPreferenceDefault(Preferences.EXT_TMX_KEEP_FOREIGN_MATCH, "30 false");
                        if (! prefForeign.contains(";")) continue; // for foreign languages, default to false
                        String[] prefForeigns = prefForeign.split(";"); 
                        for (int i = 0; i < prefForeigns.length - 1; i++) {
                            String traLang2 = prefForeigns[i].substring(0, prefForeigns[i].indexOf(' '));
                            if (traLang1.equalsIgnoreCase(traLang2)) if (prefForeigns[i].contains("with-insert")) continue MEM_LOOP;
                            matcher = org.omegat.util.PatternConsts.LANG_AND_COUNTRY.matcher(traLang2); 
                            if (matcher.find()) traLang2 = matcher.group(1);
                            if (traLang1.equalsIgnoreCase(traLang2)) if (prefForeigns[i].contains("with-insert")) continue MEM_LOOP;
                        }
                    }
                }

                if (!hasICE && !has100PC) {// TMXEntry without x-ids
                    boolean isDefaultTranslation = !isAltTranslation(e);
                    if (!existTranslation.defaultTranslation && isDefaultTranslation) {
                        // Existing translation is alt but the TMX entry is not.
                        continue;
                    }
                    if (!isDefaultTranslation && !altTranslationMatches(e, ste.getKey())) {
                        // TMX entry is an alternative translation that does not match this STE.
                        continue;
                    }
                    if (existTranslation.isTranslated()) { // default translation already exist
                        if (existTranslation.linked == TMXEntry.ExternalLinked.xAUTO
                                && !StringUtil.equalsWithNulls(existTranslation.translation, e.translation) 
                                || isEnforcedTMX) {
                            // translation already from auto and really changed or translation comes 
                            // from the enforce folder
                            setTranslation(ste, e, isDefaultTranslation, TMXEntry.ExternalLinked.xAUTO);
                        }
                    } else {
                        // default translation not exist - use from auto tmx
                        setTranslation(ste, e, isDefaultTranslation, TMXEntry.ExternalLinked.xAUTO);
                    }
                } else {// TMXEntry with x-ids
                    if (!existTranslation.isTranslated() || existTranslation.defaultTranslation) {
                        // need to update if id in xICE or x100PC
                        if (hasICE) {
                            setTranslation(ste, e, false, TMXEntry.ExternalLinked.xICE);
                        } else if (has100PC) {
                            setTranslation(ste, e, false, TMXEntry.ExternalLinked.x100PC);
                        }
                    } else if (existTranslation.linked == TMXEntry.ExternalLinked.xICE
                            || existTranslation.linked == TMXEntry.ExternalLinked.x100PC) {
                        // already contains x-ice
                        if (hasICE
                                && !StringUtil.equalsWithNulls(existTranslation.translation, e.translation)) {
                            setTranslation(ste, e, false, TMXEntry.ExternalLinked.xICE);
                        }
                    } else if (existTranslation.linked == TMXEntry.ExternalLinked.x100PC) {
                        // already contains x-100pc
                        if (has100PC
                                && !StringUtil.equalsWithNulls(existTranslation.translation, e.translation)) {
                            setTranslation(ste, e, false, TMXEntry.ExternalLinked.x100PC);
                        }
                    }
                }
            }
        }
    }
    
    private boolean isAltTranslation(PrepareTMXEntry entry) {
        if (entry.otherProperties == null) return false;
        boolean hasFileProp = false;
        boolean hasOtherProp = false;
        for (TMXProp p : entry.otherProperties) {
            if (p.getType().equals(ProjectTMX.PROP_FILE)) {
                hasFileProp = true;
            } else if (p.getType().equals(ProjectTMX.PROP_ID)
                    || p.getType().equals(ProjectTMX.PROP_NEXT)
                    || p.getType().equals(ProjectTMX.PROP_PATH)
                    || p.getType().equals(ProjectTMX.PROP_PREV)) {
                hasOtherProp = true;
            }
        }
        return EntryKey.IGNORE_FILE_CONTEXT ? hasFileProp && hasOtherProp : hasFileProp;
    }
    
    private boolean altTranslationMatches(PrepareTMXEntry entry, EntryKey key) {
        try {
            for (TMXProp p : entry.otherProperties) {
                if (p.getType().equals(ProjectTMX.PROP_FILE) && EntryKey.IGNORE_FILE_CONTEXT) {
                    continue;
                }
                try {
                    Field f = EntryKey.class.getField(p.getType());
                    Object value = f.get(key);
                    if (!value.equals(p.getValue())) return false;
                }
                catch (NoSuchFieldException nsf) {
                    // TMX can contain extra properties which are not part of the key: ignore them
                }
            }
            return true;
        } catch (Exception e) {
            return false;
        }
    }
    
    // ------------------------------ Source -------------------
    
    /**
     * This method imports translation from source files into ProjectTMX.
     * 
     * If there are multiple segments with equals source, 
     * translations will be loaded as alternative
     * unless they are unique or there are multiple occurences with same translation.
     * 
     * We shouldn't load translation from source file(even as alternative) when
     * default translation already exists in project_save.tmx. So, only first
     * load will be possible.
     */
    void importTranslationsFromSources(boolean supportDefault) {
        for (Map.Entry<String, List<SourceTextEntry>> me: existEntries.entrySet()) {
            if (project.projectTMX.getDefaultTranslation(me.getKey()) != null) continue;
            if (supportDefault)
                if (me.getValue().size() == 1) { 
                    SourceTextEntry ste = me.getValue().get(0);
                    if (ste.getSourceTranslation() == null || ste.isSourceTranslationFuzzy()) continue;
                    setTranslation(ste, ste.getSourceTranslationInfo().asPrepareEntry(me.getKey()), true, TMXEntry.ExternalLinked.xSRC);
                } else {
                    // Search for most frequent one
                    Map<String,Integer> traSet = new HashMap<>(); int max = 0; String freq = null;
                    for (SourceTextEntry ste: me.getValue()) 
                        if (ste.getSourceTranslation() == null || ste.isSourceTranslationFuzzy()) continue;
                        else if (traSet.get(ste.getSourceTranslation()) == null) traSet.put(ste.getSourceTranslation(), 1);
                        else {
                            int count = traSet.get(ste.getSourceTranslation()) + 1; traSet.put(ste.getSourceTranslation(), count);
                            if (count > max) { max = count; freq = ste.getSourceTranslation(); }
                        }
                    for (SourceTextEntry ste: me.getValue()) 
                        if (ste.getSourceTranslation() == null || ste.isSourceTranslationFuzzy()) continue;
                        else if (project.projectTMX.getMultipleTranslation(ste.getKey()) == null)
                            if (max <= 1) 
                                setTranslation(ste, ste.getSourceTranslationInfo().asPrepareEntry(me.getKey()), false, TMXEntry.ExternalLinked.xSRC);   // all as alternatives
                            else if (ste.getSourceTranslation().equals(freq)) 
                                setTranslation(ste, ste.getSourceTranslationInfo().asPrepareEntry(me.getKey()), true, TMXEntry.ExternalLinked.xSRC);
                            else
                                setTranslation(ste, ste.getSourceTranslationInfo().asPrepareEntry(me.getKey()), false, TMXEntry.ExternalLinked.xSRC);
                }
            else // set all as alternatives
                for (SourceTextEntry ste: me.getValue()) 
                    if (ste.getSourceTranslation() == null || ste.isSourceTranslationFuzzy()) continue;
                    else if (project.projectTMX.getMultipleTranslation(ste.getKey()) == null)
                        setTranslation(ste, ste.getSourceTranslationInfo().asPrepareEntry(me.getKey()), false, TMXEntry.ExternalLinked.xSRC);
        }
    }
    
    // ------------------------------ All -------------------
    
    private void setTranslation(SourceTextEntry entry, PrepareTMXEntry trans, boolean defaultTranslation,
            TMXEntry.ExternalLinked externalLinked) {
        if (StringUtil.isEmpty(trans.note)) {
            trans.note = null;
        }

        trans.source = entry.getSrcText();

        if (!didAnyChange) { // check if this call really changes anything
            TMXEntry oldEntry = defaultTranslation ? project.projectTMX.defaults.get(entry.getSrcText())
                : project.projectTMX.alternatives.get(entry.getKey());
            if (oldEntry == null) {
                if (trans.isTranslated()) didAnyChange = true;
            } else if (oldEntry.isTranslated()) {
                if (! oldEntry.getTranslationText().equals(trans.getTranslationText())) didAnyChange = true;
            } else {
                if (trans.isTranslated()) didAnyChange = true;
            }
        }
        
        TMXEntry newTrEntry;

        if (trans.translation == null && trans.note == null) {
            // no translation, no note
            newTrEntry = null;
        } else {
            newTrEntry = new TMXEntry(trans, defaultTranslation, externalLinked);
        }
        project.projectTMX.setTranslation(entry, newTrEntry, defaultTranslation);
    }
}
