/* OdLib_Io - Copyright (c) 2013-2021 Vincent Calame - Exemole
 * Logiciel libre donné sous triple licence :
 * 1) selon les termes de la CeCILL V2
 * 2) selon les termes de l’EUPL V.1.1
 * 3) selon les termes de la GNU GPLv3
 * Voir le fichier licences.txt
 */


package net.mapeadores.opendocument.io;

import java.io.IOException;
import net.mapeadores.util.money.Amount;
import net.mapeadores.util.primitives.FuzzyDate;
import net.mapeadores.util.text.StringUtils;
import net.mapeadores.util.xml.XMLWriter;


/**
 *
 * @author Vincent Calame
 */
public final class OdXML {

    private OdXML() {
    }

    public static void addDocumentNameSpaceAttributes(XMLWriter xmlWriter) throws IOException {
        xmlWriter.addAttribute("xmlns:office", "urn:oasis:names:tc:opendocument:xmlns:office:1.0");
        xmlWriter.addAttribute("xmlns:style", "urn:oasis:names:tc:opendocument:xmlns:style:1.0");
        xmlWriter.addAttribute("xmlns:text", "urn:oasis:names:tc:opendocument:xmlns:text:1.0");
        xmlWriter.addAttribute("xmlns:table", "urn:oasis:names:tc:opendocument:xmlns:table:1.0");
        xmlWriter.addAttribute("xmlns:draw", "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0");
        xmlWriter.addAttribute("xmlns:fo", "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0");
        xmlWriter.addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
        xmlWriter.addAttribute("xmlns:dc", "http://purl.org/dc/elements/1.1/");
        xmlWriter.addAttribute("xmlns:meta", "urn:oasis:names:tc:opendocument:xmlns:meta:1.0");
        xmlWriter.addAttribute("xmlns:number", "urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0");
        xmlWriter.addAttribute("xmlns:presentation", "urn:oasis:names:tc:opendocument:xmlns:presentation:1.0");
        xmlWriter.addAttribute("xmlns:svg", "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0");
        xmlWriter.addAttribute("xmlns:chart", "urn:oasis:names:tc:opendocument:xmlns:chart:1.0");
        xmlWriter.addAttribute("xmlns:dr3d", "urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0");
        xmlWriter.addAttribute("xmlns:math", "http://www.w3.org/1998/Math/MathML");
        xmlWriter.addAttribute("xmlns:form", "urn:oasis:names:tc:opendocument:xmlns:form:1.0");
        xmlWriter.addAttribute("xmlns:script", "urn:oasis:names:tc:opendocument:xmlns:script:1.0");
        xmlWriter.addAttribute("xmlns:ooo", "http://openoffice.org/2004/office");
        xmlWriter.addAttribute("xmlns:ooow", "http://openoffice.org/2004/writer");
        xmlWriter.addAttribute("xmlns:oooc", "http://openoffice.org/2004/calc");
        xmlWriter.addAttribute("xmlns:dom", "http://www.w3.org/2001/xml-events");
        xmlWriter.addAttribute("xmlns:xforms", "http://www.w3.org/2002/xforms");
        xmlWriter.addAttribute("xmlns:xsd", "http://www.w3.org/2001/XMLSchema");
        xmlWriter.addAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
        xmlWriter.addAttribute("xmlns:rpt", "http://openoffice.org/2005/report");
        xmlWriter.addAttribute("xmlns:of", "urn:oasis:names:tc:opendocument:xmlns:of:1.2");
        xmlWriter.addAttribute("xmlns:xhtml", "http://www.w3.org/1999/xhtml");
        xmlWriter.addAttribute("xmlns:grddl", "http://www.w3.org/2003/g/data-view#");
        xmlWriter.addAttribute("xmlns:tableooo", "http://openoffice.org/2009/table");
        xmlWriter.addAttribute("xmlns:drawooo", "http://openoffice.org/2010/draw");
        xmlWriter.addAttribute("xmlns:calcext", "urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0");
        xmlWriter.addAttribute("xmlns:loext", "urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0");
        xmlWriter.addAttribute("xmlns:field", "urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0");
        xmlWriter.addAttribute("xmlns:formx", "urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0");
        xmlWriter.addAttribute("xmlns:css3t", "http://www.w3.org/TR/css3-text/");
    }

    public static void openDocumentContent(XMLWriter xmlWriter) throws IOException {
        xmlWriter.startOpenTag("office:document-content");
        addDocumentNameSpaceAttributes(xmlWriter);
        xmlWriter.addAttribute("office:version", "1.2");
        xmlWriter.endOpenTag();
    }

    public static void openDocumentContentVersion1(XMLWriter xmlWriter) throws IOException {
        xmlWriter.startOpenTag("office:document-content");
        xmlWriter.addAttribute("xmlns:office", "urn:oasis:names:tc:opendocument:xmlns:office:1.0");
        xmlWriter.addAttribute("xmlns:style", "urn:oasis:names:tc:opendocument:xmlns:style:1.0");
        xmlWriter.addAttribute("xmlns:text", "urn:oasis:names:tc:opendocument:xmlns:text:1.0");
        xmlWriter.addAttribute("xmlns:table", "urn:oasis:names:tc:opendocument:xmlns:table:1.0");
        xmlWriter.addAttribute("xmlns:draw", "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0");
        xmlWriter.addAttribute("xmlns:fo", "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0");
        xmlWriter.addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
        xmlWriter.addAttribute("xmlns:dc", "http://purl.org/dc/elements/1.1/");
        xmlWriter.addAttribute("xmlns:meta", "urn:oasis:names:tc:opendocument:xmlns:meta:1.0");
        xmlWriter.addAttribute("xmlns:number", "urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0");
        xmlWriter.addAttribute("xmlns:svg", "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0");
        xmlWriter.addAttribute("xmlns:chart", "urn:oasis:names:tc:opendocument:xmlns:chart:1.0");
        xmlWriter.addAttribute("xmlns:dr3d", "urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0");
        xmlWriter.addAttribute("xmlns:math", "http://www.w3.org/1998/Math/MathML");
        xmlWriter.addAttribute("xmlns:form", "urn:oasis:names:tc:opendocument:xmlns:form:1.0");
        xmlWriter.addAttribute("xmlns:script", "urn:oasis:names:tc:opendocument:xmlns:script:1.0");
        xmlWriter.addAttribute("xmlns:ooo", "http://openoffice.org/2004/office");
        xmlWriter.addAttribute("xmlns:ooow", "http://openoffice.org/2004/writer");
        xmlWriter.addAttribute("xmlns:oooc", "http://openoffice.org/2004/calc");
        xmlWriter.addAttribute("xmlns:dom", "http://www.w3.org/2001/xml-events");
        xmlWriter.addAttribute("xmlns:xforms", "http://www.w3.org/2002/xforms");
        xmlWriter.addAttribute("xmlns:xsd", "http://www.w3.org/2001/XMLSchema");
        xmlWriter.addAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
        xmlWriter.addAttribute("office:version", "1.0");
        xmlWriter.endOpenTag();
    }

    public static void closeDocumentContent(XMLWriter xmlWriter) throws IOException {
        xmlWriter.closeTag("office:document-content");
    }

    public static void openDocumentSettings(XMLWriter xmlWriter) throws IOException {
        xmlWriter.startOpenTag("office:document-settings");
        xmlWriter.addAttribute("xmlns:config", "urn:oasis:names:tc:opendocument:xmlns:config:1.0");
        xmlWriter.addAttribute("xmlns:office", "urn:oasis:names:tc:opendocument:xmlns:office:1.0");
        xmlWriter.addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
        xmlWriter.addAttribute("xmlns:ooo", "http://openoffice.org/2004/office");
        xmlWriter.addAttribute("office:version", "1.2");
        xmlWriter.endOpenTag();
    }

    public static void closeDocumentSettings(XMLWriter xmlWriter) throws IOException {
        xmlWriter.closeTag("office:document-settings");
    }

    public static void openAutomaticStyles(XMLWriter xmlWriter) throws IOException {
        xmlWriter.openTag("office:automatic-styles");
    }

    public static void closeAutomaticStyles(XMLWriter xmlWriter) throws IOException {
        xmlWriter.closeTag("office:automatic-styles");
    }

    public static void openBody(XMLWriter xmlWriter) throws IOException {
        xmlWriter.openTag("office:body");
    }

    public static void closeBody(XMLWriter xmlWriter) throws IOException {
        xmlWriter.closeTag("office:body");
    }

    public static void openSpreadsheet(XMLWriter xmlWriter) throws IOException {
        xmlWriter.openTag("office:spreadsheet");
    }

    public static void closeSpreadsheet(XMLWriter xmlWriter) throws IOException {
        xmlWriter.closeTag("office:spreadsheet");
    }

    public static void openText(XMLWriter xmlWriter) throws IOException {
        xmlWriter.openTag("office:text");
    }

    public static void closeText(XMLWriter xmlWriter) throws IOException {
        xmlWriter.closeTag("office:text");
    }

    public static void openStyle(XMLWriter xmlWriter, String styleName, String styleFamily, String parentStyleName) throws IOException {
        startStyleOpenTag(xmlWriter, styleName, styleFamily, parentStyleName);
        xmlWriter.endOpenTag();
    }

    public static void startStyleOpenTag(XMLWriter xmlWriter, String styleName, String styleFamily, String parentStyleName) throws IOException {
        xmlWriter.startOpenTag("style:style");
        xmlWriter.addAttribute("style:name", styleName);
        if (styleName.contains("_20_")) {
            String displayName = styleName.replace("_20_", " ");
            xmlWriter.addAttribute("style:display-name", displayName);
        }
        xmlWriter.addAttribute("style:family", styleFamily);
        xmlWriter.addAttribute("style:parent-style-name", parentStyleName);

    }

    public static void closeStyle(XMLWriter xmlWriter) throws IOException {
        xmlWriter.closeTag("style:style");
    }

    public static void openNamedExpressions(XMLWriter xmlWriter) throws IOException {
        xmlWriter.openTag("table:named-expressions");
    }

    public static void closeNamedExpressions(XMLWriter xmlWriter) throws IOException {
        xmlWriter.closeTag("table:named-expressions");
    }

    public static void addNamedRange(XMLWriter xmlWriter, String name, String address) throws IOException {
        xmlWriter.startOpenTag("table:named-range");
        xmlWriter.addAttribute("table:name", name);
        xmlWriter.addAttribute("table:base-cell-address", address);
        xmlWriter.addAttribute("table:cell-range-address", address);
        xmlWriter.closeEmptyTag();
    }

    public static void openTable(XMLWriter xmlWriter, String name) throws IOException {
        xmlWriter.startOpenTag("table:table");
        xmlWriter.addAttribute("table:name", SheetNameChecker.checkNameCharacters(name));
        xmlWriter.endOpenTag();
    }

    public static void closeTable(XMLWriter xmlWriter) throws IOException {
        xmlWriter.closeTag("table:table");
    }

    public static void addTableColumn(XMLWriter xmlWriter, String styleName, int columnsRepeated, String defaultCellStyleName) throws IOException {
        xmlWriter.startOpenTag("table:table-column");
        xmlWriter.addAttribute("table:style-name", styleName);
        if (columnsRepeated > 1) {
            xmlWriter.addAttribute("table:number-columns-repeated", columnsRepeated);
        }
        if (defaultCellStyleName == null) {
            defaultCellStyleName = "Default";
        }
        xmlWriter.addAttribute("table:default-cell-style-name", defaultCellStyleName);
        xmlWriter.closeEmptyTag();
    }

    public static void openTableRow(XMLWriter xmlWriter) throws IOException {
        xmlWriter.openTag("table:table-row");
    }

    public static void closeTableRow(XMLWriter xmlWriter) throws IOException {
        xmlWriter.closeTag("table:table-row");
    }

    public static void addEmptyTableCell(XMLWriter xmlWriter) throws IOException {
        xmlWriter.startOpenTag("table:table-cell");
        xmlWriter.closeEmptyTag();
    }

    public static void addNumberTableCell(XMLWriter xmlWriter, int itg) throws IOException {
        addNumberTableCell(xmlWriter, String.valueOf(itg), null, 1);
    }

    public static void addNumberTableCell(XMLWriter xmlWriter, String numberValue) throws IOException {
        addNumberTableCell(xmlWriter, numberValue, null, 1);
    }

    public static void addNumberTableCell(XMLWriter xmlWriter, String numberValue, String cellStyleName, int colSpan) throws IOException {
        xmlWriter.startOpenTag("table:table-cell");
        xmlWriter.addAttribute("office:value-type", "float");
        xmlWriter.addAttribute("office:value", numberValue);
        xmlWriter.addAttribute("table:style-name", cellStyleName);
        if (colSpan > 1) {
            xmlWriter.addAttribute("table:number-columns-spanned", colSpan);
        }
        xmlWriter.closeEmptyTag();
        completeColSpan(xmlWriter, colSpan);
    }

    public static void addNumberTableCell(XMLWriter xmlWriter, String numberValue, String cellStyleName, int colSpan, String annotation) throws IOException {
        xmlWriter.startOpenTag("table:table-cell");
        xmlWriter.addAttribute("office:value-type", "float");
        xmlWriter.addAttribute("office:value", numberValue);
        xmlWriter.addAttribute("table:style-name", cellStyleName);
        if (colSpan > 1) {
            xmlWriter.addAttribute("table:number-columns-spanned", colSpan);
        }
        xmlWriter.endOpenTag();
        if (annotation != null) {
            addAnnotation(xmlWriter, annotation);
        }
        xmlWriter.closeTag("table:table-cell");
        completeColSpan(xmlWriter, colSpan);
    }

    public static void addPercentageTableCell(XMLWriter xmlWriter, String numberValue) throws IOException {
        addPercentageTableCell(xmlWriter, numberValue, null, 1);
    }

    public static void addPercentageTableCell(XMLWriter xmlWriter, String numberValue, String cellStyleName, int colSpan) throws IOException {
        xmlWriter.startOpenTag("table:table-cell");
        xmlWriter.addAttribute("office:value-type", "percentage");
        xmlWriter.addAttribute("office:value", numberValue);
        xmlWriter.addAttribute("table:style-name", cellStyleName);
        if (colSpan > 1) {
            xmlWriter.addAttribute("table:number-columns-spanned", colSpan);
        }
        xmlWriter.closeEmptyTag();
        completeColSpan(xmlWriter, colSpan);
    }

    public static void addDateTableCell(XMLWriter xmlWriter, FuzzyDate date) throws IOException {
        addDateTableCell(xmlWriter, date, null, 1);
    }

    public static void addDateTableCell(XMLWriter xmlWriter, FuzzyDate date, String cellStyleName, int colSpan) throws IOException {
        xmlWriter.startOpenTag("table:table-cell");
        xmlWriter.addAttribute("office:value-type", "date");
        xmlWriter.addAttribute("office:date-value", date.toISOString());
        xmlWriter.addAttribute("table:style-name", cellStyleName);
        if (colSpan > 1) {
            xmlWriter.addAttribute("table:number-columns-spanned", colSpan);
        }
        xmlWriter.closeEmptyTag();
        completeColSpan(xmlWriter, colSpan);
    }

    public static void addCurrencyTableCell(XMLWriter xmlWriter, Amount amount) throws IOException {
        addCurrencyTableCell(xmlWriter, amount, null, 1);
    }

    public static void addCurrencyTableCell(XMLWriter xmlWriter, Amount amount, String cellStyleName, int colSpan) throws IOException {
        xmlWriter.startOpenTag("table:table-cell");
        xmlWriter.addAttribute("office:value-type", "currency");
        xmlWriter.addAttribute("office:currency", amount.getCurrencyCode());
        xmlWriter.addAttribute("office:value", amount.toDecimal(false).toString());
        xmlWriter.addAttribute("table:style-name", cellStyleName);
        if (colSpan > 1) {
            xmlWriter.addAttribute("table:number-columns-spanned", colSpan);
        }
        xmlWriter.closeEmptyTag();
        completeColSpan(xmlWriter, colSpan);
    }

    public static void addLinkStringTableCell(XMLWriter xmlWriter, String linkString) throws IOException {
        xmlWriter.startOpenTag("table:table-cell");
        xmlWriter.addAttribute("office:value-type", "string");
        xmlWriter.endOpenTag();
        if ((linkString != null) && (linkString.length() > 0)) {
            xmlWriter.startOpenTag("text:p");
            xmlWriter.endOpenTag();
            addLink(xmlWriter, linkString);
            xmlWriter.closeTag("text:p", false);
        }
        xmlWriter.closeTag("table:table-cell");
    }

    public static void addStringTableCell(XMLWriter xmlWriter, String value) throws IOException {
        addStringTableCell(xmlWriter, value, null, 1);
    }

    public static void addStringTableCell(XMLWriter xmlWriter, String value, String cellStyleName, int colSpan) throws IOException {
        xmlWriter.startOpenTag("table:table-cell");
        xmlWriter.addAttribute("office:value-type", "string");
        xmlWriter.addAttribute("table:style-name", cellStyleName);
        if (colSpan > 1) {
            xmlWriter.addAttribute("table:number-columns-spanned", colSpan);
        }
        xmlWriter.endOpenTag();
        splitText(xmlWriter, value);
        xmlWriter.closeTag("table:table-cell");
        completeColSpan(xmlWriter, colSpan);
    }

    public static void addStringTableCell(XMLWriter xmlWriter, String value, String cellStyleName, int colSpan, String annotation) throws IOException {
        xmlWriter.startOpenTag("table:table-cell");
        xmlWriter.addAttribute("office:value-type", "string");
        xmlWriter.addAttribute("table:style-name", cellStyleName);
        if (colSpan > 1) {
            xmlWriter.addAttribute("table:number-columns-spanned", colSpan);
        }
        xmlWriter.endOpenTag();
        if (annotation != null) {
            addAnnotation(xmlWriter, annotation);
        }
        splitText(xmlWriter, value);
        xmlWriter.closeTag("table:table-cell");
        completeColSpan(xmlWriter, colSpan);
    }

    public static void addAnnotation(XMLWriter xmlWriter, String annotation) throws IOException {
        xmlWriter.startOpenTag("office:annotation");
        xmlWriter.endOpenTag();
        splitText(xmlWriter, annotation);
        xmlWriter.closeTag("office:annotation");
    }

    public static void addFormulaTableCell(XMLWriter xmlWriter, String formula, String valueType, String cellStyleName, int colSpan) throws IOException {
        xmlWriter.startOpenTag("table:table-cell");
        xmlWriter.addAttribute("table:formula", formula);
        xmlWriter.addAttribute("office:value-type", valueType);
        xmlWriter.addAttribute("table:style-name", cellStyleName);
        if (colSpan > 1) {
            xmlWriter.addAttribute("table:number-columns-spanned", colSpan);
        }
        xmlWriter.closeEmptyTag();
        completeColSpan(xmlWriter, colSpan);
    }

    public static void splitText(XMLWriter xmlWriter, String value) throws IOException {
        if ((value != null) && (value.length() > 0)) {
            String[] tokenArray = StringUtils.getTokens(value, '\n', StringUtils.NOTCLEAN);
            int length = tokenArray.length;
            for (int i = 0; i < length; i++) {
                String token = tokenArray[i];
                xmlWriter.startOpenTag("text:p");
                xmlWriter.endOpenTag();
                if (token.length() > 0) {
                    xmlWriter.addText(token);
                }
                xmlWriter.closeTag("text:p", false);
            }
        }
    }

    public static void addLink(XMLWriter xmlWriter, String link) throws IOException {
        xmlWriter.startOpenTag("text:a", false);
        xmlWriter.addAttribute("xlink:href", link);
        xmlWriter.endOpenTag();
        xmlWriter.addText(link);
        xmlWriter.closeTag("text:a", false);
    }

    public static void openSettings(XMLWriter xmlWriter) throws IOException {
        xmlWriter.openTag("office:settings");
    }

    public static void closeSettings(XMLWriter xmlWriter) throws IOException {
        xmlWriter.closeTag("office:settings");
    }

    public static void openConfigItemSet(XMLWriter xmlWriter, String name) throws IOException {
        xmlWriter.startOpenTag("config:config-item-set");
        xmlWriter.addAttribute("config:name", name);
        xmlWriter.endOpenTag();
    }

    public static void closeConfigItemSet(XMLWriter xmlWriter) throws IOException {
        xmlWriter.closeTag("config:config-item-set");
    }

    public static void openConfigItemMapIndexed(XMLWriter xmlWriter, String name) throws IOException {
        xmlWriter.startOpenTag("config:config-item-map-indexed");
        xmlWriter.addAttribute("config:name", name);
        xmlWriter.endOpenTag();
    }

    public static void closeConfigItemMapIndexed(XMLWriter xmlWriter) throws IOException {
        xmlWriter.closeTag("config:config-item-map-indexed");
    }

    public static void openConfigItemMapNamed(XMLWriter xmlWriter, String name) throws IOException {
        xmlWriter.startOpenTag("config:config-item-map-named");
        xmlWriter.addAttribute("config:name", name);
        xmlWriter.endOpenTag();
    }

    public static void closeConfigItemMapNamed(XMLWriter xmlWriter) throws IOException {
        xmlWriter.closeTag("config:config-item-map-named");
    }

    public static void openConfigItemMapEntry(XMLWriter xmlWriter, String name) throws IOException {
        xmlWriter.startOpenTag("config:config-item-map-entry");
        xmlWriter.addAttribute("config:name", name);
        xmlWriter.endOpenTag();
    }

    public static void closeConfigItemMapEntry(XMLWriter xmlWriter) throws IOException {
        xmlWriter.closeTag("config:config-item-map-entry");
    }


    public static void addConfigItem(XMLWriter xmlWriter, String name, String type, String value) throws IOException {
        xmlWriter.startOpenTag("config:config-item", true);
        xmlWriter.addAttribute("config:name", name);
        xmlWriter.addAttribute("config:type", type);
        xmlWriter.endOpenTag();
        xmlWriter.addText(value);
        xmlWriter.closeTag("config:config-item", false);
    }

    private static void completeColSpan(XMLWriter xmlWriter, int colSpan) throws IOException {
        if (colSpan > 1) {
            xmlWriter.startOpenTag("table:covered-table-cell");
            xmlWriter.addAttribute("table:number-columns-repeated", (colSpan - 1));
            xmlWriter.closeEmptyTag();
        }
    }

    public static String toLetter(int columnNumber) {
        StringBuilder buf = new StringBuilder();
        if (columnNumber < 27) {
            buf.append(toChar(columnNumber));
        } else {
            int dizaine = columnNumber / 26;
            buf.append(toChar(dizaine));
            int unite = columnNumber % 26;
            if (unite == 0) {
                unite = 26;
            }
            buf.append(toChar(unite));
        }
        return buf.toString();
    }

    private static char toChar(int columnNumber) {
        return (char) (columnNumber + 64);
    }

}
