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

 Copyright (C) 2011 Alex Buloichik
               2012 Wildrich Fourie
               2013 Didier Briel
               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.core.data;

import java.util.ArrayList;
import java.util.List;

import org.omegat.core.Core;
import org.omegat.core.segmentation.Rule;
import org.omegat.filters2.ITranslateCallback;
import org.omegat.util.Language;
import org.omegat.util.StringUtil;
import org.omegat.util.TagUtil;

/**
 * Base class for entry translation.
 * 
 * This class collects all segments which should be translated, for ability to link prev/next segments for the
 * seconds pass.
 * 
 * @creator Alex Buloichik <alex73mail@gmail.com>
 * @creator Wildrich Fourie
 * @creator Didier Briel
 * @creator Thomas Cordonnier
 */
public abstract class TranslateEntry implements ITranslateCallback {

    private final ProjectProperties m_config;
    
    private int pass;
    
    /** Collected segments. */
    private List<TranslateEntryQueueItem> translateQueue = new ArrayList<TranslateEntryQueueItem>();
    
    /**
     * Index of currently processed segment. It required for multiple translation for use right segment.
     */
    private int currentlyProcessedSegment;

    public TranslateEntry(final ProjectProperties m_config) {
        this.m_config = m_config;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setPass(int pass) {
        this.pass = pass;
        currentlyProcessedSegment = 0;
    }

    protected void fileStarted() {
        currentlyProcessedSegment = 0;
    }
    
    abstract String getCurrentFile();

    protected void fileFinished() {
        if (currentlyProcessedSegment != translateQueue.size()) {
            throw new RuntimeException("Invalid two-pass processing: number of segments are not equals");
        }
        translateQueue.clear();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getNote(final String id, final String origSource, final String path) {
        ParseEntry.ParseEntryResult spr = new ParseEntry.ParseEntryResult();
        
        boolean removeSpaces = Core.getFilterMaster().getConfig().isRemoveSpacesNonseg();
        final String source = StringUtil.normalizeUnicode(ParseEntry.stripSomeChars(
                origSource, spr, m_config.isRemoveTags(), removeSpaces));
        
        if (m_config.isSentenceSegmentingEnabled()) {
            List<StringBuilder> spaces = new ArrayList<StringBuilder>();
            List<Rule> brules = new ArrayList<Rule>();
            Language sourceLang = m_config.getSourceLanguage();
            Language targetLang = m_config.getTargetLanguage();
            List<String> segments = Core.getSegmenter().segment(sourceLang, source, spaces, brules);
            StringBuilder res = new StringBuilder();
            for (int i = 0; i < segments.size(); i++) {
                TMXEntry tex = internalGetSegmentEntry(id, i, segments.get(i), path);
                if (tex != null) if (tex.note != null) res.append(tex.note).append("\n\n");
            }
            return res.toString();
        } else {
            TMXEntry tex = internalGetSegmentEntry(id, 0, source, path);
            if (tex == null)  return null; // non-translated
            return tex.note; // may be null
        }
    }
	
	@Override
	public ITMXEntry getTranslationEntry(String id, String origSource, String path) {
        ParseEntry.ParseEntryResult spr = new ParseEntry.ParseEntryResult();
        
        boolean removeSpaces = Core.getFilterMaster().getConfig().isRemoveSpacesNonseg();
        final String source = StringUtil.normalizeUnicode(ParseEntry.stripSomeChars(
                origSource, spr, m_config.isRemoveTags(), removeSpaces));
        
        if (m_config.isSentenceSegmentingEnabled()) {
            List<StringBuilder> spaces = new ArrayList<StringBuilder>();
            List<Rule> brules = new ArrayList<Rule>();
            Language sourceLang = m_config.getSourceLanguage();
            Language targetLang = m_config.getTargetLanguage();
            List<String> segments = Core.getSegmenter().segment(sourceLang, source, spaces, brules);
			PrepareTMXEntry res = new PrepareTMXEntry();
			res.source = source; res.translation = getTranslation(id,source,path); res.note = getNote(id,source,path);
            for (int i = 0; i < segments.size(); i++) {
                String onesrc = segments.get(i);
                TMXEntry tex = internalGetSegmentEntry(id, i, onesrc, path);
				if (tex != null)
					if (res.changer == null) { 
						res.changer = tex.changer == null ? tex.creator : tex.changer; 
						res.changeDate = tex.changeDate == 0 ? tex.creationDate : tex.changeDate; 
						res.creationDate = tex.creationDate; res.creator = tex.creator;
					}
					else if (! res.changer.equals(tex.changer)) {
						res.changer = res.creator = null;
						res.changeDate = res.creationDate = 0L;
						return res;
					}
					else {
						if (res.changeDate < tex.changeDate) res.changeDate = tex.changeDate; // keep most recently changed
					}
            }
            if ((res.translation == null) && (res.note == null)) return null; // there is no even one translated segment
			return res;
        }
        else return internalGetSegmentEntry(id, 0, source, path);
	}
    
    /**
     * {@inheritDoc}
     */
    @Override
    public String getTranslation(final String id, final String origSource, final String path) {
        ParseEntry.ParseEntryResult spr = new ParseEntry.ParseEntryResult();

        // fix for bug 3487497; 
        // Fetch removed tags if the options 
        // has been enabled.
        String tags = null;
        if (m_config.isRemoveTags()) {
            tags = TagUtil.buildTagListForRemove(origSource);
        }
        
        boolean removeSpaces = Core.getFilterMaster().getConfig().isRemoveSpacesNonseg();
        final String source = StringUtil.normalizeUnicode(ParseEntry.stripSomeChars(
                origSource, spr, m_config.isRemoveTags(), removeSpaces));
        
        StringBuilder res = new StringBuilder();

        if (m_config.isSentenceSegmentingEnabled()) {
            boolean translated = false;
            List<StringBuilder> spaces = new ArrayList<StringBuilder>();
            List<Rule> brules = new ArrayList<Rule>();
            Language sourceLang = m_config.getSourceLanguage();
            Language targetLang = m_config.getTargetLanguage();
            List<String> segments = Core.getSegmenter().segment(sourceLang, source, spaces, brules);
            for (int i = 0; i < segments.size(); i++) {
                String onesrc = segments.get(i);
                TMXEntry tex = internalGetSegmentEntry(id, i, onesrc, path);
                String tr = tex == null ? onesrc : tex.translation;
                if (tex != null) translated = true;
                segments.set(i, tr);
            }
            if (!translated) {
                return null; // there is no even one translated segment
            }
            res.append(Core.getSegmenter().glue(sourceLang, targetLang, segments, spaces, brules));
        } else {
            TMXEntry tex = internalGetSegmentEntry(id, 0, source, path);
            if (tex == null) {
                return null; // non-translated
            }
            res.append(tex.translation);
        }
        
        // replacing all occurrences of LF (\n) by either single CR (\r) or CRLF
        // (\r\n)
        // this is a reversal of the process at the beginning of this method
        // fix for bug 1462566
        String r = res.toString();
        
        //- Word: anything placed before the leading tag is omitted in translated document
        // https://sourceforge.net/p/omegat/bugs/634/
        // This is a Word document, Remove Tags (from Project Properties) is not checked and Remove leading and 
        // trailing tags (from File Filters) is not checked
        String fileName = getCurrentFile().toLowerCase();
        if ((fileName.endsWith(".docx") || fileName.endsWith(".docm")) &&
            !m_config.isRemoveTags() && !Core.getFilterMaster().getConfig().isRemoveTags()) {
            // Locate the location of the first tag
            String firstTag = TagUtil.getFirstTag(r);
            if (firstTag != null) { 
                int locFirstTag = r.indexOf(firstTag);
                // Is there text before that first tag?
                if (locFirstTag > 0) {
                    // Was the first tag between two words without any spaces around?
                    String addSpace = "";
                    if (!Character.isWhitespace(r.codePointBefore(locFirstTag)) && 
                        !Character.isWhitespace(r.codePointAt(locFirstTag + firstTag.length())) &&
                        Core.getProject().getProjectProperties().getTargetLanguage().isSpaceDelimited()) {
                        addSpace = " ";
                    }
                    // Move that first tag before the text, adding a space if needed.
                    r = firstTag + r.substring(0, locFirstTag) + addSpace + r.substring(locFirstTag + firstTag.length());
                }
            }                           
        }

        
        // fix for bug 3487497; 
        // explicitly add the removed tags at 
        // the end of the translated string.
        if (m_config.isRemoveTags()) {
            r += tags;
        }
        
        if (spr.crlf) {
            r = r.replace("\n", "\r\n");
        } else if (spr.cr) {
            r = r.replace("\n", "\r");
        }

        if (spr.spacesAtBegin > 0) {
            r = origSource.substring(0, spr.spacesAtBegin) + r;
        }

        if (spr.spacesAtEnd > 0) {
            r = r + origSource.substring(origSource.length() - spr.spacesAtEnd);
        }
        
        return r;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getTranslation(final String id, final String origSource) {
        return getTranslation(id, origSource, null);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void linkPrevNextSegments() {
        for (int i = 0; i < translateQueue.size(); i++) {
            TranslateEntryQueueItem item = translateQueue.get(i);
            try {
                item.prevSegment = translateQueue.get(i - 1).segmentSource;
            } catch (IndexOutOfBoundsException ex) {
                // first entry - previous will be empty
                item.prevSegment = "";
            }
            try {
                item.nextSegment = translateQueue.get(i + 1).segmentSource;
            } catch (IndexOutOfBoundsException ex) {
                // last entry - next will be empty
                item.nextSegment = "";
            }
        }
    }
    
    /**
     * This method calls real method for empty prev/next on the first pass, then with real prev/next on the
     * second pass.
     * 
     * @param id
     * @param segmentIndex
     * @param segmentSource
     * @return
     */
    private TMXEntry internalGetSegmentEntry(String id, int segmentIndex, String segmentSource, String path) {
        if (segmentSource.trim().isEmpty()) {
            // empty segment
            return null;
        }

        TranslateEntryQueueItem item;
        switch (pass) {
        case 1:
            item = new TranslateEntryQueueItem();
            item.id = id;
            item.segmentIndex = segmentIndex;
            item.segmentSource = segmentSource;
            translateQueue.add(item);
            currentlyProcessedSegment++;
            return getSegmentEntry(id, segmentIndex, segmentSource, null, null, path);
        case 2:
            item = translateQueue.get(currentlyProcessedSegment);
            if (!StringUtil.equalsWithNulls(id, item.id) || segmentIndex != item.segmentIndex
                    || !StringUtil.equalsWithNulls(segmentSource, item.segmentSource)) {
                throw new RuntimeException("Invalid two-pass processing: not equals fields");
            }
            currentlyProcessedSegment++;
            return getSegmentEntry(id, segmentIndex, segmentSource, item.prevSegment, item.nextSegment, path);
        default:
            throw new RuntimeException("Invalid pass number: " + pass);
        }
    }
    
    protected abstract TMXEntry getSegmentEntry(String id, int segmentIndex, String segmentSource,
            String prevSegment, String nextSegment, String path);
    
    /**
     * Storage for cached segments.
     */
    protected static class TranslateEntryQueueItem {
        String id;
        int segmentIndex;
        String segmentSource;
        String prevSegment;
        String nextSegment;
    }
}
