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

 Copyright (C) 2000-2006 Keith Godfrey and Maxym Mykhalchuk
               2009-2013 Alex Buloichik
               2015 Aaron Madlon-Kay
               2017-2019 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.Collections;
import java.util.List;

import java.util.function.Function;

import org.omegat.core.Core;
import org.omegat.core.segmentation.Rule;
import org.omegat.util.Language;


/**
 * Source text entry represents an individual segment for translation pulled
 * directly from the input files. There can be many SourceTextEntries having
 * identical source language strings
 * 
 * @author Keith Godfrey
 * @author Alex Buloichik (alex73mail@gmail.com)
 * @author Aaron Madlon-Kay
 * @author Thomas Cordonnier
 */
public class SourceTextEntry {

    private static final ProtectedPart[] EMPTY_PROTECTED_PARTS = new ProtectedPart[0];

    /** Storage for full entry's identifiers, including source text. */
    private final EntryKey key;

	/**
	 * Contains data which are in the source file but which are neither part of the key, nor the source string.<br/>
	 * Only bilingual file formats contain such information
	 *
	 * Note: static because we may create such entries long time before we insert them inside a SourceTextEntry
	 * @author Thomas Cordonnier
	 **/
	public static class SourceTranslationInfo {
		/** Comment in source file. */
		public final String comment;

		/** Translation from source files. */
		private String sourceTranslation;

		/** Translation from source files is fuzzy. */
		public final boolean sourceTranslationFuzzy;
		
		public SourceTranslationInfo(String tra, String comment, boolean fuzzy) {
			this.sourceTranslation = tra; this.comment = comment; this.sourceTranslationFuzzy = fuzzy;
		}
		
		public boolean isTranslated() {
			return sourceTranslation != null && sourceTranslation.length() > 0;
		}

		// Only for package
		void setUntranslated() {
			sourceTranslation = null; //sourceTranslationFuzzy = false;
		}
		
		public String getTranslation() {
			return sourceTranslation;
		}
		
		// Only for package
		void applyToTranslation(Function<String,String> fun) {
			sourceTranslation = fun.apply(sourceTranslation);
		}
		
		public PrepareTMXEntry asPrepareEntry(String source) {
			PrepareTMXEntry entry = new PrepareTMXEntry();
			entry.source = source; entry.translation = this.sourceTranslation; entry.note = this.comment;
			return entry;
		}

		public List<SourceTranslationInfo> cut(Language traLang) {
			List<String> traCut = Core.getSegmenter().segment(traLang, sourceTranslation, new ArrayList<StringBuilder>(), new ArrayList<Rule>());
			if ((traCut == null) || (traCut.size() == 1)) return java.util.Collections.singletonList(this);
			List<SourceTranslationInfo> res = new ArrayList<> (traCut.size());
			for (int i = 0; i < res.size(); i++) res.add(new SourceTranslationInfo(traCut.get(i), comment, sourceTranslationFuzzy));
			return res;
		}
	}
	
	/**
	 * Used by formats which store a complete entry, with name of author and date
	 * @author Thomas Cordonnier
	 **/
	public static class SourceTranslationEntry extends SourceTranslationInfo {
		public final String creator;
		public final long creationDate;
		public final String changer;
		public final long changeDate;
		
		public SourceTranslationEntry(String tra, String comment, boolean fuzzy, String creator, long creationDate) {
			super(tra,comment, fuzzy);
			this.creator = creator; this.creationDate = creationDate;
			this.changer = null; this.changeDate = 0;
		}		
		
		public SourceTranslationEntry(String tra, String comment, boolean fuzzy, String creator, long creationDate, String changer, long changeDate) {
			super(tra,comment, fuzzy);
			this.creator = creator; this.creationDate = creationDate;
			this.changer = changer; this.changeDate = changeDate;
		}

		@Override
		public PrepareTMXEntry asPrepareEntry(String source) {
			PrepareTMXEntry entry = super.asPrepareEntry(source);
			entry.changer = this.changer; entry.changeDate = this.changeDate;
			entry.creator = this.creator; entry.creationDate = this.creationDate;
			return entry;
		}
		
		@Override
		public List<SourceTranslationInfo> cut(Language traLang) {
			List<String> traCut = Core.getSegmenter().segment(traLang, getTranslation(), new ArrayList<StringBuilder>(), new ArrayList<Rule>());
			if ((traCut == null) || (traCut.size() == 1)) return java.util.Collections.singletonList(this);
			List<SourceTranslationInfo> res = new ArrayList<> (traCut.size());
			for (int i = 0; i < res.size(); i++) res.add(new SourceTranslationEntry(traCut.get(i), comment, sourceTranslationFuzzy, creator, creationDate, changer, changeDate));
			return res;
		}
	}
	
	/** Contains translation which has been extracted from source. Most often null, except for bilingual formats **/
	private SourceTranslationInfo sourceTranslation;

    public enum DUPLICATE {
        /** There is no entries with the same source. */
        NONE,
        /** There is entries with the same source, and this is first entry. */
        FIRST,
        /** There is entries with the same source, and this is not first entry. */
        NEXT
    };

    /** 
     * A list of duplicates of this STE. Will be non-null for the FIRST duplicate,
     * null for NONE and NEXT STEs. See {@link #getDuplicate()} for full logic.
     */
    List<SourceTextEntry> duplicates;
    
    /**
     * The first duplicate of this STE. Will be null for NONE and FIRST STEs,
     * non-null for NEXT STEs. See {@link #getDuplicate()} for full logic.
     */
    SourceTextEntry firstInstance;

    /** Holds the number of this entry in a project. */
    private final int m_entryNum;

    /**
     * Protected parts(shortcuts) and details of full content (for tooltips).
     * Read-only info, cat be accessible from any threads. It can't be null.
     */
    private final ProtectedPart[] protectedParts;
	
	private boolean isParagraphStart = true;

    /**
     * Creates a new source text entry.
     * 
     * @param key
     *            entry key
     * @param entryNum
     *            the number of this entry in a project
     * @param comment
     *            entry comment
     * @param sourceTranslation
     *            translation from source file
     * @param shortcuts
     *            tags shortcuts
     */
    public SourceTextEntry(EntryKey key, int entryNum, SourceTranslationInfo traInfo, List<ProtectedPart> protectedParts, boolean isParagraphStart) {
        this.key = key;
        m_entryNum = entryNum;
        this.sourceTranslation = traInfo;
		this.isParagraphStart = isParagraphStart;
        if (protectedParts.isEmpty()) {
            this.protectedParts = EMPTY_PROTECTED_PARTS;
        } else {
            // remove empty protected parts
            for (int i = 0; i < protectedParts.size(); i++) {
                if (protectedParts.get(i).getTextInSourceSegment().isEmpty()) {
                    protectedParts.remove(i);
                    i--;
                }
            }
            this.protectedParts = protectedParts.toArray(new ProtectedPart[protectedParts.size()]);
        }
        this.duplicates = null;
        this.firstInstance = null;
    }

    public EntryKey getKey() {
        return key;
    }

    /**
     * Returns the source text (shortcut for
     * <code>getStrEntry().getSrcText()</code>).
     */
    public String getSrcText() {
        return key.sourceText;
    }

    /**
     * Returns comment of entry if exist in source document.
     */
    public String getComment() {
        return sourceTranslation == null ? null : sourceTranslation.comment;
    }

    /** Returns the number of this entry in a project. */
    public int entryNum() {
        return m_entryNum;
    }

    /** If entry with the same source already exist in project. */
    public DUPLICATE getDuplicate() {
        if (firstInstance != null) {
            return DUPLICATE.NEXT;
        }
        return duplicates == null ? DUPLICATE.NONE : DUPLICATE.FIRST;
    }

    public int getNumberOfDuplicates() {
        if (firstInstance != null) {
            return firstInstance.getNumberOfDuplicates();
        }
        return duplicates == null ? 0 : duplicates.size();
    }
    
    public List<SourceTextEntry> getDuplicates() {
        if (firstInstance != null) {
            List<SourceTextEntry> result = new ArrayList<SourceTextEntry>(firstInstance.getDuplicates());
            result.remove(this);
            result.add(0, firstInstance);
            return Collections.unmodifiableList(result);
        }
        if (duplicates == null) {
            return Collections.emptyList();
        } else {
            return Collections.unmodifiableList(duplicates);
        }
    }
    
    public String getSourceTranslation() {
        return sourceTranslation == null ? null : sourceTranslation.sourceTranslation;
    }

    public SourceTranslationInfo getSourceTranslationInfo() {
        return sourceTranslation;
    }
	
    public boolean isSourceTranslationFuzzy() {
        return sourceTranslation == null ? false : sourceTranslation.sourceTranslationFuzzy;
    }

    public ProtectedPart[] getProtectedParts() {
        return protectedParts;
    }
	
	public boolean isParagraphStart() {
		return isParagraphStart;
	}
}
