/* FichothequeLib_Xml - Copyright (c) 2009-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.xml.extraction;

import java.io.IOException;
import java.text.DecimalFormatSymbols;
import java.time.format.FormatStyle;
import java.util.Locale;
import net.fichotheque.SubsetKey;
import net.fichotheque.corpus.fiche.AmountItem;
import net.fichotheque.corpus.fiche.CountryItem;
import net.fichotheque.corpus.fiche.DateItem;
import net.fichotheque.corpus.fiche.EmailItem;
import net.fichotheque.corpus.fiche.FicheItem;
import net.fichotheque.corpus.fiche.FicheItems;
import net.fichotheque.corpus.fiche.GeopointItem;
import net.fichotheque.corpus.fiche.ImageItem;
import net.fichotheque.corpus.fiche.Item;
import net.fichotheque.corpus.fiche.LanguageItem;
import net.fichotheque.corpus.fiche.LinkItem;
import net.fichotheque.corpus.fiche.NumberItem;
import net.fichotheque.corpus.fiche.ParaItem;
import net.fichotheque.corpus.fiche.PersonItem;
import net.fichotheque.extraction.ExtractParameters;
import net.fichotheque.extraction.ExtractionConstants;
import net.fichotheque.extraction.ExtractionContext;
import net.fichotheque.extraction.IrefConverter;
import net.fichotheque.sphere.Redacteur;
import net.fichotheque.sphere.Sphere;
import net.fichotheque.utils.SphereUtils;
import net.mapeadores.util.date.FuzzyDate;
import net.mapeadores.util.date.FuzzyDateFormatter;
import net.mapeadores.util.localisation.Lang;
import net.mapeadores.util.localisation.LangContext;
import net.mapeadores.util.localisation.ListLangContext;
import net.mapeadores.util.localisation.SpecialCodes;
import net.mapeadores.util.localisation.UserLangContext;
import net.mapeadores.util.models.EmailCore;
import net.mapeadores.util.models.PersonCore;
import net.mapeadores.util.money.ExtendedCurrency;
import net.mapeadores.util.money.MoneyUtils;
import net.mapeadores.util.primitives.Decimal;
import net.mapeadores.util.primitives.DegreDecimal;
import net.mapeadores.util.primitives.DegreSexagesimal;
import net.mapeadores.util.text.LabelUtils;
import net.mapeadores.util.text.StringUtils;
import net.mapeadores.util.xml.XMLPart;
import net.mapeadores.util.xml.XMLWriter;


/**
 *
 * @author Vincent Calame
 */
public class FicheItemXMLPart extends XMLPart {

    private final ExtractParameters extractParameters;
    private final FicheBlockXMLPart ficheBlockXMLPart;
    private final ExtractionContext extractionContext;
    private final int extractVersion;
    private final TagNames tagNames;

    public FicheItemXMLPart(XMLWriter xmlWriter, ExtractParameters extractParameters) {
        super(xmlWriter);
        this.extractParameters = extractParameters;
        this.extractionContext = extractParameters.getExtractionContext();
        this.extractVersion = extractParameters.getExtractVersion();
        ficheBlockXMLPart = new FicheBlockXMLPart(xmlWriter, extractParameters);
        this.tagNames = new TagNames(extractVersion);
    }

    public void addFicheItems(FicheItems ficheItems, ExtractOptions extractOptions, IrefConverter irefConverter) throws IOException {
        int size = ficheItems.size();
        for (int i = 0; i < size; i++) {
            addFicheItem(ficheItems.get(i), extractOptions, irefConverter);
        }
    }

    public void addFicheItem(FicheItem ficheItem, ExtractOptions extractOptions, IrefConverter irefConverter) throws IOException {
        if (ficheItem instanceof Item) {
            addItem((Item) ficheItem);
        } else if (ficheItem instanceof PersonItem) {
            addPerson((PersonItem) ficheItem);
        } else if (ficheItem instanceof DateItem) {
            addDate((DateItem) ficheItem, extractOptions);
        } else if (ficheItem instanceof LanguageItem) {
            addLanguage((LanguageItem) ficheItem);
        } else if (ficheItem instanceof CountryItem) {
            addCountry((CountryItem) ficheItem);
        } else if (ficheItem instanceof LinkItem) {
            addLink((LinkItem) ficheItem, extractOptions);
        } else if (ficheItem instanceof EmailItem) {
            addEmail((EmailItem) ficheItem);
        } else if (ficheItem instanceof AmountItem) {
            addAmount((AmountItem) ficheItem, extractOptions);
        } else if (ficheItem instanceof NumberItem) {
            addNumber((NumberItem) ficheItem);
        } else if (ficheItem instanceof GeopointItem) {
            addGeopoint((GeopointItem) ficheItem);
        } else if (ficheItem instanceof ParaItem) {
            addPara((ParaItem) ficheItem, irefConverter);
        } else if (ficheItem instanceof ImageItem) {
            addImage((ImageItem) ficheItem, extractOptions);
        } else {
            throw new ClassCastException("unknown instance of FicheItem = " + ficheItem.getClass().getName());
        }
    }

    public void addItem(Item item) throws IOException {
        addSimpleElement("item", item.getValue());
    }

    public void addEmail(EmailItem emailItem) throws IOException {
        String emailTagName = tagNames.getEmailTagName();
        EmailCore emailCore = emailItem.getEmailCore();
        String addrSpec = emailCore.getAddrSpec();
        startOpenTag(emailTagName);
        addAttribute("addr-spec", addrSpec);
        String realName = emailCore.getRealName();
        if (realName.length() > 0) {
            addAttribute("real-name", realName);
        }
        int idx = addrSpec.indexOf('@');
        addAttribute("user", addrSpec.substring(0, idx));
        addAttribute("domain", addrSpec.substring(idx + 1));
        endOpenTag();
        addText(emailItem.toString());
        closeTag(emailTagName, false);
    }

    public void addDate(DateItem dateItem, ExtractOptions extractOptions) throws IOException {
        String dateTagName = tagNames.getDateTagName();
        FuzzyDate date = dateItem.getDate();
        startOpenTag(dateTagName);
        if (extractVersion == ExtractionConstants.INITIAL_VERSION) {
            addAttribute("a", String.valueOf(date.getYear()));
            switch (date.getDateType()) {
                case FuzzyDate.HALFYEAR_TYPE:
                    addAttribute("type", "s");
                    addAttribute("s", String.valueOf(date.getHalfYear()));
                    break;
                case FuzzyDate.QUARTER_TYPE:
                    addAttribute("type", "t");
                    addAttribute("t", String.valueOf(date.getQuarter()));
                    break;
                case FuzzyDate.MONTH_TYPE:
                    addAttribute("type", "m");
                    addAttribute("m", String.valueOf(date.getMonth()));
                    break;
                case FuzzyDate.DAY_TYPE:
                    addAttribute("type", "j");
                    addAttribute("m", String.valueOf(date.getMonth()));
                    addAttribute("j", String.valueOf(date.getDay()));
                    break;
                default:
                    addAttribute("type", "a");
            }
        } else {
            addAttribute("y", String.valueOf(date.getYear()));
            switch (date.getDateType()) {
                case FuzzyDate.HALFYEAR_TYPE:
                    addAttribute("type", "s");
                    addAttribute("s", String.valueOf(date.getHalfYear()));
                    break;
                case FuzzyDate.QUARTER_TYPE:
                    addAttribute("type", "q");
                    addAttribute("q", String.valueOf(date.getQuarter()));
                    break;
                case FuzzyDate.MONTH_TYPE:
                    addAttribute("type", "m");
                    addAttribute("m", String.valueOf(date.getMonth()));
                    break;
                case FuzzyDate.DAY_TYPE:
                    addAttribute("type", "d");
                    addAttribute("m", String.valueOf(date.getMonth()));
                    addAttribute("d", String.valueOf(date.getDay()));
                    break;
                default:
                    addAttribute("type", "y");
            }
        }
        addAttribute("iso", date.toISOString());
        addAttribute("sort", date.toSortString());
        endOpenTag();
        addDateLabels(dateItem.getDate(), extractOptions);
        closeTag(dateTagName);
    }

    private void addDateLabels(FuzzyDate date, ExtractOptions extractOptions) throws IOException {
        FormatStyle formatStyle = null;
        if ((extractOptions != null) && (extractOptions instanceof ExtractOptions.Date)) {
            ExtractOptions.Date dateOptions = (ExtractOptions.Date) extractOptions;
            String truncateDateType = dateOptions.getTruncateDateType();
            if (truncateDateType != null) {
                date = date.truncate(truncateDateType);
            }
            formatStyle = dateOptions.getStyle();
        }
        if (formatStyle == null) {
            formatStyle = FormatStyle.LONG;
        }
        LangContext langContext = extractionContext.getLangContext();
        if (langContext instanceof ListLangContext) {
            for (ListLangContext.Unit unit : (ListLangContext) langContext) {
                Lang lang = unit.getLang();
                Locale formatLocale = unit.getFormatLocale();
                FuzzyDateFormatter fuzzyDateFormatter = FuzzyDateFormatter.build(formatStyle, formatLocale);
                LabelUtils.addLabel(this, lang, fuzzyDateFormatter.format(date));
            }
        } else if (langContext instanceof UserLangContext) {
            UserLangContext userLangContext = (UserLangContext) langContext;
            FuzzyDateFormatter fuzzyDateFormatter = FuzzyDateFormatter.build(formatStyle, userLangContext.getFormatLocale());
            LabelUtils.addLabel(this, null, fuzzyDateFormatter.format(date));
        }
    }

    public void addLanguage(LanguageItem language) throws IOException {
        String languageTagName = tagNames.getLanguageTagName();
        String code = language.getLang().toString();
        startOpenTag(languageTagName);
        addAttribute("lang", code);
        endOpenTag();
        addLangLabels(code);
        closeTag(languageTagName);
    }

    private void addLangLabels(String code) throws IOException {
        LangContext langContext = extractionContext.getLangContext();
        if (langContext instanceof ListLangContext) {
            for (ListLangContext.Unit unit : (ListLangContext) langContext) {
                Lang lang = unit.getLang();
                String message = extractionContext.getMessageLocalisationProvider().getMessageLocalisation(lang).toString(code);
                if (message != null) {
                    message = StringUtils.getFirstPart(message);
                    LabelUtils.addLabel(this, lang, message);
                }
            }
        } else if (langContext instanceof UserLangContext) {
            UserLangContext userLangContext = (UserLangContext) langContext;
            String message = extractionContext.getMessageLocalisationProvider().getMessageLocalisation(userLangContext).toString(code);
            if (message != null) {
                message = StringUtils.getFirstPart(message);
                LabelUtils.addLabel(this, null, message);
            }
        }
    }

    public void addNumber(NumberItem numberItem) throws IOException {
        String numberTagName = tagNames.getNumberTagName();
        startOpenTag(numberTagName);
        Decimal decimal = numberItem.getDecimal();
        addDecimalAttributes(decimal);
        endOpenTag();
        addNumberLabels(decimal);
        closeTag(numberTagName);
    }

    public void addGeopoint(GeopointItem geopoint) throws IOException {
        startOpenTag("geopoint");
        DegreDecimal latitude = geopoint.getLatitude();
        DegreDecimal longitude = geopoint.getLongitude();
        addAttribute("lat", latitude.toString());
        addAttribute("lon", longitude.toString());
        endOpenTag();
        DegreSexagesimal latitudeSexa = DegreSexagesimal.fromDegreDecimal(latitude);
        DegreSexagesimal longitudeSexa = DegreSexagesimal.fromDegreDecimal(longitude);
        startOpenTag("latitude");
        addSexagesimalAttributes(latitudeSexa);
        endOpenTag();
        addSexagesimalLabels(latitudeSexa, true);
        closeTag("latitude");
        startOpenTag("longitude");
        addSexagesimalAttributes(longitudeSexa);
        endOpenTag();
        addSexagesimalLabels(longitudeSexa, false);
        closeTag("longitude");
        closeTag("geopoint");
    }

    private void addSexagesimalAttributes(DegreSexagesimal sexa) throws IOException {
        addAttribute("deg", sexa.getDegre());
        addAttribute("mn", sexa.getMinute());
        addAttribute("sec", sexa.getSeconde());
    }

    private void addSexagesimalLabels(DegreSexagesimal sexa, boolean latitude) throws IOException {
        String code;
        if (latitude) {
            code = SpecialCodes.getLatitudeSpecialCode(sexa);
        } else {
            code = SpecialCodes.getLongitudeSpecialCode(sexa);
        }
        LangContext langContext = extractionContext.getLangContext();
        if (langContext instanceof ListLangContext) {
            for (ListLangContext.Unit unit : (ListLangContext) langContext) {
                Lang lang = unit.getLang();
                StringBuilder buf = new StringBuilder();
                buf.append(sexa.toString(false, " "));
                buf.append(" ");
                String message = extractionContext.getMessageLocalisationProvider().getMessageLocalisation(lang).toString(code);
                if (message != null) {
                    buf.append(message);
                } else {
                    buf.append(code);
                }
                LabelUtils.addLabel(this, lang, buf.toString());
            }
        } else if (langContext instanceof UserLangContext) {
            UserLangContext userLangContext = (UserLangContext) langContext;
            StringBuilder buf = new StringBuilder();
            buf.append(sexa.toString(false, " "));
            buf.append(" ");
            String message = extractionContext.getMessageLocalisationProvider().getMessageLocalisation(userLangContext).toString(code);
            if (message != null) {
                buf.append(message);
            } else {
                buf.append(code);
            }
            LabelUtils.addLabel(this, null, buf.toString());
        }
    }

    private void addNumberLabels(Decimal decimal) throws IOException {
        LangContext langContext = extractionContext.getLangContext();
        if (langContext instanceof ListLangContext) {
            for (ListLangContext.Unit unit : (ListLangContext) langContext) {
                Lang lang = unit.getLang();
                Locale formatLocale = unit.getFormatLocale();
                DecimalFormatSymbols symbols = new DecimalFormatSymbols(formatLocale);
                String strg = decimal.toString(symbols);
                LabelUtils.addLabel(this, lang, strg);
            }
        } else if (langContext instanceof UserLangContext) {
            UserLangContext userLangContext = (UserLangContext) langContext;
            Locale formatLocale = userLangContext.getFormatLocale();
            DecimalFormatSymbols symbols = new DecimalFormatSymbols(formatLocale);
            String strg = decimal.toString(symbols);
            LabelUtils.addLabel(this, null, strg);
        }
    }

    public void addAmount(AmountItem amountItem, ExtractOptions extractOptions) throws IOException {
        String amountTagName = tagNames.getAmountTagName();
        startOpenTag(amountTagName);
        Decimal decimal = amountItem.getDecimal();
        addDecimalAttributes(decimal);
        addAttribute("cur", amountItem.getCurrency().getCurrencyCode());
        addAttribute("money-long", String.valueOf(amountItem.toMoneyLong()));
        endOpenTag();
        addAmountLabels(decimal, amountItem.getCurrency(), amountItem.toMoneyLong(), extractOptions);
        closeTag(amountTagName);
    }

    private void addAmountLabels(Decimal decimal, ExtendedCurrency currency, long moneyLong, ExtractOptions extractOptions) throws IOException {
        boolean forceSubunit = false;
        if ((extractOptions != null) && (extractOptions instanceof ExtractOptions.Amount)) {
            ExtractOptions.Amount amountOptions = (ExtractOptions.Amount) extractOptions;
            forceSubunit = amountOptions.isForceSubunit();
        }
        LangContext langContext = extractionContext.getLangContext();
        if (langContext instanceof ListLangContext) {
            for (ListLangContext.Unit unit : (ListLangContext) langContext) {
                Lang lang = unit.getLang();
                Locale formatLocale = unit.getFormatLocale();
                DecimalFormatSymbols symbols = new DecimalFormatSymbols(formatLocale);
                String strg;
                if (forceSubunit) {
                    strg = MoneyUtils.toLitteralString(moneyLong, currency, symbols, false);
                } else {
                    strg = MoneyUtils.toLitteralString(decimal, currency, symbols);
                }
                LabelUtils.addLabel(this, lang, strg);
            }
        } else if (langContext instanceof UserLangContext) {
            UserLangContext userLangContext = (UserLangContext) langContext;
            Locale formatLocale = userLangContext.getFormatLocale();
            DecimalFormatSymbols symbols = new DecimalFormatSymbols(formatLocale);
            String strg;
            if (forceSubunit) {
                strg = MoneyUtils.toLitteralString(moneyLong, currency, symbols, false);
            } else {
                strg = MoneyUtils.toLitteralString(decimal, currency, symbols);
            }
            LabelUtils.addLabel(this, null, strg);
        }
    }

    private void addDecimalAttributes(Decimal decimal) throws IOException {
        String partieEntiere = decimal.getPartieEntiereString();
        String partieDecimale = decimal.getPartieDecimaleString();
        addAttribute("int", partieEntiere);
        addAttribute("dec", partieDecimale);
        String val = (partieDecimale.length() != 0) ? partieEntiere + "." + partieDecimale : partieEntiere;
        addAttribute("val", val);
    }

    public void addLink(LinkItem link, ExtractOptions extractOptions) throws IOException {
        startOpenTag("link");
        addAttribute("href", link.getHref());
        checkBaseUrl(extractOptions, link.getHref());
        endOpenTag();
        String title = link.getTitle();
        if (title.length() == 0) {
            title = link.getHref();
            if ((title.startsWith("http://")) && (title.length() > 8)) {
                title = title.substring(7);
            } else if ((title.startsWith("https://")) && (title.length() > 9)) {
                title = title.substring(8);
            }
        }
        addSimpleElement("title", title);
        addSimpleElement("comment", link.getComment());
        closeTag("link");
    }

    public void addImage(ImageItem image, ExtractOptions extractOptions) throws IOException {
        startOpenTag("image");
        addAttribute("src", image.getSrc());
        checkBaseUrl(extractOptions, image.getSrc());
        endOpenTag();
        addSimpleElement("alt", image.getAlt());
        addSimpleElement("title", image.getTitle());
        closeTag("image");
    }

    private void checkBaseUrl(ExtractOptions extractOptions, String ref) throws IOException {
        if ((extractOptions != null) && (extractOptions instanceof ExtractOptions.Ref)) {
            ExtractOptions.Ref refOptions = (ExtractOptions.Ref) extractOptions;
            String baseUrl = refOptions.getBaseUrl();
            if (baseUrl != null) {
                if (!StringUtils.isAbsoluteUrlString(ref)) {
                    addAttribute("base-url", baseUrl);
                }
            }
        }
    }

    public void addCountry(CountryItem countryItem) throws IOException {
        String countryTagName = tagNames.getCountryTagName();
        startOpenTag(countryTagName);
        addAttribute("country", countryItem.getCountry().toString());
        endOpenTag();
        addCountryLabels(countryItem.getCountry().toString());
        closeTag(countryTagName);
    }

    private void addCountryLabels(String countryCode) throws IOException {
        LangContext langContext = extractionContext.getLangContext();
        if (langContext instanceof ListLangContext) {
            for (ListLangContext.Unit unit : (ListLangContext) langContext) {
                Lang lang = unit.getLang();
                String message = extractionContext.getMessageLocalisationProvider().getMessageLocalisation(lang).toString(countryCode);
                if (message != null) {
                    message = StringUtils.getFirstPart(message);
                    LabelUtils.addLabel(this, lang, message);
                }
            }
        } else if (langContext instanceof UserLangContext) {
            UserLangContext userLangContext = (UserLangContext) langContext;
            String message = extractionContext.getMessageLocalisationProvider().getMessageLocalisation(userLangContext).toString(countryCode);
            if (message != null) {
                message = StringUtils.getFirstPart(message);
                LabelUtils.addLabel(this, null, message);
            }
        }
    }

    public void addPerson(PersonItem personItem) throws IOException {
        String personTagName = tagNames.getPersonTagName();
        startOpenTag(personTagName);
        String redacteurGlobalId = personItem.getRedacteurGlobalId();
        if (redacteurGlobalId != null) {
            try {
                SubsetKey sphereKey = SphereUtils.getSubsetKey(redacteurGlobalId);
                int id = SphereUtils.getId(redacteurGlobalId);
                addAttribute("sphere", sphereKey.getSubsetName());
                addAttribute("id", String.valueOf(id));
                Redacteur redacteur = null;
                Sphere sphere = (Sphere) extractionContext.getFichotheque().getSubset(sphereKey);
                if (sphere != null) {
                    redacteur = sphere.getRedacteurById(id);
                }
                if (redacteur != null) {
                    addAttribute("login", redacteur.getLogin());
                    endOpenTag();
                    addPersonCore(redacteur.getPersonCore());
                    closeTag(personTagName);
                } else {
                    closeEmptyTag();
                }
            } catch (java.text.ParseException pe) {
                closeEmptyTag();
            }
        } else {
            endOpenTag();
            addPersonCore(personItem.getPersonCore());
            addSimpleElement("organism", personItem.getOrganism());
            if (extractVersion == ExtractionConstants.INITIAL_VERSION) {
                addSimpleElement("organisme", personItem.getOrganism());
            }
            closeTag(personTagName);
        }
    }

    private void addPersonCore(PersonCore personCore) throws IOException {
        String surname = personCore.getSurname();
        boolean surnameFirst = personCore.isSurnameFirst();
        if (surname.length() > 0) {
            startOpenTag("surname");
            if (surnameFirst) {
                addAttribute("surname-first", "true");
            }
            endOpenTag();
            addText(surname);
            closeTag("surname", false);
        }
        addSimpleElement("forename", personCore.getForename());
        addSimpleElement("nonlatin", personCore.getNonlatin());
        if (extractVersion == ExtractionConstants.INITIAL_VERSION) {
            if (surname.length() > 0) {
                startOpenTag("nom");
                if (surnameFirst) {
                    addAttribute("avant", "true");
                }
                endOpenTag();
                addText(surname);
                closeTag("nom", false);
            }
            addSimpleElement("prenom", personCore.getForename());
            addSimpleElement("original", personCore.getNonlatin());
        }
    }

    public void addPara(ParaItem para, IrefConverter irefConverter) throws IOException {
        startOpenTag("para");
        endOpenTag();
        ficheBlockXMLPart.addTextContent(para, irefConverter);
        closeTag("para", false);
    }

}
