/* FichothequeLib_API - Copyright (c) 2006-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.corpus.fiche;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.fichotheque.corpus.metadata.FieldKey;
import net.mapeadores.util.localisation.Lang;
import net.mapeadores.util.text.StringUtils;


/**
 *
 * @author Vincent Calame
 */
public class Fiche implements FicheAPI, Serializable {

    private static final long serialVersionUID = 14L;
    private static final List<Prop> EMPTY_PROPLIST = Collections.emptyList();
    private static final List<Info> EMPTY_INFOLIST = Collections.emptyList();
    private static final List<Section> EMPTY_SECTIONLIST = Collections.emptyList();
    private final Map<String, Object> fieldMap = new HashMap<String, Object>();
    private ArrayList<Info> infoList;
    private ArrayList<Prop> propList;
    private ArrayList<Section> sectionList;
    private FicheItemsImpl owners = null;
    private String title = "";
    private ParaItem subtitle = null;
    private Lang lang = null;

    public Fiche() {
    }

    public void setLang(Lang lang) {
        this.lang = lang;
    }

    @Override
    public Lang getLang() {
        return lang;
    }

    /**
     * Ne retourne jamais <em>null</em>
     */
    @Override
    public String getTitle() {
        return title;
    }

    /**
     *
     * @throws IllegalArgumentException si titre est nul
     */
    public void setTitle(String title) {
        if (title == null) {
            throw new IllegalArgumentException("argument is null");
        }
        this.title = StringUtils.cleanString(title);
    }

    /**
     * Peut retourner <em>null</em>
     */
    @Override
    public ParaItem getSubtitle() {
        return subtitle;
    }

    /**
     *
     * @throws IllegalArgumentException si subtitlePara est nul
     */
    public void setSubtitle(ParaItem subtitlePara) {
        if ((subtitlePara == null) || (subtitlePara.isEmpty())) {
            this.subtitle = null;
        } else {
            this.subtitle = subtitlePara;
        }
    }

    /**
     * <em>null</em> ou non vide
     */
    @Override
    public FicheItems getOwners() {
        return owners;
    }

    @Override
    public List<Prop> getPropList() {
        if (propList == null) {
            return EMPTY_PROPLIST;
        }
        return propList;
    }

    @Override
    public List<Info> getInfoList() {
        if (infoList == null) {
            return EMPTY_INFOLIST;
        }
        return infoList;
    }

    @Override
    public List<Section> getSectionList() {
        if (sectionList == null) {
            return EMPTY_SECTIONLIST;
        }
        return sectionList;
    }

    public void appendOwners(FicheItems ficheItems) {
        setOwners(ficheItems, true);
    }

    public void setOwners(FicheItems ficheItems) {
        setOwners(ficheItems, false);
    }

    private void setOwners(FicheItems ficheItems, boolean append) {
        if ((ficheItems != null) && (ficheItems.isEmpty())) {
            ficheItems = null;
        }
        if (ficheItems == null) {
            if (!append) {
                owners = null;
            }
            return;
        }
        if (owners == null) {
            owners = new FicheItemsImpl();
        } else if (!append) {
            owners.clear();
        }
        for (FicheItem ficheItem : ficheItems) {
            if ((ficheItem instanceof Item) || (ficheItem instanceof PersonItem)) {
                owners.add(ficheItem);
            }
        }
        if (owners.isEmpty()) {
            owners = null;
        }
    }

    /**
     *
     * @throws IllegalArgumentException si fieldKey.isInfo() == false
     */
    @Override
    public Info getInfo(FieldKey fieldKey) {
        if (!fieldKey.isInfo()) {
            throw new IllegalArgumentException("fieldKey is not an Info FieldKey");
        }
        return (Info) getField(fieldKey);
    }

    /**
     *
     * @throws IllegalArgumentException si fieldKey.isProp() == false
     */
    @Override
    public Prop getProp(FieldKey fieldKey) {
        if (!fieldKey.isProp()) {
            throw new IllegalArgumentException("fieldKey is not an Prop FieldKey");
        }
        return (Prop) getField(fieldKey);
    }

    /**
     *
     * @throws IllegalArgumentException si fieldKey.isSectionFieldKey() == false
     */
    @Override
    public Section getSection(FieldKey fieldKey) {
        if (!fieldKey.isSection()) {
            throw new IllegalArgumentException("fieldKey is not an SectionFieldKey");
        }
        return (Section) getField(fieldKey);
    }

    public void appendInfo(FieldKey fieldKey, FicheItems ficheItems) {
        setInfo(fieldKey, ficheItems, true);
    }

    public void setInfo(FieldKey fieldKey, FicheItems ficheItems) {
        setInfo(fieldKey, ficheItems, false);
    }

    private void setInfo(FieldKey fieldKey, FicheItems ficheItems, boolean append) {
        if (!fieldKey.isInfo()) {
            throw new IllegalArgumentException("Not a Info FieldKey");
        }
        if ((ficheItems != null) && (ficheItems.isEmpty())) {
            ficheItems = null;
        }
        InternalInfo current = (InternalInfo) getField(fieldKey);
        if (current == null) {
            if (ficheItems == null) {
                return;
            }
            if (infoList == null) {
                infoList = new ArrayList<Info>();
            }
            InternalInfo info = new InternalInfo(fieldKey);
            info.addFicheItems(ficheItems);
            putField(fieldKey, info);
            infoList.add(info);
        } else {
            if (ficheItems == null) {
                if (!append) {
                    removeField(fieldKey);
                    infoList.remove(current);
                }
            } else {
                if (!append) {
                    current.clear();
                }
                current.addFicheItems(ficheItems);
            }
        }
    }

    public void appendSection(FieldKey fieldKey, FicheBlocks ficheBlocks) {
        setSection(fieldKey, ficheBlocks, true);
    }

    public void setSection(FieldKey fieldKey, FicheBlocks ficheBlocks) {
        setSection(fieldKey, ficheBlocks, false);
    }

    private void setSection(FieldKey fieldKey, FicheBlocks ficheBlocks, boolean append) {
        if (!fieldKey.isSection()) {
            throw new IllegalArgumentException("Not a Section FieldKey");
        }
        if ((ficheBlocks != null) && (ficheBlocks.isEmpty())) {
            ficheBlocks = null;
        }
        InternalSection current = (InternalSection) getField(fieldKey);
        if (current == null) {
            if (ficheBlocks == null) {
                return;
            }
            if (sectionList == null) {
                sectionList = new ArrayList<Section>();
            }
            InternalSection section = new InternalSection(fieldKey);
            section.addAll(ficheBlocks);
            putField(fieldKey, section);
            sectionList.add(section);
        } else {
            if (ficheBlocks == null) {
                if (!append) {
                    removeField(fieldKey);
                    sectionList.remove(current);
                }
            } else {
                if (!append) {
                    current.clear();
                }
                current.addAll(ficheBlocks);
            }
        }
    }

    public void setProp(FieldKey fieldKey, FicheItem ficheItem) {
        if (!fieldKey.isProp()) {
            throw new IllegalArgumentException("Not a Prop FieldKey");
        }
        InternalProp current = (InternalProp) getField(fieldKey);
        if (current == null) {
            if (ficheItem == null) {
                return;
            }
            if (propList == null) {
                propList = new ArrayList<Prop>();
            }
            InternalProp prop = new InternalProp(fieldKey);
            prop.setFicheItem(ficheItem);
            putField(fieldKey, prop);
            propList.add(prop);
        } else {
            if (ficheItem == null) {
                removeField(fieldKey);
                propList.remove(current);
            } else {
                current.setFicheItem(ficheItem);
            }
        }
    }

    public void clear() {
        title = "";
        subtitle = null;
        owners = null;
        if (infoList != null) {
            infoList.clear();
            infoList = null;
        }
        if (propList != null) {
            propList.clear();
            propList = null;
        }
        if (sectionList != null) {
            sectionList.clear();
            sectionList = null;
        }
        clearFieldMap();
    }

    private Object getField(FieldKey fieldKey) {
        return fieldMap.get(fieldKey.getKeyString());
    }

    private void putField(FieldKey fieldKey, Object obj) {
        fieldMap.put(fieldKey.getKeyString(), obj);
    }

    private void removeField(FieldKey fieldKey) {
        fieldMap.remove(fieldKey.getKeyString());
    }

    private void clearFieldMap() {
        fieldMap.clear();
    }


    private static class InternalProp implements Prop, Serializable {

        private final FieldKey fieldKey;
        private FicheItem item;

        private InternalProp(FieldKey fieldKey) {
            this.fieldKey = fieldKey;
        }

        @Override
        public FieldKey getFieldKey() {
            return fieldKey;
        }

        @Override
        public FicheItem getFicheItem() {
            return item;
        }

        private void setFicheItem(FicheItem ficheitem) {
            this.item = ficheitem;
        }

    }


    private static class InternalInfo extends FicheItemsImpl implements Info, Serializable {

        private final FieldKey fieldKey;

        private InternalInfo(FieldKey fieldKey) {
            this.fieldKey = fieldKey;
        }

        @Override
        public FieldKey getFieldKey() {
            return fieldKey;
        }

    }


    private static class InternalSection extends FicheBlocksImpl implements Section, Serializable {

        private final FieldKey fieldKey;

        private InternalSection(FieldKey fieldKey) {
            this.fieldKey = fieldKey;
        }

        @Override
        public FieldKey getFieldKey() {
            return fieldKey;
        }

    }

}
