/* FichothequeLib_API - Copyright (c) 2013-2022 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.utils;

import java.util.AbstractList;
import java.util.Collection;
import java.util.RandomAccess;
import net.fichotheque.corpus.FicheMeta;
import net.fichotheque.corpus.fiche.AttConsumer;
import net.fichotheque.corpus.fiche.Atts;
import net.fichotheque.corpus.fiche.FicheBlock;
import net.fichotheque.corpus.fiche.FicheBlocks;
import net.fichotheque.corpus.fiche.FicheItem;
import net.fichotheque.corpus.fiche.FicheItems;
import net.fichotheque.corpus.metadata.FieldKey;
import net.mapeadores.util.annotation.Nullable;


/**
 * Utilitaire portant sur une fiche.
 *
 * @author Vincent Calame
 */
public final class FicheUtils {

    public final static FicheItems EMPTY_FICHEITEMS = new EmptyFicheItems();
    public final static FicheBlocks EMPTY_FICHEBLOCKS = new EmptyFicheBlocks();

    private FicheUtils() {
    }

    /**
     * Extrait la valeur du champ fieldKey pour la fiche décrite par FicheMeta.
     * C'est un raccourci à utiliser que si on est sûr de ne vouloir qu'une
     * seule valeur car cela charge la fiche à chaque appel.
     * <p>
     * Pour la valeur de retour, voir FicheUtils.getValue(FicheAPI fiche,
     * FieldKey fieldKey). La différence, c'est que si l'argument fieldKey est
     * égal à FieldKey.ID, la méthode ne renvoie pas une exception mais une
     * instance de Integer.
     *
     *
     * @param ficheMeta descriptif de la fiche sur laquelle on veut extraire une
     * valeur
     * @param fieldKey clé du champ à extraire
     * @return Valeur du champ, peut être nulle.
     */
    public static Object getValue(FicheMeta ficheMeta, FieldKey fieldKey) {
        if (fieldKey.isSection()) {
            return ficheMeta.getFicheAPI(true).getValue(fieldKey);
        } else {
            switch (fieldKey.getKeyString()) {
                case FieldKey.SPECIAL_ID:
                    return ficheMeta.getId();
                case FieldKey.SPECIAL_TITLE:
                    String title = ficheMeta.getTitle();
                    if (title.isEmpty()) {
                        return null;
                    } else {
                        return title;
                    }

                default:
                    return ficheMeta.getFicheAPI(false).getValue(fieldKey);
            }
        }
    }


    public static FicheItems toFicheItems(FicheItem ficheItem) {
        if (ficheItem == null) {
            return EMPTY_FICHEITEMS;
        } else {
            return new SingletonFicheItems(ficheItem);
        }
    }

    public static FicheItems toFicheItems(Collection<FicheItem> ficheItemCollection) {
        int size = ficheItemCollection.size();
        if (size == 0) {
            return EMPTY_FICHEITEMS;
        }
        FicheItem[] array = ficheItemCollection.toArray(new FicheItem[size]);
        if (size == 1) {
            return new SingletonFicheItems(array[0]);
        } else {
            return new ArrayFicheItems(array);
        }
    }

    public static FicheBlocks toFicheBlocks(FicheBlock ficheBlock) {
        if (ficheBlock == null) {
            return EMPTY_FICHEBLOCKS;
        } else {
            return new SingletonFicheBlocks(ficheBlock);
        }
    }

    public static FicheBlocks toFicheBlocks(Collection<FicheBlock> ficheBlockCollection) {
        int size = ficheBlockCollection.size();
        if (size == 0) {
            return EMPTY_FICHEBLOCKS;
        }
        FicheBlock[] array = ficheBlockCollection.toArray(new FicheBlock[size]);
        if (size == 1) {
            return new SingletonFicheBlocks(array[0]);
        } else {
            return new ArrayFicheBlocks(array);
        }
    }

    public static void populate(AttConsumer attConsumer, @Nullable Atts atts) {
        if (atts != null) {
            int attLength = atts.size();
            for (int i = 0; i < attLength; i++) {
                attConsumer.putAtt(atts.getName(i), atts.getValue(i));
            }
        }
    }


    private static class EmptyFicheItems extends AbstractList<FicheItem> implements FicheItems, RandomAccess {

        private EmptyFicheItems() {
        }

        @Override
        public int size() {
            return 0;
        }

        @Override
        public FicheItem get(int index) {
            throw new IndexOutOfBoundsException();
        }

    }


    private static class SingletonFicheItems extends AbstractList<FicheItem> implements FicheItems, RandomAccess {

        private final FicheItem ficheItem;

        private SingletonFicheItems(FicheItem ficheItem) {
            this.ficheItem = ficheItem;
        }

        @Override
        public int size() {
            return 1;
        }

        @Override
        public FicheItem get(int index) {
            if (index == 0) {
                return ficheItem;
            } else {
                throw new IndexOutOfBoundsException();
            }
        }

    }


    private static class ArrayFicheItems extends AbstractList<FicheItem> implements FicheItems, RandomAccess {

        private final FicheItem[] ficheItemArray;

        private ArrayFicheItems(FicheItem[] ficheItemArray) {
            this.ficheItemArray = ficheItemArray;
        }

        @Override
        public int size() {
            return ficheItemArray.length;
        }

        @Override
        public FicheItem get(int index) {
            return ficheItemArray[index];
        }

    }


    private static class EmptyFicheBlocks extends AbstractList<FicheBlock> implements FicheBlocks {

        private EmptyFicheBlocks() {
        }

        @Override
        public int size() {
            return 0;
        }

        @Override
        public FicheBlock get(int index) {
            throw new IndexOutOfBoundsException();
        }

    }


    private static class SingletonFicheBlocks extends AbstractList<FicheBlock> implements FicheBlocks {

        private final FicheBlock ficheBlock;

        private SingletonFicheBlocks(FicheBlock ficheBlock) {
            this.ficheBlock = ficheBlock;
        }

        @Override
        public int size() {
            return 1;
        }

        @Override
        public FicheBlock get(int index) {
            if (index == 0) {
                return ficheBlock;
            } else {
                throw new IndexOutOfBoundsException();
            }
        }

    }


    private static class ArrayFicheBlocks extends AbstractList<FicheBlock> implements FicheBlocks {

        private final FicheBlock[] array;

        private ArrayFicheBlocks(FicheBlock[] array) {
            this.array = array;
        }

        @Override
        public int size() {
            return array.length;
        }

        @Override
        public FicheBlock get(int index) {
            return array[index];
        }

    }

}
