/* OdLib_Io - Copyright (c) 2013-2020 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.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 ISODATE_DATASTYLE_NAME = "N101";
    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<OdCellKey, CellStyle> cellStyleNameMap = new LinkedHashMap<OdCellKey, CellStyle>();
    private final Map<ExtendedCurrency, Integer> currencyNumberMap = new LinkedHashMap<ExtendedCurrency, Integer>();
    private ElementMaps elementMaps = null;
    private boolean withISODate = false;
    private int columnStyleNumber = 1;
    private int cellStyleNumber = 1;
    private int currencyStyleNumber = 102;

    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(OdCellKey odCellKey) {
        CellStyle cellStyle = cellStyleNameMap.get(odCellKey);
        if (cellStyle != null) {
            return cellStyle;
        }
        String name = CELLSTYLE_PREFIX + cellStyleNumber;
        cellStyleNumber++;
        String dataStyleName = null;
        switch (odCellKey.getStyleFamliy()) {
            case OdColumnDef.DATE_STYLE_FAMILY:
                withISODate = true;
                dataStyleName = ISODATE_DATASTYLE_NAME;
                break;
            case OdColumnDef.CURRENCY_STYLE_FAMILY:
                ExtendedCurrency currency = odCellKey.getCurrency();
                int number;
                if (currencyNumberMap.containsKey(currency)) {
                    number = currencyNumberMap.get(currency);
                } else {
                    number = currencyStyleNumber;
                    currencyStyleNumber++;
                    currencyNumberMap.put(currency, number);
                }
                dataStyleName = DATASTYLE_PREFIX + number;
                break;
        }
        cellStyle = new CellStyle(name, dataStyleName, odCellKey.getStyleName());
        cellStyleNameMap.put(odCellKey, cellStyle);
        return cellStyle;
    }

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


    private class InternalStyleManager implements StyleManager {

        private InternalStyleManager() {

        }

        @Override
        public String getCellStyleName(String tableName, int columnNumber, short styleFamily, String parentStyleName) {
            if (styleFamily == OdColumnDef.CURRENCY_STYLE_FAMILY) {
                throw new IllegalArgumentException("Not use with styleFamily = OdColumnDef.CURRENCY_STYLE_FAMILY");
            }
            OdCellKey odCellKey = OdCellKey.newInstance(styleFamily, parentStyleName);
            return getCellStyleName(tableName, columnNumber, odCellKey);
        }

        @Override
        public String getCurrencyStyleName(String tableName, int columnNumber, String parentStyleName, ExtendedCurrency currency) {
            OdCellKey odCellKey = OdCellKey.newInstance(OdColumnDef.CURRENCY_STYLE_FAMILY, currency, parentStyleName);
            return getCellStyleName(tableName, columnNumber, odCellKey);
        }

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

        @Override
        public void insertAutomaticStyles(XMLWriter xmlWriter) throws IOException {
            if (elementMaps != null) {
                StylesXMLPart stylesPart = new StylesXMLPart(xmlWriter);
                stylesPart.insertAutomaticStyles(elementMaps, true);
            }
            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");
                    xmlWriter.addAttribute("style:column-width", "3.5cm");
                    xmlWriter.closeEmptyTag();
                    OdXML.closeStyle(xmlWriter);
                }
            }
            if (withISODate) {
                xmlWriter.startOpenTag("number:date-style");
                xmlWriter.addAttribute("style:name", ISODATE_DATASTYLE_NAME);
                xmlWriter.endOpenTag();
                xmlWriter.startOpenTag("number:year");
                xmlWriter.addAttribute("number:style", "long");
                xmlWriter.closeEmptyTag();
                xmlWriter.addSimpleElement("number:text", "-");
                xmlWriter.startOpenTag("number:month");
                xmlWriter.addAttribute("number:style", "long");
                xmlWriter.closeEmptyTag();
                xmlWriter.addSimpleElement("number:text", "-");
                xmlWriter.startOpenTag("number:day");
                xmlWriter.addAttribute("number:style", "long");
                xmlWriter.closeEmptyTag();
                xmlWriter.closeTag("number:date-style");
            }
            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");
                xmlWriter.addAttribute("style:name", dataStyleName + "P0");
                xmlWriter.addAttribute("style:volatile", "true");
                xmlWriter.endOpenTag();
                if (before) {
                    xmlWriter.addSimpleElement("number:currency-symbol", symbol);
                }
                xmlWriter.startOpenTag("number:number");
                xmlWriter.addAttribute("number:decimal-places", fractionDigits);
                xmlWriter.addAttribute("number:min-integer-digits", "1");
                xmlWriter.addAttribute("number:grouping", "true");
                xmlWriter.closeEmptyTag();
                if (!before) {
                    xmlWriter.addSimpleElement("number:text", " ");
                    xmlWriter.addSimpleElement("number:currency-symbol", symbol);
                }
                xmlWriter.closeTag("number:currency-style");

                xmlWriter.startOpenTag("number:currency-style");
                xmlWriter.addAttribute("style:name", dataStyleName);
                xmlWriter.endOpenTag();
                xmlWriter.startOpenTag("style:text-properties");
                xmlWriter.addAttribute("fo:color", "#ff0000");
                xmlWriter.closeEmptyTag();
                xmlWriter.addSimpleElement("number:text", "-");
                if (before) {
                    xmlWriter.addSimpleElement("number:currency-symbol", symbol);
                }
                xmlWriter.startOpenTag("number:number");
                xmlWriter.addAttribute("number:decimal-places", fractionDigits);
                xmlWriter.addAttribute("number:min-integer-digits", "1");
                xmlWriter.addAttribute("number:grouping", "true");
                xmlWriter.closeEmptyTag();
                if (!before) {
                    xmlWriter.addSimpleElement("number:text", " ");
                    xmlWriter.addSimpleElement("number:currency-symbol", symbol);
                }
                xmlWriter.startOpenTag("style:map");
                xmlWriter.addAttribute("style:condition", "value()>=0");
                xmlWriter.addAttribute("style:apply-style-name", dataStyleName + "P0");
                xmlWriter.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);
                xmlWriter.addAttribute("style:data-style-name", cellStyle.getDataStyleName());
                xmlWriter.closeEmptyTag();
            }
        }

        private String getCellStyleName(String tableName, int columnNumber, OdCellKey odCellKey) {
            TableInfo tableInfo = tableInfoMap.get(tableName);
            if (tableInfo != null) {
                OdCellKey defaultOdCellKey = tableInfo.getDefaultOdCellKey(columnNumber);
                if ((defaultOdCellKey != null) && (defaultOdCellKey.equals(odCellKey))) {
                    return null;
                }
            }
            CellStyle cellStyle = initCellStyle(odCellKey);
            return cellStyle.getName();
        }

    }


    private class TableInfo {

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

        private TableInfo(OdTableDef odTableDef) {
            for (OdColumnDef columnDef : odTableDef.getOdColumnDefList()) {
                short type = columnDef.getColumnStyleFamily();
                ExtendedCurrency currency = columnDef.getCurrency();
                String customStyleName = columnDef.getCustomStyleName();
                String columnStyleName = null;
                if (customStyleName != null) {
                    columnStyleName = customStyleName;
                } else {
                    columnStyleName = getNewColumnStyleName();
                }
                OdCellKey defaultOdCellKey = OdCellKey.newInstance(type, currency, columnDef.getDefaultCellStyleName());
                CellStyle defaultCellStyle = initCellStyle(defaultOdCellKey);
                odColumnList.add(new OdColumn(columnStyleName, defaultOdCellKey, defaultCellStyle));
            }
        }

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


    }

}
