/**************************************************************************
 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.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, SourceTextEntry> existEntries = new HashMap<String, 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) 
            existEntries.put(ste.getSrcText(), ste.firstInstance == null ? ste : ste.firstInstance);
    }

    /**
     * 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();        
        while (cursor.next()) { // iterate by all entries in TMX
            SourceTextEntry topEntry = existEntries.get(cursor.getEntrySource());
            if (topEntry == null) continue; // there is no entries for this source
            // else:
            processEntry(cursor, topEntry, isEnforcedTMX);
            if (topEntry.duplicates != null)
               for (SourceTextEntry ste: topEntry.duplicates)  processEntry(cursor, ste, isEnforcedTMX);
        }
    }    
    
    void processEntry(IEntryCursor cursor, SourceTextEntry ste, boolean isEnforcedTMX) throws Exception {
                TMXEntry existTranslation = project.getTranslationInfo(ste);
                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) // Never automatically include matches from foreign languages.
                    if (! e.getPropValue("x-target-lang").equalsIgnoreCase(project.getProjectProperties().getTargetLanguage().toString())) return; 

                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.
                        return;
                    }
                    if (!isDefaultTranslation && !altTranslationMatches(e, ste.getKey())) {
                        // TMX entry is an alternative translation that does not match this STE.
                        return;
                    }
                    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;
        }
    }
    
    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);
    }
}
