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

 Copyright (C) 2018-2021 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.segmentation;

import java.io.InputStream;
import java.io.File;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import java.net.URL;

import org.omegat.util.Language;
import org.omegat.util.Log;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;

/**
 * Loader for Culter Segmentation Convertible format (CSC: http://www.silvestris-lab.org/node/66)
 * Actually can only load XML form, and cannot save
 * 
 * @author Thomas Cordonnier
 */
public class CSC implements ISegmentationData {

    public static final String CSC_URI = "http://culter.silvestris-lab.org/compatible";

    public CSC() {}
    
    /** Converts SRX to CSC. Does not build any template **/
    public CSC (SRX ori) {
        this.cascade = ori.isCascade();
        for (MapRule mr: this.mappingRules = ori.getMappingRules())
            if (this.namedLists.get(mr.getLanguageCode()) != null) continue; // do not redefine twice the same
            else {
                List<ApplyRuleTemplate> newList = new ArrayList<>(mr.getRules().size());
                this.namedLists.put(mr.getLanguageCode(), newList);
                for (Rule rule0: mr.getRules()) newList.add (new ApplyRuleTemplate(rule0));
            }
    }
    
    // ------------------- Reader, using STAX --------------

    private static final XMLInputFactory staxInputFactory = XMLInputFactory.newInstance();

    /**
     * Loads a file in CSCX format.
     * For the moment, supports only rules-mapping and language rules
     **/
    protected static CSC loadCscxFile (URL rulesUrl) throws Exception {
        CSC data = new CSC(); CSC base = null; 
        try (InputStream io = rulesUrl.openStream()) { 
            XMLStreamReader reader = staxInputFactory.createXMLStreamReader(io);
            while (reader.hasNext()) {
                int next = reader.next();
                if (next == XMLStreamReader.START_ELEMENT)
                    switch (reader.getName().getLocalPart()) {
                        case "seg-rules": {
                            String extendsAttr = reader.getAttributeValue(null,"extends");
                            if (extendsAttr != null) {
                                if (extendsAttr.toUpperCase().equals("#DEFAULT-CONFIG")) base = new CSC (ISegmentationData.getDefault());
                                else if (extendsAttr.toUpperCase().equals("#GLOBAL-CONFIG")) {
                                    ISegmentationData baseData = ISegmentationData.loadFromDir(new File(org.omegat.util.StaticUtils.getConfigDir()), true);
                                    if (baseData instanceof SRX) base = new CSC((SRX) baseData);
                                    if (baseData instanceof CSC) base = (CSC) baseData;
                                }
                                else {
                                    URL baseUrl = new URL(rulesUrl, extendsAttr);
                                    if (extendsAttr.toLowerCase().endsWith(".srx")) base = new CSC(SRX.loadSrxFile(baseUrl));
                                    //if (val.toLowerCase().endsWith(".conf")) base = new CSC(SRX.loadConfFile(baseUrl));
                                    if (extendsAttr.toLowerCase().endsWith(".cscx")) base = loadCscxFile(baseUrl);
                                }
                                data.templatesMap = (HashMap) base.templatesMap.clone(); data.namedLists = (HashMap) base.namedLists.clone();
                            }
                            break;
                        }
                        case "rules-mapping": data.readRulesMapping(reader, base); break;
                        case "rule-templates": data.readRuleTemplates(reader); break;
                        case "languagerules": data.readLanguageRules(reader, rulesUrl); break;
                        case "formathandle":
                             switch (reader.getAttributeValue(null, "type")) {
                                 case "start": data.includeStartingTags = "yes".equals(reader.getAttributeValue(null, "include")); break;
                                 case "end": data.includeEndingTags = "yes".equals(reader.getAttributeValue(null, "include")); break;
                                 case "isolated": data.includeIsolatedTags = "yes".equals(reader.getAttributeValue(null, "include")); break;
                             }
                             break;
                    }
                // Special case: when you extend an existing CSCX file, mapping rules are optional
                if ((next == XMLStreamReader.END_ELEMENT) && (reader.getName().getLocalPart().equals("seg-rules")))
                    if ((base != null) && (data.mappingRules.size() == 0))
                        data.mappingRules.addAll (base.mappingRules);
            }
            reader.close();
        }
        return data;
    }
        
    // Reads until </rules-mapping> and loads mappings in current CSC
    private void readRulesMapping(XMLStreamReader reader, CSC base) throws Exception {
        this.setCascade(! "false".equals(reader.getAttributeValue(null, "cascade")));
        String extensionMode = reader.getAttributeValue(null, "extension-mode"); // may be null
        if ("after".equals(extensionMode)) mappingRules.addAll (base.mappingRules); // first add base rules, then add new ones
        while (reader.hasNext()) {
            int next = reader.next();
            if ((next == XMLStreamReader.END_ELEMENT) && reader.getName().getLocalPart().equals("rules-mapping")) return;
            if ((next == XMLStreamReader.START_ELEMENT) && reader.getName().getLocalPart().equals("languagemap"))
                try {
                    mappingRules.add (new MapRule(reader.getAttributeValue(null, "languagerulename"), reader.getAttributeValue(null, "languagepattern"), null));
                } catch (Exception e) {
                    Log.log("Warning: wrong map rule in CSC file: " + reader.getName());
                }
        }
        if ("before".equals(extensionMode)) mappingRules.addAll (base.mappingRules); // add new rules after new ones
    }
    
    // Reads until </rule-templates> and loads templates in current CSC
    private void readRuleTemplates(XMLStreamReader reader) throws Exception {
        Rule currentRule = null;
        while (reader.hasNext()) {
            int next = reader.next();
            if ((next == XMLStreamReader.END_ELEMENT) && reader.getName().getLocalPart().equals("rule-templates")) return;
            if (next == XMLStreamReader.START_ELEMENT)
                try {
                    if (reader.getName().getLocalPart().equals("rule-template"))
                        templatesMap.put (reader.getAttributeValue(null, "name"), currentRule = new Rule());					
                    if (reader.getName().getLocalPart().equals("rule")) {
                        currentRule.setBreakRule("yes".equals(reader.getAttributeValue(null, "break")));
                        readRule(reader, currentRule);
                    }
                    if (reader.getName().getLocalPart().equals("break-rule")) {
                        currentRule.setBreakRule(true);
                        readRule(reader, currentRule);
                    }
                    if (reader.getName().getLocalPart().equals("exception-rule")) {
                        currentRule.setBreakRule(false);
                        readRule(reader, currentRule);
                    }
                } catch (ClassCastException cce) {
                    // Nothing : except </rule-templates>, end elements are not managed by this method
                } catch (NullPointerException npe) {
                    Log.log("Error [CSC]: something wrong (probably missing attribute) with rule template : " + reader.getName());
                }
        }
    }

    public static final Pattern PATTERN_CSC_VARIABLE = Pattern.compile("%[\\{\\<](\\w+)[\\>\\}]");
    
    private void readRule(XMLStreamReader reader, Rule currentRule) throws Exception {
        StringBuffer text = new StringBuffer();
        while (reader.hasNext()) {
            int next = reader.next();
            if ((next == XMLStreamReader.END_ELEMENT) && reader.getName().getLocalPart().endsWith("rule")) return;
            
            if ((next == XMLStreamReader.START_ELEMENT) && reader.getName().getLocalPart().equals("beforebreak")) text.setLength(0);
            if ((next == XMLStreamReader.START_ELEMENT) && reader.getName().getLocalPart().equals("afterbreak")) text.setLength(0);
            if (next == XMLStreamReader.CHARACTERS) text.append(reader.getText());
            if ((next == XMLStreamReader.END_ELEMENT) && reader.getName().getLocalPart().equals("beforebreak")) {
                currentRule.setBeforebreak(PATTERN_CSC_VARIABLE.matcher(text.toString()).replaceAll("%<$1>")); text.setLength(0);
            }
            if ((next == XMLStreamReader.END_ELEMENT) && reader.getName().getLocalPart().equals("afterbreak")) {
                currentRule.setAfterbreak(PATTERN_CSC_VARIABLE.matcher(text.toString()).replaceAll("%<$1>")); text.setLength(0);
            }
        }
    }
    
    /** Contains either a rule without params, or a template with association param -> value **/
    private static class ApplyRuleTemplate {
        Rule rule;
        HashMap<String,List<String>> params;
        List<String> paramOrder;
        String name;
        
        // Used for non-apply-template
        ApplyRuleTemplate(Rule rule) { this.rule = rule; this.params = null; this.paramOrder = null; }
        // Used for apply-template
        ApplyRuleTemplate(Rule rule, HashMap<String,List<String>> params, List<String> order) { this.rule = rule; this.params = params; this.paramOrder = order; }		
    }
    private HashMap<String,List<ApplyRuleTemplate>> namedLists = new HashMap<>();
    
    // Reads until </languagerules> and loads rules in current CSC
    private void readLanguageRules(XMLStreamReader reader, URL dataURL) throws Exception {
        List<ApplyRuleTemplate> currentList = null; 
        String extensionMode = reader.getAttributeValue(null, "extension-mode");
        while (reader.hasNext()) {
            int next = reader.next();
            if ((next == XMLStreamReader.END_ELEMENT) && reader.getName().getLocalPart().endsWith("languagerules")) return;
            
            if ((next == XMLStreamReader.START_ELEMENT) && reader.getName().getLocalPart().equals("languagerule")) {
                currentList = new ArrayList<>();
                if (extensionMode == null || extensionMode.equals("overwrite")) // default : overwrite, so ignore what is in base
                    namedLists.put (reader.getAttributeValue(null, "languagerulename"), currentList);
                else if (extensionMode.equals("before")) // add what we just read at the beginning
                    namedLists.get (reader.getAttributeValue(null, "languagerulename")).addAll(0, currentList);
                else if (extensionMode.equals("after")) // add what we just read at the end
                    namedLists.get (reader.getAttributeValue(null, "languagerulename")).addAll(currentList);				
                readSingleLanguageRule (reader, currentList, dataURL);
            }
        }
    }
    
    private void readSingleLanguageRule(XMLStreamReader reader, List<ApplyRuleTemplate> currentList, URL dataURL) throws Exception {
        Rule currentRule = null;
        while (reader.hasNext()) {
            int next = reader.next();
            if ((next == XMLStreamReader.END_ELEMENT) && reader.getName().getLocalPart().endsWith("languagerule")) return;
            
            if ((next == XMLStreamReader.START_ELEMENT) && reader.getName().getLocalPart().equals("rule")) {
                currentRule = new Rule();
                currentRule.setBreakRule("yes".equals(reader.getAttributeValue(null,"break")));
                readRule(reader, currentRule);
                currentList.add (new ApplyRuleTemplate(currentRule));
            }
            if ((next == XMLStreamReader.START_ELEMENT) && reader.getName().getLocalPart().equals("break-rule")) {
                currentRule = new Rule();
                currentRule.setBreakRule(true);
                readRule(reader, currentRule);
                currentList.add (new ApplyRuleTemplate(currentRule));
            }
            if ((next == XMLStreamReader.START_ELEMENT) && reader.getName().getLocalPart().equals("exception-rule")) {
                currentRule = new Rule();
                currentRule.setBreakRule(false);
                readRule(reader, currentRule);
                currentList.add (new ApplyRuleTemplate(currentRule));
            }
            if ((next == XMLStreamReader.START_ELEMENT) && reader.getName().getLocalPart().equals("apply-rule-template")) {
                currentRule = templatesMap.get(reader.getAttributeValue(null,"name"));
                ApplyRuleTemplate withParams = new ApplyRuleTemplate(currentRule, new HashMap<>(), new ArrayList<>());
                withParams.name = reader.getAttributeValue(null,"name");
                readParameters(reader, withParams.params, withParams.paramOrder, dataURL);
                currentList.add (withParams);
            }
        }
    }

    private void readParameters(XMLStreamReader reader, HashMap<String,List<String>> params, List<String> order, URL dataURL) throws Exception {
        String curName = null; List<String> curList = null; StringBuffer buf = new StringBuffer();
        while (reader.hasNext()) {
            int next = reader.next();
            if ((next == XMLStreamReader.END_ELEMENT) && reader.getName().getLocalPart().endsWith("apply-rule-template")) return;
            
            if ((next == XMLStreamReader.START_ELEMENT) && reader.getName().getLocalPart().equals("param")) {
                curName = reader.getAttributeValue(null,"name");
                order.add(curName);
                if (reader.getAttributeValue(null,"val") != null)
                    params.put(curName, java.util.Collections.singletonList(reader.getAttributeValue(null,"val")));
                else
                    params.put(curName, curList = new java.util.ArrayList<>());
            }
            if ((next == XMLStreamReader.START_ELEMENT) && reader.getName().getLocalPart().equals("item")) buf.setLength(0);
            if (next == XMLStreamReader.CHARACTERS) buf.append(reader.getText()); 
            if ((next == XMLStreamReader.END_ELEMENT) && reader.getName().getLocalPart().endsWith("item")) curList.add(buf.toString());
            if ((next == XMLStreamReader.START_ELEMENT) && reader.getName().getLocalPart().equals("item-list-file"))
                try (InputStream is = new URL (dataURL, reader.getAttributeValue(null,"name")).openStream()) {
                    String encoding = "UTF-8"; String attr = reader.getAttributeValue(null,"format"); if (attr != null) { encoding = attr; encoding = encoding.substring(encoding.indexOf(':') + 1); }
                    Pattern remove = null; attr = reader.getAttributeValue(null,"remove"); if (attr != null) remove = Pattern.compile(attr);
                    Pattern comments = null; attr = reader.getAttributeValue(null,"comments"); if (attr != null) comments = Pattern.compile(attr);
                    
                    try (java.io.BufferedReader fileReader = new java.io.BufferedReader(new java.io.InputStreamReader(is, encoding))) {
                        String line = "";
                        while ((line = fileReader.readLine()) != null) {
                            if ((comments != null) && comments.matcher(line).matches()) continue;
                            line = remove.matcher(line).replaceAll("");
                            curList.add (line);
                        }
                    }
                }
        }
    }
    
    // ------------------- ISegmentationData --------------
    
    private boolean cascade = true;
    private List<MapRule> mappingRules = new ArrayList<MapRule>();
    private HashMap<String,Rule> templatesMap = new HashMap<>();
    
    public List<Rule> lookupRulesForLanguage(Language srclang) {
        List<Rule> rules = new ArrayList<Rule>();
        for (int i = 0; i < mappingRules.size(); i++) {
            MapRule maprule = mappingRules.get(i);
            if (maprule.getCompiledPattern().matcher(srclang.getLanguageCode()).matches()) {
                // rules.addAll(maprule.getRules()); -- in CSC, rules are not stored in the maprule
                for (ApplyRuleTemplate tpl : namedLists.get(maprule.getLanguageCode())) {
                    if (tpl.params == null) rules.add (tpl.rule);
                    else {
                        // Translate the template with params to one big rule
                        String before = tpl.rule.getBeforebreak(), after = tpl.rule.getAfterbreak();
                        for (String paramName : tpl.paramOrder) {
                            before = before.replace("%<" + paramName + ">", String.join("|", tpl.params.get(paramName)));
                            after = after.replace("%<" + paramName + ">", String.join("|", tpl.params.get(paramName)));
                        }
                        rules.add (new Rule(tpl.rule.isBreakRule(), before, after));
                    }
                }
                if (! this.cascade) break; // non-cascading means: do not search for other patterns
            }
        }
        return rules;
    }
    
    public SRX toSRX() {
        SRX res = new SRX(); res.setCascade(this.cascade);
        List<MapRule> newRules = new ArrayList<MapRule>(mappingRules.size());
        for (MapRule mapRule0 : mappingRules) {
            List<Rule> srxRules = new ArrayList<>();
            for (ApplyRuleTemplate tpl : namedLists.get(mapRule0.getLanguageCode())) {
                if (tpl.params == null) srxRules.add (tpl.rule);
                else
                    // Translate the template with params to human-readable set of rules
                    CSC.toSrxRules(tpl.rule.getBeforebreak(), tpl.rule.getAfterbreak(), tpl, 0, srxRules);
            }            
            newRules.add (new MapRule (mapRule0.getLanguageCode(), mapRule0.getPattern(), srxRules));
        }
        res.setMappingRules(newRules); return res;
    }
    
    private static void toSrxRules(String before, String after, ApplyRuleTemplate tpl, int paramId, List<Rule> srxRules) {
        if (paramId >= tpl.paramOrder.size()) srxRules.add (new Rule(tpl.rule.isBreakRule(), before, after));
        else {
            String paramName = tpl.paramOrder.get(paramId);
            for (String paramVal : tpl.params.get(paramName))
                CSC.toSrxRules(before.replace("%<" + paramName + ">", paramVal), after.replace("%<" + paramName + ">", paramVal),
                    tpl, paramId + 1, srxRules);
        }
    }

    public boolean isCascade() {
        return this.cascade;
    }
    
    public void setCascade(boolean cascade) {
        this.cascade = cascade;
    }

    // When segmentation with tags is active, decide which tags come before and after
    private boolean includeStartingTags = false, includeEndingTags = true, includeIsolatedTags = false;

    public boolean isIncludeStartingTags() {
        return this.includeStartingTags;
    }

    public void setIncludeStartingTags(boolean includeStartingTags) {
        this.includeStartingTags = includeStartingTags;
    }

    public boolean isIncludeEndingTags() {
        return this.includeEndingTags;
    }

    public void setIncludeEndingTags(boolean includeEndingTags) {
        this.includeEndingTags = includeEndingTags;
    }

    public boolean isIncludeIsolatedTags() {
        return this.includeIsolatedTags;
    }

    public void setIncludeIsolatedTags(boolean includeIsolatedTags) {
        this.includeIsolatedTags = includeIsolatedTags;
    }
    
    public HashMap<String,Rule> getTemplates() {
        return templatesMap;
    }
    
    /**
     * Returns all mapping rules (of class {@link MapRule}) at once:
     * correspondences between languages and their segmentation rules.
     */
    public List<MapRule> getMappingRules() {
        return mappingRules;
    }
    
    public void insertMapRule(int pos, MapRule mr) {
        mappingRules.add(pos, mr); namedLists.put(mr.getLanguageCode(), new java.util.ArrayList<>());
    }
    
    public void insertRule(int pos, MapRule mr, Rule r) {
        mr.getRules().add(pos, r); namedLists.get(mr.getLanguageCode()).add(new ApplyRuleTemplate(r, null, null));
    }
    
    /** Add template apply rule. For the moment only with one variable **/
    public void addTemplateApply(MapRule mr, String templateName, String templateVar) {
        List<ApplyRuleTemplate> tl = namedLists.get(mr.getLanguageCode());
        for (ApplyRuleTemplate art: tl) 
            if (art.name.equals(templateName)) {
                List<String> values = art.params.values().iterator().next();
                try { values.add(templateVar); }
                catch (Exception e) {
                    String key = art.params.keySet().iterator().next();
                    art.params.put(key, values = new ArrayList<>(art.params.get(key)));
                    values.add(templateVar);
                }
            }
        // still here? add at top
        HashMap<String,List<String>> params = new HashMap<>(); List<String> paramOrder = new ArrayList<>(1);
        java.util.regex.Matcher matcher = PATTERN_CSC_VARIABLE.matcher(templatesMap.get(templateName).getBeforebreak());
        while (matcher.find()) { paramOrder.add(matcher.group(1)); params.put(matcher.group(1), new ArrayList<>()); }
        matcher = PATTERN_CSC_VARIABLE.matcher(templatesMap.get(templateName).getAfterbreak());
        while (matcher.find()) { paramOrder.add(matcher.group(1)); params.put(matcher.group(1), new ArrayList<>()); }
        params.values().iterator().next().add(templateVar); // For the moment works only with one variable
        ApplyRuleTemplate art = new ApplyRuleTemplate(templatesMap.get(templateName), params, paramOrder); art.name = templateName; tl.add(art);
    }
    
    private static final javax.xml.stream.XMLOutputFactory staxOutputFactory = javax.xml.stream.XMLOutputFactory.newInstance();
    
    public static void saveTo(CSC csc, File outFile) throws java.io.IOException {
        try (java.io.OutputStreamWriter fos = new java.io.OutputStreamWriter(new java.io.FileOutputStream(outFile), "UTF-8")) {
            javax.xml.stream.XMLStreamWriter writer = staxOutputFactory.createXMLStreamWriter(fos);
            writer.writeStartDocument("UTF-8", "1.0");
            writer.writeStartElement("seg-rules"); writer.writeDefaultNamespace(CSC_URI);
            // Rules mapping
            writer.writeCharacters("\n    "); writer.writeStartElement("rules-mapping");
            writer.writeAttribute("cascade", Boolean.toString(csc.cascade)); 
            for (MapRule mr: csc.mappingRules) {
                writer.writeCharacters("\n        "); writer.writeEmptyElement("languagemap");
                writer.writeAttribute("languagerulename", mr.getLanguageCode());
                writer.writeAttribute("languagepattern", mr.getPattern());
            }
            writer.writeCharacters("\n    "); writer.writeEndElement(/*"rules-mapping"*/); 
            // Rule templates
            writer.writeCharacters("\n    "); writer.writeStartElement("rule-templates"); 
            for (HashMap.Entry<String,Rule> tpl: csc.templatesMap.entrySet()) {
                writer.writeCharacters("\n        "); writer.writeStartElement("rule-template");
                writer.writeAttribute("name", tpl.getKey());
                writer.writeCharacters("\n            "); writer.writeStartElement("rewrite");
                writer.writeCharacters("\n                "); writer.writeStartElement("rule"); writer.writeAttribute("break", tpl.getValue().isBreakRule() ? "yes" : "no");
                writer.writeCharacters("\n                    "); 
                writer.writeStartElement("beforebreak"); writer.writeCharacters(tpl.getValue().getBeforebreak().replaceAll("<","{").replaceAll(">","}")); writer.writeEndElement(); 
                writer.writeCharacters("\n                    "); 
                writer.writeStartElement("afterbreak"); writer.writeCharacters(tpl.getValue().getAfterbreak().replaceAll("<","{").replaceAll(">","}")); writer.writeEndElement(); 
                writer.writeCharacters("\n                "); writer.writeEndElement(/*"rule"*/);                 
                writer.writeCharacters("\n            "); writer.writeEndElement(/*rewrite*/);
                writer.writeCharacters("\n        "); writer.writeEndElement(/*rule-template*/);
            }
            writer.writeCharacters("\n    "); writer.writeEndElement(/*rule-templates*/); 
            // Language rules
            writer.writeCharacters("\n    "); writer.writeStartElement("languagerules");
            for (HashMap.Entry entry: csc.namedLists.entrySet()) {
                writer.writeCharacters("\n        "); writer.writeStartElement("languagerule");
                writer.writeAttribute("languagerulename", entry.getKey().toString());
                for (ApplyRuleTemplate tpl : ((List<ApplyRuleTemplate>) entry.getValue()))
                    if (tpl.params == null) {
                        writer.writeCharacters("\n            "); writer.writeStartElement("rule"); writer.writeAttribute("break", tpl.rule.isBreakRule() ? "yes" : "no");
                        writer.writeCharacters("\n                "); writer.writeStartElement("beforebreak"); writer.writeCharacters(tpl.rule.getBeforebreak()); writer.writeEndElement(); 
                        writer.writeCharacters("\n                "); writer.writeStartElement("afterbreak"); writer.writeCharacters(tpl.rule.getAfterbreak()); writer.writeEndElement(); 
                        writer.writeCharacters("\n            "); writer.writeEndElement(/*"rule"*/);
                    } else {
                        writer.writeCharacters("\n            "); writer.writeStartElement("apply-rule-template"); writer.writeAttribute("name", tpl.name);
                        for (String param: tpl.paramOrder) {
                            writer.writeCharacters("\n                "); writer.writeStartElement("param"); writer.writeAttribute("name", param);
                            if (tpl.params.get(param).size() < 2) {
                                writer.writeAttribute("mode", "value");
                                writer.writeAttribute("value", tpl.params.get(param).size() == 0 ? "" : tpl.params.get(param).get(0));
                            } else {
                                writer.writeAttribute("mode", "loop");
                                for (String val: tpl.params.get(param)) { writer.writeCharacters("\n                    "); writer.writeStartElement("item"); writer.writeCharacters(val); writer.writeEndElement(/*"item*/); }
                            }
                            writer.writeCharacters("\n                "); writer.writeEndElement(/*param*/);
                        }
                        writer.writeCharacters("\n            "); writer.writeEndElement(/*apply-rule-template*/);                
                    }
                writer.writeCharacters("\n        "); writer.writeEndElement(/*languagerule*/);
            }
            writer.writeCharacters("\n    "); writer.writeEndElement(/*languagerules*/); 
            writer.writeCharacters("\n"); writer.writeEndElement(/*seg-rules*/); 
        } catch (Exception e) {
            throw new java.io.IOException(e);
        }
    }	
}
