/* FichothequeLib_Tools - Copyright (c) 2007-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.fichotheque.tools.format.parsers;

import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import net.fichotheque.corpus.fiche.FicheItem;
import net.fichotheque.corpus.metadata.CorpusField;
import net.fichotheque.corpus.metadata.FieldOptionConstants;
import net.fichotheque.format.FormatContext;
import net.fichotheque.format.FormatSource;
import net.fichotheque.format.formatters.FicheBlockFormatter;
import net.fichotheque.format.formatters.FicheItemFormatter;
import net.fichotheque.tools.format.DefaultPatternCatalog;
import net.fichotheque.tools.format.JsonParameters;
import net.fichotheque.tools.format.LangParameters;
import net.fichotheque.tools.format.catalogs.FicheItemFormatterCatalog;
import net.fichotheque.utils.FormatterUtils;
import net.mapeadores.util.date.FuzzyDate;
import net.mapeadores.util.exceptions.SwitchException;
import net.mapeadores.util.format.FormatConstants;
import net.mapeadores.util.instruction.Argument;
import net.mapeadores.util.instruction.Instruction;
import net.mapeadores.util.instruction.InstructionResolver;
import net.mapeadores.util.logging.ErrorMessageException;
import net.mapeadores.util.logging.MessageHandler;


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

    private final static JsonParameters AMOUNT_DEFAULTJSONPARAMETERS = JsonParameters.build("type", "currency", "decimal", "long");
    private final static JsonParameters COUNTRY_DEFAULTJSONPARAMETERS = JsonParameters.build("type", "code");
    private final static JsonParameters DATE_DEFAULTJSONPARAMETERS = JsonParameters.build("type", "datetype", "code", "iso");
    private final static JsonParameters EMAIL_DEFAULTJSONPARAMETERS = JsonParameters.build("type", "address");
    private final static JsonParameters GEOPOINT_DEFAULTJSONPARAMETERS = JsonParameters.build("type", "lat", "lon");
    private final static JsonParameters IMAGE_DEFAULTJSONPARAMETERS = JsonParameters.build("type", "src");
    private final static JsonParameters ITEM_DEFAULTJSONPARAMETERS = JsonParameters.build("type", "value");
    private final static JsonParameters LANGUAGE_DEFAULTJSONPARAMETERS = JsonParameters.build("type", "code");
    private final static JsonParameters LINK_DEFAULTJSONPARAMETERS = JsonParameters.build("type", "href");
    private final static JsonParameters NUMBER_DEFAULTJSONPARAMETERS = JsonParameters.build("type", "code", "value");
    private final static JsonParameters PARA_DEFAULTJSONPARAMETERS = JsonParameters.build("type", "raw");
    private final static JsonParameters PERSON_DEFAULTJSONPARAMETERS = JsonParameters.build("type", "standard");

    private FicheItemFormatterParser() {
    }

    public static FicheItemFormatter parse(CorpusField corpusField, String pattern, FormatContext formatContext, MessageHandler messageHandler) {
        String ficheItemType = corpusField.getFicheItemType();
        if (pattern == null) {
            pattern = DefaultPatternCatalog.ficheItem(ficheItemType);
        }
        try {
            Object[] partArray = FormatterUtils.parsePattern(new CorpusFieldInstructionResolver(formatContext, corpusField), pattern);
            return new InternalFicheItemFormatter(partArray);
        } catch (ErrorMessageException fe) {
            messageHandler.addMessage(FormatConstants.SEVERE_PATTERN, fe.getErrorMessage());
            return null;
        }
    }

    public static FicheItemFormatter parse(String ficheItemType, String pattern, FormatContext formatContext, MessageHandler messageHandler) {
        if (pattern == null) {
            pattern = DefaultPatternCatalog.ficheItem(ficheItemType);
        }
        try {
            Object[] partArray = FormatterUtils.parsePattern(new ItemTypeInstructionResolver(formatContext, ficheItemType), pattern);
            return new InternalFicheItemFormatter(partArray);
        } catch (ErrorMessageException fe) {
            messageHandler.addMessage(FormatConstants.SEVERE_PATTERN, fe.getErrorMessage());
            return null;
        }
    }

    public static FicheItemFormatter resolveFicheItem(Instruction instruction, String ficheItemType, FormatContext formatContext, CorpusField corpusField) throws ErrorMessageException {
        Argument arg1 = instruction.get(0);
        String key = arg1.getKey();
        if (key.equals("formsyntax")) {
            return FicheItemFormatterCatalog.FORMSYNTAX;
        } else if (key.equals("json")) {
            JsonParameters jsonParameters = JsonParameters.fromInstruction(instruction, getDefaultJsonParameters(ficheItemType));
            LangParameters langParameters = LangParameters.fromInstruction(instruction, "langs");
            return new FicheItemFormatterCatalog.Json(jsonParameters, langParameters.getCustomLangContext());
        }
        switch (ficheItemType) {
            case CorpusField.LANGUAGE_FIELD:
                switch (key) {
                    case "code":
                        return FicheItemFormatterCatalog.LANGUAGE_CODE;
                    case "label":
                    case "lib":
                        return new FicheItemFormatterCatalog.LanguageLabel(LangParameters.fromInstruction(instruction, key));
                    default:
                        return null;
                }
            case CorpusField.COUNTRY_FIELD:
                switch (key) {
                    case "code":
                        return FicheItemFormatterCatalog.COUNTRY_CODE;
                    case "label":
                    case "lib":
                        return new FicheItemFormatterCatalog.CountryLabel(LangParameters.fromInstruction(instruction, key));
                    default:
                        return null;
                }
            case CorpusField.ITEM_FIELD:
                switch (key) {
                    case "value":
                        return FicheItemFormatterCatalog.ITEM;
                    default:
                        return null;
                }
            case CorpusField.PARA_FIELD:
                switch (key) {
                    case "raw":
                        return FicheItemFormatterCatalog.PARA_RAW;
                    case "transformation":
                        String transformationName = arg1.getValue();
                        FicheBlockFormatter ficheBlockFormatter = formatContext.getFicheBlockFormatter(transformationName, instruction.toOptionMap(1));
                        if (ficheBlockFormatter == null) {
                            throw new ErrorMessageException("_ error.unknown.template", transformationName);
                        }
                        return new FicheItemFormatterCatalog.ParaTransformation(ficheBlockFormatter);
                    default:
                        return null;
                }
            case CorpusField.GEOPOINT_FIELD:
                switch (key) {
                    case "lat":
                        return FicheItemFormatterCatalog.GEO_LAT;
                    case "latlabel":
                    case "latlib":
                        return new FicheItemFormatterCatalog.GeoLatLabel(LangParameters.fromInstruction(instruction, key));
                    case "lon":
                        return FicheItemFormatterCatalog.GEO_LON;
                    case "lonlabel":
                    case "lonlib":
                        return new FicheItemFormatterCatalog.GeoLonLabel(LangParameters.fromInstruction(instruction, key));
                    default:
                        return null;
                }
            case CorpusField.LINK_FIELD:
                switch (key) {
                    case "href":
                        String base = getBaseValue(instruction, corpusField);
                        if (base == null) {
                            return FicheItemFormatterCatalog.LINK_HREF;
                        } else {
                            return new FicheItemFormatterCatalog.LinkHref(base);
                        }
                    case "title":
                        return FicheItemFormatterCatalog.LINK_TITLE;
                    case "comment":
                        return FicheItemFormatterCatalog.LINK_COMMENT;
                    default:
                        return null;
                }
            case CorpusField.IMAGE_FIELD:
                switch (key) {
                    case "src":
                        String base = getBaseValue(instruction, corpusField);
                        if (base == null) {
                            return FicheItemFormatterCatalog.IMAGE_SRC;
                        } else {
                            return new FicheItemFormatterCatalog.ImageSrc(getBaseValue(instruction, corpusField));
                        }
                    case "alt":
                        return FicheItemFormatterCatalog.IMAGE_ALT;
                    case "title":
                        return FicheItemFormatterCatalog.IMAGE_TITLE;
                    default:
                        return null;
                }
            case CorpusField.EMAIL_FIELD:
                switch (key) {
                    case "complete":
                    case "value":
                        return FicheItemFormatterCatalog.EMAIL_COMPLETE;
                    case "address":
                    case "addr":
                        return FicheItemFormatterCatalog.EMAIL_ADDRESS;
                    case "name":
                        return FicheItemFormatterCatalog.EMAIL_NAME;
                    default:
                        return null;
                }
            case CorpusField.NUMBER_FIELD:
                switch (key) {
                    case "code":
                        return FicheItemFormatterCatalog.NUMBER_CODE;
                    case "label":
                    case "lib":
                        return new FicheItemFormatterCatalog.NumberLabel(LangParameters.fromInstruction(instruction, key));
                    default:
                        return null;
                }
            case CorpusField.AMOUNT_FIELD:
                switch (key) {
                    case "code":
                        return FicheItemFormatterCatalog.AMOUNT_CODE;
                    case "currency":
                    case "cur":
                        return FicheItemFormatterCatalog.AMOUNT_CURRENCY;
                    case "label":
                    case "lib":
                        return new FicheItemFormatterCatalog.AmountLabel(LangParameters.fromInstruction(instruction, key), isForceSubunit(instruction));
                    case "decimal":
                    case "value":
                        return FicheItemFormatterCatalog.AMOUNT_DECIMAL;
                    case "long":
                        return FicheItemFormatterCatalog.AMOUNT_MONEYLONG;
                    default:
                        return null;
                }
            case CorpusField.DATE_FIELD:
                switch (key) {
                    case "code":
                        return FicheItemFormatterCatalog.DATE_CODE;
                    case "iso":
                        return FicheItemFormatterCatalog.DATE_ISO;
                    case "lastday":
                    case "iso_last":
                        return FicheItemFormatterCatalog.DATE_ISO_LAST;
                    case "year":
                    case "a":
                        return FicheItemFormatterCatalog.DATE_YEAR;
                    case "isomonth":
                    case "a-m":
                        return FicheItemFormatterCatalog.DATE_ISOMONTH;
                    case "lastmonth":
                    case "a-m_last":
                        return FicheItemFormatterCatalog.DATE_ISOMONTH_LAST;
                    case "label":
                    case "lib":
                        return new FicheItemFormatterCatalog.DateLabel(LangParameters.fromInstruction(instruction, key), getTruncateType(instruction), getFormatStyle(instruction));
                    case "monthlabel":
                    case "lib_m":
                        return new FicheItemFormatterCatalog.DateLabel(LangParameters.fromInstruction(instruction, key), FuzzyDate.MONTH_TYPE, getFormatStyle(instruction));
                    case "pattern":
                        String value = arg1.getValue();
                        LangParameters langParameters = null;
                        if (instruction.hasArgument("lang")) {
                            langParameters = LangParameters.fromInstruction(instruction, "lang");
                        }
                        if (value == null) {
                            return new FicheItemFormatterCatalog.DatePattern(DateTimeFormatter.BASIC_ISO_DATE, langParameters);
                        } else {
                            try {
                                return new FicheItemFormatterCatalog.DatePattern(DateTimeFormatter.ofPattern(value), langParameters);
                            } catch (IllegalArgumentException iae) {
                                throw new ErrorMessageException("_ error.wrong.pattern", value);
                            }
                        }

                    default:
                        return null;
                }
            case CorpusField.PERSON_FIELD:
                switch (key) {
                    case "code":
                        return FicheItemFormatterCatalog.PERSON_CODE;
                    case "sphere":
                        return FicheItemFormatterCatalog.PERSON_SPHERE;
                    case "login":
                        return FicheItemFormatterCatalog.PERSON_LOGIN;
                    case "standard":
                    case "normal":
                        return FicheItemFormatterCatalog.PERSON_STANDARD;
                    case "directory":
                    case "annu":
                        return FicheItemFormatterCatalog.PERSON_DIRECTORY;
                    case "updirectory":
                    case "annu_up":
                        return FicheItemFormatterCatalog.PERSON_UPDIRECTORY;
                    case "biblio":
                        return FicheItemFormatterCatalog.PERSON_BIBLIO;
                    case "upbiblio":
                    case "biblio_up":
                        return FicheItemFormatterCatalog.PERSON_UPBIBLIO;
                    case "surname":
                    case "nom":
                        return FicheItemFormatterCatalog.PERSON_SURNAME;
                    case "upsurname":
                    case "nom_up":
                        return FicheItemFormatterCatalog.PERSON_UPSURNAME;
                    case "forename":
                    case "prenom":
                        return FicheItemFormatterCatalog.PERSON_FORENAME;
                    case "nonlatin":
                    case "original":
                        return FicheItemFormatterCatalog.PERSON_NONLATIN;
                    case "surnamefirst":
                    case "nomavant":
                        return FicheItemFormatterCatalog.PERSON_SURNAMEFIRST;
                    case "organism":
                    case "organisme":
                        return FicheItemFormatterCatalog.PERSON_ORGANISM;
                    default:
                        return null;

                }
            default:
                throw new SwitchException("ficheItemType = " + ficheItemType);
        }
    }

    private static JsonParameters getDefaultJsonParameters(String ficheItemType) {
        switch (ficheItemType) {
            case CorpusField.AMOUNT_FIELD:
                return AMOUNT_DEFAULTJSONPARAMETERS;
            case CorpusField.COUNTRY_FIELD:
                return COUNTRY_DEFAULTJSONPARAMETERS;
            case CorpusField.DATE_FIELD:
                return DATE_DEFAULTJSONPARAMETERS;
            case CorpusField.EMAIL_FIELD:
                return EMAIL_DEFAULTJSONPARAMETERS;
            case CorpusField.GEOPOINT_FIELD:
                return GEOPOINT_DEFAULTJSONPARAMETERS;
            case CorpusField.LANGUAGE_FIELD:
                return LANGUAGE_DEFAULTJSONPARAMETERS;
            case CorpusField.LINK_FIELD:
                return LINK_DEFAULTJSONPARAMETERS;
            case CorpusField.IMAGE_FIELD:
                return IMAGE_DEFAULTJSONPARAMETERS;
            case CorpusField.ITEM_FIELD:
                return ITEM_DEFAULTJSONPARAMETERS;
            case CorpusField.NUMBER_FIELD:
                return NUMBER_DEFAULTJSONPARAMETERS;
            case CorpusField.PARA_FIELD:
                return PARA_DEFAULTJSONPARAMETERS;
            case CorpusField.PERSON_FIELD:
                return PERSON_DEFAULTJSONPARAMETERS;
            default:
                throw new SwitchException("ficheItemType = " + ficheItemType);
        }
    }

    private static String getBaseValue(Instruction instruction, CorpusField corpusField) {
        String baseValue = instruction.getArgumentValue("base");
        if ((baseValue != null) && (!baseValue.isEmpty())) {
            return baseValue;
        }
        if (corpusField == null) {
            return null;
        }
        String defaultBase = corpusField.getStringOption(FieldOptionConstants.BASEURL_OPTION);
        if (defaultBase != null) {
            return defaultBase;
        }
        return null;
    }

    private static FormatStyle getFormatStyle(Instruction instruction) {
        String dateStyle = instruction.getArgumentValue(FormatConstants.DATESTYLE_PARAMKEY);
        FormatStyle formatStyle = null;
        if (dateStyle != null) {
            formatStyle = FormatConstants.getMatchingFormatStyle(dateStyle);
        }
        if (formatStyle != null) {
            return formatStyle;
        } else {
            return FormatStyle.LONG;
        }
    }

    private static String getTruncateType(Instruction instruction) {
        String truncateType = instruction.getArgumentValue("truncate");
        if ((truncateType == null) || (truncateType.isEmpty())) {
            return FuzzyDate.DAY_TYPE;
        } else {
            return truncateType;
        }
    }

    private static boolean isForceSubunit(Instruction instruction) {
        String subunitValue = instruction.getArgumentValue("subunit");
        if (subunitValue == null) {
            return false;
        }
        if ((subunitValue.isEmpty()) || (subunitValue.equals(FormatConstants.FORCE_AMOUNTSUBUNIT))) {
            return true;
        } else {
            return false;
        }
    }


    private static class ItemTypeInstructionResolver implements InstructionResolver {

        private final FormatContext formatContext;
        private final String ficheItemType;

        private ItemTypeInstructionResolver(FormatContext formatContext, String ficheItemType) {
            this.formatContext = formatContext;
            this.ficheItemType = ficheItemType;
        }

        @Override
        public Object resolve(Instruction instruction) throws ErrorMessageException {
            InstructionResolver resolver = formatContext.getInstructionResolverProvider().getInstructionResolver(FicheItemFormatter.class, ficheItemType);
            if (resolver != null) {
                Object formatter = resolver.resolve(instruction);
                if (formatter != null) {
                    return formatter;
                }
            }
            return resolveFicheItem(instruction, ficheItemType, formatContext, null);
        }

    }


    private static class CorpusFieldInstructionResolver implements InstructionResolver {

        private final FormatContext formatContext;
        private final CorpusField corpusField;

        private CorpusFieldInstructionResolver(FormatContext formatContext, CorpusField corpusField) {
            this.formatContext = formatContext;
            this.corpusField = corpusField;
        }

        @Override
        public Object resolve(Instruction instruction) throws ErrorMessageException {
            InstructionResolver resolver = formatContext.getInstructionResolverProvider().getInstructionResolver(FicheItemFormatter.class, corpusField);
            if (resolver != null) {
                Object formatter = resolver.resolve(instruction);
                if (formatter != null) {
                    return formatter;
                }
            }
            return resolveFicheItem(instruction, corpusField.getFicheItemType(), formatContext, corpusField);
        }

    }


    private static class InternalFicheItemFormatter implements FicheItemFormatter {

        private final Object[] partArray;

        private InternalFicheItemFormatter(Object[] partArray) {
            this.partArray = partArray;
        }

        @Override
        public String formatFicheItem(FicheItem ficheItem, FormatSource formatSource) {
            StringBuilder buf = new StringBuilder();
            int length = partArray.length;
            for (int i = 0; i < length; i++) {
                Object obj = partArray[i];
                if (obj instanceof String) {
                    buf.append((String) obj);
                    continue;
                }
                FicheItemFormatter formatter = (FicheItemFormatter) obj;
                buf.append(formatter.formatFicheItem(ficheItem, formatSource));
            }
            return buf.toString();
        }

    }

}
