/* FichothequeLib_Impl - Copyright (c) 2010-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.impl;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import net.fichotheque.EditOrigin;
import net.fichotheque.ExistingIdException;
import net.fichotheque.Fichotheque;
import net.fichotheque.FichothequeEditor;
import net.fichotheque.Metadata;
import net.fichotheque.Subset;
import net.fichotheque.SubsetItem;
import net.fichotheque.SubsetKey;
import net.fichotheque.album.Album;
import net.fichotheque.album.AlbumConstants;
import net.fichotheque.album.AlbumEditor;
import net.fichotheque.album.Illustration;
import net.fichotheque.album.metadata.AlbumDim;
import net.fichotheque.album.metadata.AlbumMetadataEditor;
import net.fichotheque.corpus.Corpus;
import net.fichotheque.utils.AlbumUtils;
import net.fichotheque.utils.FichothequeUtils;
import net.mapeadores.util.images.ImagesUtils;
import net.mapeadores.util.io.IOUtils;
import net.mapeadores.util.logging.ErrorMessageException;


/**
 *
 * @author Vincent Calame
 */
class AlbumImpl implements Album {

    private final SubsetKey albumKey;
    private final FichothequeImpl fichotheque;
    private final Map<Integer, IllustrationImpl> itemMap = new HashMap<Integer, IllustrationImpl>();
    private AlbumMetadataImpl albumMetadata;
    private int availableId = 1;
    private List<SubsetItem> cacheSubsetItemList;
    private List<Illustration> cacheList;

    private AlbumImpl(SubsetKey albumKey, FichothequeImpl fichotheque) {
        this.fichotheque = fichotheque;
        this.albumKey = albumKey;
    }

    static AlbumImpl.InitEditor fromInit(SubsetKey albumKey, FichothequeImpl fichotheque, FichothequeImpl.InitEditor fichothequeInitEditor) {
        AlbumImpl album = new AlbumImpl(albumKey, fichotheque);
        return new AlbumImpl.InitEditor(album, fichothequeInitEditor);
    }

    static AlbumImpl fromNew(SubsetKey albumKey, FichothequeImpl fichotheque) {
        AlbumImpl album = new AlbumImpl(albumKey, fichotheque);
        album.albumMetadata = AlbumMetadataImpl.fromNew(album);
        return album;
    }

    @Override
    public Fichotheque getFichotheque() {
        return fichotheque;
    }

    @Override
    public Metadata getMetadata() {
        return albumMetadata;
    }

    @Override
    public List<Illustration> getIllustrationList() {
        List<Illustration> result = cacheList;
        if (result == null) {
            result = initIllustrationList();
        }
        return result;
    }

    @Override
    public Illustration getIllustrationById(int id) {
        return itemMap.get(id);
    }

    @Override
    public SubsetKey getSubsetKey() {
        return albumKey;
    }

    @Override
    public SubsetItem getSubsetItemById(int id) {
        return itemMap.get(id);
    }

    @Override
    public List<SubsetItem> getSubsetItemList() {
        List<SubsetItem> list = cacheSubsetItemList;
        if (list == null) {
            list = initSubsetItemList();
        }
        return list;
    }

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

    @Override
    public List<Corpus> getSatelliteCorpusList() {
        return FichothequeUtils.EMPTY_CORPUSLIST;
    }

    AlbumEditorImpl getAlbumEditor(FichothequeEditor fichothequeEditor) {
        return new AlbumEditorImpl(fichothequeEditor);
    }

    void clear() {
        albumMetadata.clear();
    }

    private synchronized Illustration innerCreateIllustration(int id, short type) throws ExistingIdException {
        if (id < 1) {
            id = availableId;
        } else {
            if (itemMap.containsKey(id)) {
                throw new ExistingIdException();
            }
        }
        IllustrationImpl implIllustration = new IllustrationImpl(id, type);
        itemMap.put(id, implIllustration);
        availableId = Math.max(availableId, id + 1);
        clearCache();
        return implIllustration;
    }

    private void innerUpdateIllustration(Illustration illustration, InputStream inputStream, short type) throws IOException, ErrorMessageException {
        IllustrationImpl implIllustration = (IllustrationImpl) illustration;
        implIllustration.type = type;
        byte[] byteArray = IOUtils.toByteArray(inputStream);
        BufferedImage bufferedImage = ImagesUtils.read(new ByteArrayInputStream(byteArray));
        implIllustration.originalWidth = bufferedImage.getWidth();
        implIllustration.originalHeight = bufferedImage.getHeight();
        getAlbumDataSource().update(illustration, new ByteArrayInputStream(byteArray));
    }

    private synchronized boolean innerRemoveIllustration(Illustration illustration) {
        IllustrationImpl implIllustration = (IllustrationImpl) illustration;
        if (!fichotheque.isRemoveable(implIllustration)) {
            return false;
        }
        int id = illustration.getId();
        itemMap.remove(id);
        clearCache();
        return true;
    }

    private void clearCache() {
        cacheSubsetItemList = null;
        cacheList = null;
    }

    private synchronized List<SubsetItem> initSubsetItemList() {
        SortedMap<Integer, IllustrationImpl> sortedMap = new TreeMap<Integer, IllustrationImpl>();
        sortedMap.putAll(itemMap);
        List<SubsetItem> list = FichothequeUtils.wrap(sortedMap.values().toArray(new SubsetItem[sortedMap.size()]));
        cacheSubsetItemList = list;
        return list;
    }

    private synchronized List<Illustration> initIllustrationList() {
        SortedMap<Integer, IllustrationImpl> sortedMap = new TreeMap<Integer, IllustrationImpl>();
        sortedMap.putAll(itemMap);
        List<Illustration> list = AlbumUtils.wrap(sortedMap.values().toArray(new Illustration[sortedMap.size()]));
        cacheList = list;
        return list;
    }


    private AlbumDataSource getAlbumDataSource() {
        return fichotheque.getFichothequeDataSource().getAlbumDataSource();
    }


    static class InitEditor implements AlbumEditor {

        private final AlbumImpl album;
        private final FichothequeImpl.InitEditor fichothequeInitEditor;
        private final AlbumMetadataImpl.InitEditor metadataInitEditor;

        private InitEditor(AlbumImpl album, FichothequeImpl.InitEditor fichothequeInitEditor) {
            this.album = album;
            this.fichothequeInitEditor = fichothequeInitEditor;
            this.metadataInitEditor = AlbumMetadataImpl.fromInit(this);
            album.albumMetadata = (AlbumMetadataImpl) metadataInitEditor.getMetadata();
        }

        @Override
        public Album getAlbum() {
            return album;
        }

        @Override
        public FichothequeEditor getFichothequeEditor() {
            return fichothequeInitEditor;
        }

        @Override
        public AlbumMetadataEditor getAlbumMetadataEditor() {
            return metadataInitEditor;
        }

        @Override
        public Illustration createIllustration(int id, short type) throws ExistingIdException {
            return album.innerCreateIllustration(id, type);
        }

        @Override
        public void updateIllustration(Illustration illustration, InputStream inputStream, short type) throws IOException, ErrorMessageException {
            IllustrationImpl implIllustration = (IllustrationImpl) illustration;
            BufferedImage bufferedImage = ImagesUtils.read(inputStream);
            implIllustration.originalWidth = bufferedImage.getWidth();
            implIllustration.originalHeight = bufferedImage.getHeight();
        }

        @Override
        public boolean removeIllustration(Illustration illustration) {
            throw new UnsupportedOperationException("Not during init");
        }

    }


    class AlbumEditorImpl implements AlbumEditor {

        private final FichothequeEditor fichothequeEditor;
        private final Set<Integer> changedIllustrationSet = new HashSet<Integer>();
        private final Set<Integer> removedIllustrationSet = new HashSet<Integer>();
        private AlbumMetadataEditor albumMetadataEditor;
        private boolean metadataChanged = false;

        private AlbumEditorImpl(FichothequeEditor fichothequeEditor) {
            this.fichothequeEditor = fichothequeEditor;
        }

        @Override
        public Album getAlbum() {
            return AlbumImpl.this;
        }

        @Override
        public FichothequeEditor getFichothequeEditor() {
            return fichothequeEditor;
        }

        @Override
        public AlbumMetadataEditor getAlbumMetadataEditor() {
            if (albumMetadataEditor == null) {
                albumMetadataEditor = albumMetadata.getAlbumMetadataEditor(this);
            }
            return albumMetadataEditor;
        }

        @Override
        public Illustration createIllustration(int id, short type) throws ExistingIdException {
            return innerCreateIllustration(id, type);
        }

        @Override
        public void updateIllustration(Illustration illustration, InputStream inputStream, short type) throws IOException, ErrorMessageException {
            innerUpdateIllustration(illustration, inputStream, type);
        }

        @Override
        public boolean removeIllustration(Illustration illustration) {
            int illustrationid = illustration.getId();
            boolean done = innerRemoveIllustration(illustration);
            if (done) {
                removedIllustrationSet.add(illustrationid);
            }
            return done;
        }

        void addIllustrationChange(int id) {
            changedIllustrationSet.add(id);
        }

        void setMetadataChange() {
            metadataChanged = true;
        }

        void saveChanges() {
            EditOrigin editOrigin = fichothequeEditor.getEditOrigin();
            AlbumDataSource albumDataSource = getAlbumDataSource();
            if (metadataChanged) {
                albumDataSource.saveMetadata(AlbumImpl.this, editOrigin);
                metadataChanged = false;
            }
            for (Integer id : changedIllustrationSet) {
                Illustration illustration = getIllustrationById(id);
                if (illustration != null) {
                    albumDataSource.saveIllustration(illustration, editOrigin);
                }
            }
            changedIllustrationSet.clear();
            for (Integer id : removedIllustrationSet) {
                albumDataSource.removeIllustration(AlbumImpl.this, id, editOrigin);
            }
            removedIllustrationSet.clear();
        }

    }


    private class IllustrationImpl extends AbstractSubsetItem implements Illustration {

        private final int id;
        private short type;
        private int originalWidth;
        private int originalHeight;

        private IllustrationImpl(int id, short type) {
            this.id = id;
            this.type = type;
        }

        @Override
        public Subset getSubset() {
            return AlbumImpl.this;
        }

        @Override
        public int getId() {
            return id;
        }

        @Override
        public short getFormatType() {
            return type;
        }

        @Override
        public int getOriginalWidth() {
            return originalWidth;
        }

        @Override
        public int getOriginalHeight() {
            return originalHeight;
        }

        @Override
        public InputStream getInputStream(short specialDim) {
            switch (specialDim) {
                case AlbumConstants.ORIGINAL_SPECIALDIM:
                    return getAlbumDataSource().getInputStream(this, null, null);
                case AlbumConstants.MINI_SPECIALDIM:
                    return getAlbumDataSource().getInputStream(this, AlbumConstants.MINI_DIMNAME, albumMetadata.getResizeInfo(specialDim));
                default:
                    throw new IllegalArgumentException("Unknown specialDim: " + specialDim);

            }
        }

        @Override
        public InputStream getInputStream(AlbumDim albumDim) {
            if (!albumMetadata.isFromAlbum(albumDim)) {
                throw new IllegalArgumentException("albumDim is not linked to this Album");
            }
            return getAlbumDataSource().getInputStream(this, albumDim.getName(), albumDim.getResizeInfo());
        }

    }

}
