/**************************************************************************
 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
               2013 Aaron Madlon-Kay
               2014-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.util;

import java.io.File;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.omegat.core.Core;
import org.omegat.core.data.ProjectOptions;

/**
 * This class is used to transform a string by expansion of variables it
 * contains. It can expand:
 * <ul>
 * <li>Entries in Bundle.properties: see {@link #expandBundleEntries(String)}
 * for syntax
 * <li>Variables: see {@link #expandVariables(Object)} for syntax
 * </ul>
 * Here we define variables which depends only on the project. This class should
 * be overriden to define more specific substitutions.
 * 
 * @author Thomas CORDONNIER
 * @author Aaron Madlon-Kay
 */
public abstract class VarExpansion<Param> {

    // ------------------------------ definitions -------------------

    public static final String VAR_SOURCE_TEXT = "${sourceText}";
    public static final String VAR_TARGET_TEXT = "${targetText}";
    public static final String VAR_PROJECT_SOURCE_LANG = "${projectSourceLang}";    
    public static final String VAR_PROJECT_SOURCE_LANG_CODE = "${projectSourceLangCode}";
    public static final String VAR_PROJECT_TARGET_LANG = "${projectTargetLang}";    
    public static final String VAR_PROJECT_TARGET_LANG_CODE = "${projectTargetLangCode}";
    public static final String VAR_FILE_NAME = "${fileName}";
    public static final String VAR_FILE_NAME_ONLY = "${fileNameOnly}";
    public static final String VAR_FILE_EXTENSION = "${fileExtension}";	
    public static final String VAR_FILE_PATH = "${filePath}";
    public static final String VAR_FILE_SHORT_PATH = "${fileShortPath}";
    
    public static final Pattern patternBundleEntry = Pattern.compile("#\\{([\\w\\.]+?)\\}(\\[.+?\\])*");

    protected String template;

    public VarExpansion(String template) {
        this (Core.getProject().isProjectLoaded() ? Core.getProject().getProjectProperties() : null, template);
    }
        
    public VarExpansion(ProjectOptions prop, String template) {
        // Optimisation : pre-expand all what is not one-match-dependant
        
        // Bundle entries
        template = expandBundleEntries(template);

        // Variables
        if (prop != null) {
            template = template.replace(VAR_PROJECT_TARGET_LANG, prop.getTargetLanguage().getLanguage());
            template = template.replace(VAR_PROJECT_TARGET_LANG_CODE, prop.getTargetLanguage().getLanguageCode());
            template = template.replace(VAR_PROJECT_SOURCE_LANG, prop.getSourceLanguage().getLanguage());
            template = template.replace(VAR_PROJECT_SOURCE_LANG_CODE, prop.getSourceLanguage().getLanguageCode());
        }
        
        this.template = template;        
    }

   // ------------------------------ functions -------------------
    
    /** 
     * Replace bundle entries with their translation <br>
     * Format : #{BUNDLE_ENTRY_NAME}[param0][param1][param2]... 
     * (parameters can contain expanded variables but not with [ or ])
     * @param localTemplate  Initial template. 
     * @return Expanded template
     */
    protected static String expandBundleEntries(String localTemplate) {
        Matcher matcher;
        while ((matcher = patternBundleEntry.matcher(localTemplate)).find()) {
            String original = matcher.group();
            String translation = OStrings.getString(matcher.group(1));
            if (!StringUtil.isEmpty(matcher.group(2))) {
                String vars = matcher.group(2); 
                List<String> values = new ArrayList<String>();
                matcher = Pattern.compile("\\[(.+?)\\]").matcher(vars);
                while (matcher.find()) values.add(matcher.group(1));
                translation = MessageFormat.format(translation, values.toArray());
            }
            localTemplate = localTemplate.replace (original, translation);
        }
        return localTemplate;
    }
    
    /**
     * Expands all variables relating to file name : <ul>
     * <li>${filePath} = full file path
     * <li>${fileShortPath} = file path relative to given root
     * <li>${fileName} = full file name (w/o path but with extension)
     * <li>${fileNameOnly} = file name without extension
     * <li>${fileNameOnly-1}, ${fileNameOnly-2}, ... = filename with 1, 2, ... extensions
     * <li>${fileExtension} = all extensions after '.'
	 * <li>${fileExtension-1}, ${fileExtension-2}, ... = ${fileExtension} after removing 1, 2, ... extensions
     * </ul>
     * @param localTemplate initial template. If null, use instance's template but does not modify it 
     * @param filePath  path used by variable ${fileShortPath}
     * @return	Copy of the template with mentioned variables expanded. Other variables remain unchanged
     */
    public String expandFileName(String localTemplate, String filePath, String baseDir) {
        if (localTemplate == null) localTemplate = this.template; // copy
        localTemplate = localTemplate.replace(VAR_FILE_PATH, filePath);
        if (filePath.startsWith(baseDir)) 
            filePath = filePath.substring(baseDir.length());
        localTemplate = localTemplate.replace(VAR_FILE_SHORT_PATH, filePath); // path without TMRoot
        if (filePath.contains(File.separator)) 
            filePath = filePath.substring(filePath.lastIndexOf(File.separator) + 1); 
        localTemplate = localTemplate.replace(VAR_FILE_NAME, filePath);
        if (filePath.contains(".")) {
            String[] splitName = filePath.split("\\.");
            StringBuilder nameOnlyBuf = new StringBuilder(splitName[0]);
            StringBuilder extensionBuf = new StringBuilder(splitName[splitName.length - 1]);
            localTemplate = localTemplate.replace(VAR_FILE_NAME_ONLY, nameOnlyBuf.toString());
            localTemplate = localTemplate.replace(VAR_FILE_EXTENSION, extensionBuf.toString());
            for (int i = 0; i < splitName.length; i++) {
                localTemplate = localTemplate.replaceAll ("\\$\\{fileNameOnly-" + i + "\\}", nameOnlyBuf.toString());
                localTemplate = localTemplate.replaceAll ("\\$\\{fileExtension-" + i + "\\}", extensionBuf.toString());
                if (i + 1 < splitName.length) {
                    nameOnlyBuf.append (".").append(splitName[i + 1]);
                    extensionBuf.insert(0, splitName[splitName.length - i - 2] + '.');
                }
            }
        }
		// prevent unexpanded fileName variables in case the file has less extensions than expected
        localTemplate = localTemplate.replaceAll("\\$\\{fileNameOnly(-\\d+)?\\}", filePath);
        localTemplate = localTemplate.replaceAll("\\$\\{fileExtension(-\\d+)?\\}", "");
        return localTemplate;        
    }
    
    public static final Pattern patternSingleProperty = Pattern.compile("@\\{(.+?)\\}");
    public static final Pattern patternPropertyGroup = Pattern.compile("@\\[(.+?)\\]\\[(.+?)\\]\\[(.+?)\\]"); 
     
    /**
     * Replace property calls by the corresponding value <br>
     * Format : @{PropertyName} 
     *      in this case, retreive only the property value, name is elsewhere. <br>
     * Format : @[Property name with *][separator 1][separator2] 
     *      in this case, return all properties matching the 1st pattern,
     *      as key=value pairs where = is replaced by separator1 and use separator2 between entries.<br>
     * Expression \n for new line is accepted in separators. 
     * @param localTemplate  Initial template
     * @param props Map of properties
     * @return Expanded template
     */
    public String expandProperties (String localTemplate, List<TMXProp> props) {
        Matcher matcher;
        while ((matcher = patternSingleProperty.matcher(localTemplate)).find()) {
            String value = getPropValue(props, matcher.group(1));
            localTemplate = localTemplate.replace (matcher.group(), value == null ? "" : value);
        }
        while ((matcher = patternPropertyGroup.matcher(localTemplate)).find()) {
            String patternStr = matcher.group(1), separator1 = matcher.group(2), separator2 = matcher.group(3);
            separator1 = separator1.replace("\\n","\n"); 
            separator2 = separator2.replace("\\n","\n");
            Pattern pattern = Pattern.compile(patternStr.replace("*","(.*)").replace("?","(.)"));
            StringBuilder res = new StringBuilder();
            for (TMXProp me: props) {
                if (pattern.matcher(me.getType().toString()).matches())
                    res.append(me.getType()).append(separator1).append(me.getValue()).append(separator2);
            }
            if (res.toString().endsWith(separator2))
                res.replace(res.toString().lastIndexOf(separator2), res.length(), "");
            localTemplate = localTemplate.replace(matcher.group(), res.toString());
        }        
        return localTemplate;        
    }

    protected String getPropValue(List<TMXProp> props, String type) {
        if (props == null) return null;
        for (TMXProp me : props) {
            if (type.equals(me.getType())) {
                return me.getValue();
            }
        }
        return null;
    }

    public abstract String expandVariables (Param param);
    
}
