/* 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.formatters;

import java.time.format.DateTimeFormatter;
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.patterndefs.DefaultPattern;
import net.fichotheque.utils.FormatterUtils;
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;
import net.mapeadores.util.primitives.FuzzyDate;


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

    private final static JsonParameters CODE_DEFAULTJSONPARAMETERS = JsonParameters.parse("code");
    private final static JsonParameters LATLON_DEFAULTJSONPARAMETERS = JsonParameters.parse("lat,lon");
    private final static JsonParameters HREF_DEFAULTJSONPARAMETERS = JsonParameters.parse("href");
    private final static JsonParameters SRC_DEFAULTJSONPARAMETERS = JsonParameters.parse("src");
    private final static JsonParameters ADDRESS_DEFAULTJSONPARAMETERS = JsonParameters.parse("address");
    private final static JsonParameters RAW_DEFAULTJSONPARAMETERS = JsonParameters.parse("raw");
    private final static JsonParameters STANDARD_DEFAULTJSONPARAMETERS = JsonParameters.parse("standard");

    private FicheItemFormatterParser() {
    }

    public static FicheItemFormatter parse(CorpusField corpusField, String pattern, FormatContext formatContext, MessageHandler messageHandler) {
        String ficheItemType = corpusField.getFicheItemType();
        if (pattern == null) {
            pattern = DefaultPattern.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 = DefaultPattern.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 FicheItemFormatterParts.FORMSYNTAX_FORMATTER;
        } else if (key.equals("json")) {
            JsonParameters jsonParameters = JsonParameters.fromInstruction(instruction);
            if (jsonParameters == null) {
                jsonParameters = getDefaultJsonParameters(ficheItemType);
            }
            LangParameters langParameters = LangParameters.fromInstruction(instruction, "langs");
            return FicheItemFormatterParts.newJsonPart(jsonParameters, langParameters);
        }
        switch (ficheItemType) {
            case CorpusField.LANGUAGE_FIELD:
                switch (key) {
                    case "code":
                        return FicheItemFormatterParts.LANGUAGE_CODE_PART;
                    case "label":
                    case "lib":
                        return FicheItemFormatterParts.newLanguageLabelPart(LangParameters.fromInstruction(instruction, key));
                    default:
                        return null;
                }
            case CorpusField.COUNTRY_FIELD:
                switch (key) {
                    case "code":
                        return FicheItemFormatterParts.COUNTRY_CODE_PART;
                    case "label":
                    case "lib":
                        return FicheItemFormatterParts.newCountryLabelPart(LangParameters.fromInstruction(instruction, key));
                    default:
                        return null;
                }
            case CorpusField.ITEM_FIELD:
                switch (key) {
                    case "value":
                        return FicheItemFormatterParts.ITEM_PART;
                    default:
                        return null;
                }
            case CorpusField.PARA_FIELD:
                switch (key) {
                    case "raw":
                        return FicheItemFormatterParts.PARA_RAW_PART;
                    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 FicheItemFormatterParts.newParaTransformationPart(ficheBlockFormatter);
                    default:
                        return null;
                }
            case CorpusField.GEOPOINT_FIELD:
                switch (key) {
                    case "lat":
                        return FicheItemFormatterParts.GEO_LAT_PART;
                    case "latlabel":
                    case "latlib":
                        return FicheItemFormatterParts.newGeoLatLabelPart(LangParameters.fromInstruction(instruction, key));
                    case "lon":
                        return FicheItemFormatterParts.GEO_LON_PART;
                    case "lonlabel":
                    case "lonlib":
                        return FicheItemFormatterParts.newGeoLonLabelPart(LangParameters.fromInstruction(instruction, key));
                    default:
                        return null;
                }
            case CorpusField.LINK_FIELD:
                switch (key) {
                    case "href":
                        return FicheItemFormatterParts.newLinkHrefPart(getBaseValue(instruction, corpusField));
                    case "title":
                        return FicheItemFormatterParts.LINK_TITLE_PART;
                    case "comment":
                        return FicheItemFormatterParts.LINK_COMMENT_PART;
                    default:
                        return null;
                }
            case CorpusField.IMAGE_FIELD:
                switch (key) {
                    case "src":
                        return FicheItemFormatterParts.newImageSrcPart(getBaseValue(instruction, corpusField));
                    case "alt":
                        return FicheItemFormatterParts.IMAGE_ALT_PART;
                    case "title":
                        return FicheItemFormatterParts.IMAGE_TITLE_PART;
                    default:
                        return null;
                }
            case CorpusField.EMAIL_FIELD:
                switch (key) {
                    case "complete":
                    case "value":
                        return FicheItemFormatterParts.EMAIL_COMPLETE_PART;
                    case "address":
                    case "addr":
                        return FicheItemFormatterParts.EMAIL_ADDRESS_PART;
                    case "name":
                        return FicheItemFormatterParts.EMAIL_NAME_PART;
                    default:
                        return null;
                }
            case CorpusField.NUMBER_FIELD:
                switch (key) {
                    case "code":
                        return FicheItemFormatterParts.NUMBER_CODE_PART;
                    case "label":
                    case "lib":
                        return FicheItemFormatterParts.newNumberLabelPart(LangParameters.fromInstruction(instruction, key));
                    default:
                        return null;
                }
            case CorpusField.AMOUNT_FIELD:
                switch (key) {
                    case "code":
                        return FicheItemFormatterParts.AMOUNT_CODE_PART;
                    case "currency":
                    case "cur":
                        return FicheItemFormatterParts.AMOUNT_CURRENCY_PART;
                    case "label":
                    case "lib":
                        return FicheItemFormatterParts.newAmountLabelPart(LangParameters.fromInstruction(instruction, key));
                    case "decimal":
                    case "value":
                        return FicheItemFormatterParts.AMOUNT_DECIMAL_PART;
                    case "long":
                        return FicheItemFormatterParts.AMOUNT_MONEYLONG_PART;
                    default:
                        return null;
                }
            case CorpusField.DATE_FIELD:
                switch (key) {
                    case "code":
                        return FicheItemFormatterParts.DATE_CODE_PART;
                    case "iso":
                        return FicheItemFormatterParts.DATE_ISO_PART;
                    case "lastday":
                    case "iso_last":
                        return FicheItemFormatterParts.DATE_ISO_LAST_PART;
                    case "year":
                    case "a":
                        return FicheItemFormatterParts.DATE_YEAR_PART;
                    case "isomonth":
                    case "a-m":
                        return FicheItemFormatterParts.DATE_ISOMONTH_PART;
                    case "lastmonth":
                    case "a-m_last":
                        return FicheItemFormatterParts.DATE_ISOMONTH_LAST_PART;
                    case "label":
                    case "lib":
                        return FicheItemFormatterParts.newDateLabelPart(LangParameters.fromInstruction(instruction, key), FuzzyDate.DAY_TYPE);
                    case "monthlabel":
                    case "lib_m":
                        return FicheItemFormatterParts.newDateLabelPart(LangParameters.fromInstruction(instruction, key), FuzzyDate.MONTH_TYPE);
                    case "pattern":
                        String value = arg1.getValue();
                        if (value == null) {
                            return FicheItemFormatterParts.newDatePatternPart(DateTimeFormatter.BASIC_ISO_DATE);
                        } else {
                            try {
                                return FicheItemFormatterParts.newDatePatternPart(DateTimeFormatter.ofPattern(value));
                            } catch (IllegalArgumentException iae) {
                                throw new ErrorMessageException("_ error.wrong.pattern", value);
                            }
                        }

                    default:
                        return null;
                }
            case CorpusField.PERSON_FIELD:
                switch (key) {
                    case "code":
                        return FicheItemFormatterParts.PERSON_CODE_PART;
                    case "sphere":
                        return FicheItemFormatterParts.PERSON_SPHERE_PART;
                    case "login":
                        return FicheItemFormatterParts.PERSON_LOGIN_PART;
                    case "standard":
                    case "normal":
                        return FicheItemFormatterParts.PERSON_STANDARD_PART;
                    case "directory":
                    case "annu":
                        return FicheItemFormatterParts.PERSON_DIRECTORY_PART;
                    case "updirectory":
                    case "annu_up":
                        return FicheItemFormatterParts.PERSON_UPDIRECTORY_PART;
                    case "biblio":
                        return FicheItemFormatterParts.PERSON_BIBLIO_PART;
                    case "upbiblio":
                    case "biblio_up":
                        return FicheItemFormatterParts.PERSON_UPBIBLIO_PART;
                    case "surname":
                    case "nom":
                        return FicheItemFormatterParts.PERSON_SURNAME_PART;
                    case "upsurname":
                    case "nom_up":
                        return FicheItemFormatterParts.PERSON_UPSURNAME_PART;
                    case "forename":
                    case "prenom":
                        return FicheItemFormatterParts.PERSON_FORENAME_PART;
                    case "nonlatin":
                    case "original":
                        return FicheItemFormatterParts.PERSON_NONLATIN_PART;
                    case "surnamefirst":
                    case "nomavant":
                        return FicheItemFormatterParts.PERSON_SURNAMEFIRST_PART;
                    case "organism":
                    case "organisme":
                        return FicheItemFormatterParts.PERSON_ORGANISM_PART;
                    default:
                        return null;

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

    private static JsonParameters getDefaultJsonParameters(String ficheItemType) {
        switch (ficheItemType) {
            case CorpusField.PARA_FIELD:
                return RAW_DEFAULTJSONPARAMETERS;
            case CorpusField.GEOPOINT_FIELD:
                return LATLON_DEFAULTJSONPARAMETERS;
            case CorpusField.LINK_FIELD:
                return HREF_DEFAULTJSONPARAMETERS;
            case CorpusField.IMAGE_FIELD:
                return SRC_DEFAULTJSONPARAMETERS;
            case CorpusField.EMAIL_FIELD:
                return ADDRESS_DEFAULTJSONPARAMETERS;
            case CorpusField.ITEM_FIELD:
            case CorpusField.NUMBER_FIELD:
            case CorpusField.AMOUNT_FIELD:
            case CorpusField.DATE_FIELD:
            case CorpusField.COUNTRY_FIELD:
            case CorpusField.LANGUAGE_FIELD:
                return CODE_DEFAULTJSONPARAMETERS;
            case CorpusField.PERSON_FIELD:
                return STANDARD_DEFAULTJSONPARAMETERS;
            default:
                throw new SwitchException("ficheItemType = " + ficheItemType);
        }
    }

    private static String getBaseValue(Instruction instruction, CorpusField corpusField) {
        Argument baseArgument = null;
        for (Argument argument : instruction) {
            if (argument.getKey().equals("base")) {
                baseArgument = argument;
            }
        }
        if (baseArgument == null) {
            return null;
        }
        String baseValue = baseArgument.getValue();
        if (baseValue != null) {
            return baseValue;
        }
        if (corpusField == null) {
            return null;
        }
        String defaultBase = corpusField.getStringOption(FieldOptionConstants.BASEURL_OPTION);
        if (defaultBase != null) {
            return defaultBase;
        }
        return null;
    }


    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();
        }

    }

}
