/* FichothequeLib_Tools - Copyright (c) 2016-2023 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.fichotheque.tools.exportation.table;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.fichotheque.SubsetKey;
import net.fichotheque.exportation.table.ColDef;
import net.fichotheque.exportation.table.SubsetTable;
import net.fichotheque.utils.TableDefUtils;
import net.mapeadores.opendocument.io.OdXML;
import net.mapeadores.util.format.FormatConstants;
import net.mapeadores.util.instruction.Instruction;
import net.mapeadores.util.instruction.Instructions;
import net.mapeadores.util.instruction.InstructionsParser;
import net.mapeadores.util.xml.XMLWriter;


/**
 *
 * @author Vincent Calame
 */
public class FormulaEngine {

    public final static String CELL_ARGUMENT = "cell";
    public final static String COLUMNSUM_ARGUMENT = "columnsum";
    private final boolean[] isFormulaArray;
    private final String[] valueTypeArray;
    private final Map<String, ColInfo> colMap = new HashMap<String, ColInfo>();
    private final Map<String, Object> instructionMap = new HashMap<String, Object>();
    private final SubsetKey subsetKey;

    public FormulaEngine(SubsetTable subsetTable) {
        this.subsetKey = subsetTable.getSubset().getSubsetKey();
        List<ColDef> colDefList = subsetTable.getColDefList();
        int colLength = colDefList.size();
        this.isFormulaArray = new boolean[colLength];
        this.valueTypeArray = new String[colLength];
        for (int i = 0; i < colLength; i++) {
            ColDef colDef = colDefList.get(i);
            String letter = OdXML.toLetter(i + 1);
            colMap.put(colDef.getColName(), new ColInfo(colDef, letter));
            if (TableDefUtils.isFormula(colDef)) {
                isFormulaArray[i] = true;
                valueTypeArray[i] = getType(colDef);
            } else {
                isFormulaArray[i] = false;
            }
        }
    }

    public boolean isFormulaColumn(int columnNumber) {
        return isFormulaArray[columnNumber - 1];
    }

    public void writeFormulaCell(int rowNumber, int columnNumber, XMLWriter xmlWriter, String s) throws IOException {
        Object obj = instructionMap.get(s);
        if (obj == null) {
            obj = build(s);
            instructionMap.put(s, obj);
        }
        if (obj instanceof String) {
            OdXML.addStringTableCell(xmlWriter, (String) obj);
            return;
        }
        String formula = resoveFormula((Object[]) obj, rowNumber);
        OdXML.addFormulaTableCell(xmlWriter, formula, valueTypeArray[columnNumber - 1], null, 1);
    }

    private String getType(ColDef colDef) {
        switch (TableDefUtils.checkFormulaCastType(colDef)) {
            case FormatConstants.PERCENTAGE_CAST:
                return "percentage";
            case FormatConstants.MONEY_CAST:
                return "currency";
            default:
                return "float";
        }
    }

    private String resoveFormula(Object[] formulaArray, int rowNumber) {
        StringBuilder buf = new StringBuilder();
        buf.append("of:=");
        int length = formulaArray.length;
        for (int i = 0; i < length; i++) {
            Object obj = formulaArray[i];
            if (obj instanceof String) {
                buf.append((String) obj);
            } else if (obj instanceof ColInfo) {
                buf.append("[.");
                buf.append(((ColInfo) obj).letter);
                buf.append(rowNumber);
                buf.append("]");
            }
        }
        return buf.toString();
    }

    private Object build(String s) {
        Instructions instructions = InstructionsParser.parse(s);
        int partSize = instructions.size();
        if (partSize == 0) {
            return "#Error# empty instruction";
        }
        if (partSize == 1) {
            Instructions.Part firstPart = instructions.get(0);
            if (firstPart instanceof Instructions.LiteralPart) {
                return ((Instructions.LiteralPart) firstPart).getText();
            }
        }
        Object[] partArray = new Object[partSize];
        String error = null;
        for (int i = 0; i < partSize; i++) {
            Instructions.Part part = instructions.get(i);
            if (part instanceof Instructions.LiteralPart) {
                partArray[i] = ((Instructions.LiteralPart) part).getText();
            } else {
                Instruction instruction = ((Instructions.InstructionPart) part).getInstruction();
                String argumentKey = instruction.get(0).getKey();
                String argumentValue = instruction.get(0).getValue();
                if (argumentKey.equals(CELL_ARGUMENT)) {
                    ColInfo colInfo = colMap.get(argumentValue);
                    if (colInfo == null) {
                        error = "#Error# unknown column: " + argumentValue;
                        break;
                    }
                    partArray[i] = colInfo;
                } else if (argumentKey.equals(COLUMNSUM_ARGUMENT)) {
                    String columnSum = checkColumnSum(argumentValue);
                    if (columnSum == null) {
                        error = "#Error# unknown columnsum: " + argumentValue;
                        break;
                    }
                    partArray[i] = columnSum;
                } else {
                    error = "#Error# unknown argument: " + argumentKey;
                    break;
                }
            }
        }
        if (error != null) {
            return error;
        } else {
            return partArray;
        }
    }

    private String checkColumnSum(String argument) {
        if (argument == null) {
            return null;
        }
        ColInfo colInfo = colMap.get(argument);
        if (colInfo == null) {
            return null;
        }
        if (!TableDefUtils.isWithColumnSum(colInfo.colDef)) {
            return null;
        }
        return "columnsum_" + subsetKey + "_" + colInfo.colDef.getColName();
    }


    private static class ColInfo {

        private final ColDef colDef;
        private final String letter;

        private ColInfo(ColDef colDef, String letter) {
            this.colDef = colDef;
            this.letter = letter;
        }

    }

}
