/* OdLib_Io - Copyright (c) 2013-2025 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.odtable;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import net.mapeadores.opendocument.elements.ElementMaps;
import net.mapeadores.opendocument.io.OdXML;
import net.mapeadores.opendocument.io.StylesXMLPart;
import net.mapeadores.util.date.DateUtils;
import net.mapeadores.util.money.ExtendedCurrency;
import net.mapeadores.util.xml.XMLWriter;


/**
 * Moteur gérant les styles de cellule.
 *
 * @author Vincent Calame
 */
public class StyleManagerBuilder {

    private final static String COLUMNSTYLE_PREFIX = "co";
    private final static String CELLSTYLE_PREFIX = "ce";
    private final static String DATASTYLE_PREFIX = "N";
    private final static String DEFAULT_CELLSTYLE_NAME = "Default";
    private final List<String> columnStyleNameList = new ArrayList<String>();
    private final Map<String, TableInfo> tableInfoMap = new HashMap<String, TableInfo>();
    private final Map<StyleKey, CellStyle> cellStyleNameMap = new LinkedHashMap<StyleKey, CellStyle>();
    private final Map<ExtendedCurrency, Integer> currencyNumberMap = new LinkedHashMap<ExtendedCurrency, Integer>();
    private final Map<String, Integer> patternNumberMap = new LinkedHashMap<String, Integer>();
    private ElementMaps elementMaps = null;
    private int columnStyleNumber = 1;
    private int cellStyleNumber = 1;
    private int dataStyleNumber = 101;

    public StyleManagerBuilder() {
    }

    /**
     *
     * @param tableName
     * @param odTableDef
     */
    public StyleManagerBuilder putTableDef(String tableName, OdTableDef odTableDef) {
        if (!tableInfoMap.containsKey(tableName)) {
            tableInfoMap.put(tableName, new TableInfo(odTableDef));
        }
        return this;
    }

    public StyleManagerBuilder addTableDef(OdTableDef odTableDef) {
        String tableName = odTableDef.getTableName();
        if (!tableInfoMap.containsKey(tableName)) {
            tableInfoMap.put(tableName, new TableInfo(odTableDef));
        }
        return this;
    }

    public StyleManagerBuilder setElementMaps(ElementMaps elementMaps) {
        this.elementMaps = elementMaps;
        return this;
    }

    public StyleManager toStyleManager() {
        return new InternalStyleManager();
    }

    /**
     * Ajoute les styles automatiques créés suivant le contenu de OdTableDef,
     * ainsi que ceux créés par l'appel de getCellStyleProvider(). Le point de
     * vigilance, c'est que dans le format XML ODS les styles automatiques sont
     * placés avant le contenu, or ces styles automatiques sont spécifiques au
     * contenu. Il faut donc créer le contenu dans un tampon pour ensuite
     * insérer les styles automatiques et le contenu dans le fichier ODS final.
     */

    private String getNewColumnStyleName() {
        String styleName = COLUMNSTYLE_PREFIX + String.valueOf(columnStyleNumber);
        columnStyleNumber++;
        columnStyleNameList.add(styleName);
        return styleName;
    }

    private CellStyle initCellStyle(StyleKey styleKey) {
        CellStyle cellStyle = cellStyleNameMap.get(styleKey);
        if (cellStyle != null) {
            return cellStyle;
        }
        String name = CELLSTYLE_PREFIX + cellStyleNumber;
        cellStyleNumber++;
        String dataStyleName = null;
        switch (styleKey.getStyleFamliy()) {
            case OdColumnDef.DATE_STYLE_FAMILY: {
                String pattern = styleKey.getPattern();
                int number;
                if (patternNumberMap.containsKey(pattern)) {
                    number = patternNumberMap.get(pattern);
                } else {
                    number = dataStyleNumber;
                    dataStyleNumber++;
                    patternNumberMap.put(pattern, number);
                }
                dataStyleName = DATASTYLE_PREFIX + number;
                break;
            }
            case OdColumnDef.CURRENCY_STYLE_FAMILY: {
                ExtendedCurrency currency = styleKey.getCurrency();
                int number;
                if (currencyNumberMap.containsKey(currency)) {
                    number = currencyNumberMap.get(currency);
                } else {
                    number = dataStyleNumber;
                    dataStyleNumber++;
                    currencyNumberMap.put(currency, number);
                }
                dataStyleName = DATASTYLE_PREFIX + number;
                break;
            }
        }
        cellStyle = new CellStyle(name, dataStyleName, styleKey.getParentStyleName());
        cellStyleNameMap.put(styleKey, cellStyle);
        return cellStyle;
    }

    public static StyleManagerBuilder init() {
        return new StyleManagerBuilder();
    }


    private class InternalStyleManager implements StyleManager {

        private InternalStyleManager() {

        }

        @Override
        public String getDateCellStyleName(String tableName, int columnNumber, String pattern, String customStyleName) {
            StyleKey styleKey;
            StyleKey defaultStyleKey = getDefaultStyleKey(tableName, columnNumber);
            if (defaultStyleKey == null) {
                if (pattern == null) {
                    pattern = DateUtils.ISO_JAVAPATTERN;
                }
                styleKey = StyleKey.newDateInstance(pattern, customStyleName);
            } else if (!defaultStyleKey.isDateStyle()) {
                if (pattern == null) {
                    pattern = DateUtils.ISO_JAVAPATTERN;
                }
                if (customStyleName != null) {
                    styleKey = StyleKey.newDateInstance(pattern, customStyleName);
                } else {
                    styleKey = StyleKey.newDateInstance(pattern, defaultStyleKey.getParentStyleName());
                }
            } else if (customStyleName == null) {
                if (pattern == null) {
                    styleKey = defaultStyleKey;
                } else {
                    styleKey = StyleKey.newDateInstance(pattern, defaultStyleKey.getParentStyleName());
                }
            } else {
                if (pattern == null) {
                    pattern = defaultStyleKey.getPattern();
                }
                styleKey = StyleKey.newDateInstance(pattern, customStyleName);
            }
            if ((defaultStyleKey != null) && (styleKey.equals(defaultStyleKey))) {
                return null;
            }
            return getCellStyleName(styleKey);
        }

        @Override
        public String getStandardCellStyleName(String tableName, int columnNumber, String customStyleName) {
            StyleKey styleKey;
            StyleKey defaultStyleKey = getDefaultStyleKey(tableName, columnNumber);
            if (defaultStyleKey == null) {
                if (customStyleName == null) {
                    return null;
                }
                styleKey = StyleKey.newIsoDateInstance(customStyleName);
            } else if (!defaultStyleKey.isStandardStyle()) {
                if (customStyleName != null) {
                    styleKey = StyleKey.newStandardInstance(customStyleName);
                } else {
                    if (defaultStyleKey.getParentStyleName() == null) {
                        return null;
                    }
                    styleKey = StyleKey.newStandardInstance(defaultStyleKey.getParentStyleName());
                }
            } else if (customStyleName == null) {
                styleKey = defaultStyleKey;
            } else {
                styleKey = StyleKey.newStandardInstance(customStyleName);
            }
            if ((defaultStyleKey != null) && (styleKey.equals(defaultStyleKey))) {
                return null;
            }
            return getCellStyleName(styleKey);
        }

        @Override
        public String getCurrencyCellStyleName(String tableName, int columnNumber, ExtendedCurrency currency, String customStyleName) {
            StyleKey styleKey;
            StyleKey defaultStyleKey = getDefaultStyleKey(tableName, columnNumber);
            if (defaultStyleKey == null) {
                styleKey = StyleKey.newCurrencyInstance(currency, customStyleName);
            } else if (!defaultStyleKey.isCurrencyStyle()) {
                if (customStyleName != null) {
                    styleKey = StyleKey.newCurrencyInstance(currency, customStyleName);
                } else {
                    styleKey = StyleKey.newCurrencyInstance(currency, defaultStyleKey.getParentStyleName());
                }
            } else if (customStyleName == null) {
                styleKey = StyleKey.newCurrencyInstance(currency, defaultStyleKey.getParentStyleName());
            } else {
                styleKey = StyleKey.newCurrencyInstance(currency, customStyleName);
            }
            if ((defaultStyleKey != null) && (styleKey.equals(defaultStyleKey))) {
                return null;
            }
            return getCellStyleName(styleKey);
        }

        @Override
        public List<OdColumn> getOdColumnList(String tableName) {
            TableInfo tableInfo = tableInfoMap.get(tableName);
            if (tableInfo == null) {
                return null;
            }
            return tableInfo.odColumnList;
        }

        @Override
        public String getCellStyleName(StyleKey styleKey) {
            CellStyle cellStyle = initCellStyle(styleKey);
            return cellStyle.getName();
        }

        @Override
        public void insertAutomaticStyles(XMLWriter xmlWriter) throws IOException {
            if (elementMaps != null) {
                StylesXMLPart stylesPart = new StylesXMLPart(xmlWriter);
                stylesPart.insertAutomaticStyles(elementMaps, true);
            }
            DateStyleXMLPart dateStyleXMLPart = new DateStyleXMLPart(xmlWriter);
            for (String columnStyleName : columnStyleNameList) {
                boolean done = false;
                if (elementMaps != null) {
                    if (elementMaps.getElement("col", columnStyleName, false) != null) {
                        done = true;
                    }
                }
                if (!done) {
                    OdXML.openStyle(xmlWriter, columnStyleName, "table-column", null);
                    xmlWriter.startOpenTag("style:table-column-properties")
                            .addAttribute("style:column-width", "3.5cm")
                            .closeEmptyTag();
                    OdXML.closeStyle(xmlWriter);
                }
            }
            for (Map.Entry<String, Integer> entry : patternNumberMap.entrySet()) {
                String pattern = entry.getKey();
                int number = entry.getValue();
                String dataStyleName = DATASTYLE_PREFIX + number;
                dateStyleXMLPart.writeFromJavaPattern(dataStyleName, pattern);
            }
            for (Map.Entry<ExtendedCurrency, Integer> entry : currencyNumberMap.entrySet()) {
                ExtendedCurrency currency = entry.getKey();
                int number = entry.getValue();
                String dataStyleName = DATASTYLE_PREFIX + number;
                int fractionDigits = currency.getDefaultFractionDigits();
                String symbol = currency.getSymbol();
                boolean before = currency.isSymbolBefore();
                xmlWriter.startOpenTag("number:currency-style")
                        .addAttribute("style:name", dataStyleName + "P0")
                        .addAttribute("style:volatile", "true")
                        .endOpenTag();
                if (before) {
                    xmlWriter.addSimpleElement("number:currency-symbol", symbol);
                }
                xmlWriter.startOpenTag("number:number")
                        .addAttribute("number:decimal-places", fractionDigits)
                        .addAttribute("number:min-integer-digits", "1")
                        .addAttribute("number:grouping", "true")
                        .closeEmptyTag();
                if (!before) {
                    xmlWriter.addSimpleElement("number:text", " ");
                    xmlWriter.addSimpleElement("number:currency-symbol", symbol);
                }
                xmlWriter.closeTag("number:currency-style");

                xmlWriter.startOpenTag("number:currency-style")
                        .addAttribute("style:name", dataStyleName)
                        .endOpenTag();
                xmlWriter.startOpenTag("style:text-properties")
                        .addAttribute("fo:color", "#ff0000")
                        .closeEmptyTag();
                xmlWriter.addSimpleElement("number:text", "-");
                if (before) {
                    xmlWriter.addSimpleElement("number:currency-symbol", symbol);
                }
                xmlWriter.startOpenTag("number:number")
                        .addAttribute("number:decimal-places", fractionDigits)
                        .addAttribute("number:min-integer-digits", "1")
                        .addAttribute("number:grouping", "true")
                        .closeEmptyTag();
                if (!before) {
                    xmlWriter.addSimpleElement("number:text", " ");
                    xmlWriter.addSimpleElement("number:currency-symbol", symbol);
                }
                xmlWriter.startOpenTag("style:map")
                        .addAttribute("style:condition", "value()>=0")
                        .addAttribute("style:apply-style-name", dataStyleName + "P0")
                        .closeEmptyTag();
                xmlWriter.closeTag("number:currency-style");
            }
            for (CellStyle cellStyle : cellStyleNameMap.values()) {
                String parentStyleName = cellStyle.getParentStyleName();
                if (parentStyleName == null) {
                    parentStyleName = DEFAULT_CELLSTYLE_NAME;
                }
                OdXML.startStyleOpenTag(xmlWriter, cellStyle.getName(), "table-cell", parentStyleName)
                        .addAttribute("style:data-style-name", cellStyle.getDataStyleName())
                        .closeEmptyTag();
            }
        }

        private StyleKey getDefaultStyleKey(String tableName, int columnNumber) {
            TableInfo tableInfo = tableInfoMap.get(tableName);
            if (tableInfo != null) {
                return tableInfo.getDefaultStyleKey(columnNumber);
            } else {
                return null;
            }
        }

    }


    private class TableInfo {

        private final List<OdColumn> odColumnList = new ArrayList<OdColumn>();

        private TableInfo(OdTableDef odTableDef) {
            for (OdColumnDef columnDef : odTableDef.getOdColumnDefList()) {
                StyleKey defaultStyleKey = columnDef.getDefaultStyleKey();
                String customStyleName = columnDef.getCustomStyleName();
                String columnStyleName;
                if (customStyleName != null) {
                    columnStyleName = customStyleName;
                } else {
                    columnStyleName = getNewColumnStyleName();
                }
                CellStyle defaultCellStyle = initCellStyle(defaultStyleKey);
                odColumnList.add(new OdColumn(columnStyleName, defaultStyleKey, defaultCellStyle));
            }
        }

        private StyleKey getDefaultStyleKey(int columnNumber) {
            if ((columnNumber > 0) && (columnNumber <= odColumnList.size())) {
                return odColumnList.get(columnNumber - 1).getDefaultStyleKey();
            }
            return null;
        }


    }

}
