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

 Copyright (C) 2012 Thomas Cordonnier, Aaron Madlon-Kay
               2013-2014 Aaron Madlon-Kay, Thomas Cordonnier
               2014 Alex Buloichik, Thomas Cordonnier
               2015 Thomas Cordonnier
               2016 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.gui.matches;

import org.omegat.util.TMXProp;
import org.omegat.util.VarExpansion;
import org.omegat.core.data.ProjectProperties;
import org.omegat.core.data.SourceTextEntry;
import org.omegat.core.data.TMXEntry;
import org.omegat.core.matching.DiffDriver;
import org.omegat.core.matching.DiffDriver.TextRun;
import org.omegat.core.matching.DiffDriver.Render;
import org.omegat.core.matching.NearString;
import org.omegat.util.StringUtil;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Date;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.text.DateFormat;
import java.util.regex.Pattern;
import java.util.regex.Matcher;

import org.omegat.core.Core;
import org.omegat.util.OStrings;

/**
 * This class is used to convert a NearString to a text visible in the MatchesTextArea
 * according to the given template containing variables.
 * 
 * @author Thomas CORDONNIER
 * @author Aaron Madlon-Kay
 */
public class MatchesVarExpansion extends VarExpansion<NearString> {
    
    // ------------------------------ definitions -------------------

    public static final String VAR_NOTE = "${note}";
    
    public static final String VAR_ID = "${id}";
    public static final String VAR_SCORE_BASE = "${score}";
    public static final String VAR_SCORE_NOSTEM = "${noStemScore}";
    public static final String VAR_SCORE_ADJUSTED = "${adjustedScore}";
    /**
     * For backwards compatibility, this variable is an alias for {@link #VAR_CHANGED_ID}.
     * For the actual creation ID, use {@link #VAR_INITIAL_CREATION_ID}.
     */
    @Deprecated
    public static final String VAR_CREATION_ID = "${creationId}";
    /**
     * For backwards compatibility, this variable is an alias for {@link #VAR_CHANGED_DATE}.
     * For the actual creation date, use {@link #VAR_INITIAL_CREATION_DATE}.
     */
    @Deprecated
    public static final String VAR_CREATION_DATE = "${creationDate}";
    public static final String VAR_INITIAL_CREATION_ID = "${initialCreationId}";
    public static final String VAR_INITIAL_CREATION_DATE = "${initialCreationDate}";
    public static final String VAR_CHANGED_ID = "${changedId}";
    public static final String VAR_CHANGED_DATE = "${changedDate}";
    public static final String VAR_FUZZY_FLAG = "${fuzzyFlag}";
    public static final String VAR_DIFF = "${diff}";
    public static final String VAR_DIFF_TRA = "${diffTra}";
    public static final String VAR_DIFF_REVERSED = "${diffReversed}";
    
    
    public static final String[] MATCHES_VARIABLES = {
        VAR_ID, VAR_NOTE,
        VAR_SOURCE_TEXT,
        VAR_DIFF, VAR_DIFF_REVERSED, 
        VAR_TARGET_TEXT, VAR_DIFF_TRA,
        VAR_SCORE_BASE, VAR_SCORE_NOSTEM, VAR_SCORE_ADJUSTED,
        VAR_FILE_NAME_ONLY, VAR_FILE_PATH, VAR_FILE_SHORT_PATH,
        VAR_INITIAL_CREATION_ID, VAR_INITIAL_CREATION_DATE,
        VAR_CHANGED_ID, VAR_CHANGED_DATE, VAR_FUZZY_FLAG,
        "@{file}", "@{id}", "@{prev}", "@{next}", "@{path}", "@{revisor}", "@{revised}",	// For auto-imported project_save : warning,these are properties,not variables
        "@{Att::Req. Serv}", "@{Att::Year}", "@{Att::Doc. Type}",  // DGT-only
        "@{Txt::Doc. No.}", "@{Txt::Stored by}",  "@{Txt::Translator}", "@{Txt::TM Database}" // DGT-only
    };
    
    public static final String DEFAULT_TEMPLATE = VAR_ID + ". " 
            + VAR_FUZZY_FLAG
            + VAR_SOURCE_TEXT + "\n"
            + VAR_TARGET_TEXT + "\n"
            + "<" + VAR_SCORE_BASE + "/" 
            + VAR_SCORE_NOSTEM + "/"
            + VAR_SCORE_ADJUSTED + "% "
            + VAR_FILE_PATH + ">";
        
    private static Replacer sourceTextReplacer = new Replacer() {
        public void replace(Result R, NearString match) {
            R.sourcePos = R.text.indexOf(VAR_SOURCE_TEXT);
            R.text = R.text.replace(VAR_SOURCE_TEXT, match.source);
        }
    };
    
    private static Replacer diffReplacer = new Replacer() {
        public void replace(Result R, NearString match) {
            int diffPos = R.text.indexOf(VAR_DIFF);
            SourceTextEntry ste = Core.getEditor().getCurrentEntry();
            if (diffPos != -1 && ste != null) {
                Render diffRender = DiffDriver.render(match.source, ste.getSrcText(), true, true);
                R.diffInfo.put(diffPos, diffRender.formatting);
                if (diffRender.text != null) R.text = R.text.replace(VAR_DIFF, diffRender.text);
            }
        }
    };
    
    private static Replacer diffTraReplacer = new Replacer() {
        public void replace(Result R, NearString match) {
            int diffPos = R.text.indexOf(VAR_DIFF_TRA);
            try {
                SourceTextEntry ste = Core.getEditor().getCurrentEntry();
                TMXEntry tmxEntry = Core.getProject().getTranslationInfo(ste);
                if (diffPos != -1 && ste != null) {
                    String tra = tmxEntry.translation; if (tra == null) tra = match.translation; if (tra.trim().length() == 0) tra = match.translation;
                    Render diffRender = DiffDriver.render(match.translation, tra, false, true);
                    R.diffInfo.put(diffPos, diffRender.formatting);
                    if (diffRender.text != null) R.text = R.text.replace(VAR_DIFF_TRA, diffRender.text);
                }
            } catch (Exception e) {
                R.text = R.text.replace(VAR_DIFF_TRA, match.translation);
            }
        }
    };
    
    private static Replacer diffReversedReplacer = new Replacer() {
        public void replace(Result R, NearString match) {
            int diffPos = R.text.indexOf(VAR_DIFF_REVERSED);
            SourceTextEntry ste = Core.getEditor().getCurrentEntry();
            if (diffPos != -1 && ste != null) {
                Render diffRender = DiffDriver.render(ste.getSrcText(), match.source, true, true);
                R.diffInfo.put(diffPos, diffRender.formatting);
                R.text = R.text.replace(VAR_DIFF_REVERSED, diffRender.text);
            }
        }
    };
    
    // ------------------------------ subclasses -------------------
    
    /** Class to store formatted text and indications for other treatments **/
    public static class Result {
        public String text = null; 
        public int sourcePos = -1;
        public final Map<Integer, List<TextRun>> diffInfo = new HashMap<Integer, List<TextRun>>();
    }
    
    /** A simple interface for making anonymous functions that perform string replacements. */
    private interface Replacer {
        public void replace(Result R, NearString match);
    }

    // ------------------------------ non-static part -------------------

    /** A sorted map that ensures styled replacements are performed in the order of appearance. */
    private Map<Integer, Replacer> styledComponents = new TreeMap<Integer, Replacer>();

    public MatchesVarExpansion (String template) {
        super(template);
        
    }
    
    @Override
    public String expandVariables (NearString match) {
        String localTemplate = this.template; // do not modify template directly, so that we can reuse for another change
        localTemplate = localTemplate.replace(VAR_INITIAL_CREATION_ID, match.creator == null ? "" : match.creator);
        localTemplate = localTemplate.replace(VAR_NOTE, match.note == null ?  "" : match.note);
        // VAR_CREATION_ID is an alias for VAR_CHANGED_ID, for backwards compatibility.
        for (String s : new String[] {VAR_CHANGED_ID, VAR_CREATION_ID}) {
            localTemplate = localTemplate.replace(s, match.changer == null ? "" : match.changer);
        }
        if (match.creationDate > 0)
            localTemplate = localTemplate.replace(VAR_INITIAL_CREATION_DATE, DateFormat.getInstance().format(new Date (match.creationDate)));
        else
            localTemplate = localTemplate.replace(VAR_INITIAL_CREATION_DATE, "");
        // VAR_CREATION_DATE is an alias for VAR_CHANGED_DATE, for backwards compatibility.
        for (String s : new String[] {VAR_CHANGED_DATE, VAR_CREATION_DATE}) {
            if (match.changedDate > 0)
                localTemplate = localTemplate.replace(s, DateFormat.getInstance().format(new Date (match.changedDate)));
            else
                localTemplate = localTemplate.replace(s, "");
        }
        localTemplate = localTemplate.replace(VAR_SCORE_BASE, Integer.toString(match.score));
        localTemplate = localTemplate.replace(VAR_SCORE_NOSTEM, Integer.toString(match.scoreNoStem));
        localTemplate = localTemplate.replace(VAR_SCORE_ADJUSTED, Integer.toString(match.adjustedScore));
        localTemplate = localTemplate.replace(VAR_TARGET_TEXT, match.translation);
        localTemplate = localTemplate.replace(VAR_FUZZY_FLAG, match.fuzzyMark ? (OStrings.getString("MATCHES_FUZZY_MARK") + " ") : "");
        localTemplate = localTemplate.replace("@{revisor}", // can be either set as attribute or property
            StringUtil.nvl(match.revisor, match.props != null ? getPropValue(match.props, "revisor") : "", ""));
        localTemplate = localTemplate.replace("@{revised}", // boolean value of previous field: either "revised" or ""
            match.revisor != null ? "revised" 
            : match.props != null ? 
                ((getPropValue(match.props, "revisor") != null) ? "revised" : "")
            : "");
        ProjectProperties props = Core.getProject().getProjectProperties();		
        if (props != null) {
            String proj = match.proj; if ((proj == null) || (proj.length() == 0)) proj = OStrings.getString("MATCHES_THIS_PROJECT");
            java.util.Iterator<NearString> I = match.getMergedEntries(); 
            if (I.hasNext()) {
                int more = 0; while (I.hasNext()) { I.next(); more++; }
                proj += " " + org.omegat.util.StringUtil.format(OStrings.getString("MATCHES_MULTI_FILE_HINT"), more);
            }
            localTemplate = expandFileName(localTemplate, proj, props.getTMRoot());
        }
        return localTemplate;
    }
    
    public Result apply(NearString match, int id) {
        Result R = new Result();
        styledComponents.clear();
        
        // Variables
        R.text = this.expandVariables(match);
        R.text = R.text.replace(VAR_ID, Integer.toString(id));

        // Properties (<prop type='xxx'>value</prop>)
        if (match.props != null) {
            R.text = expandProperties(R.text, match.props);
        } else {
            R.text = R.text.replaceAll(patternSingleProperty.pattern(), "");
            R.text = R.text.replaceAll(patternPropertyGroup.pattern(), "");
        }

        styledComponents.put(R.text.indexOf(VAR_SOURCE_TEXT), sourceTextReplacer);
        styledComponents.put(R.text.indexOf(VAR_DIFF), diffReplacer);
        styledComponents.put(R.text.indexOf(VAR_DIFF_REVERSED), diffReversedReplacer);
        styledComponents.put(R.text.indexOf(VAR_DIFF_TRA), diffTraReplacer);

        for (Entry<Integer, Replacer> e : styledComponents.entrySet()) {
            e.getValue().replace(R, match);
        }
        
        return R;
    }
}
