/* BdfServer_DirectoryStorage - Copyright (c) 2010-2023 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 fr.exemole.bdfserver.storage.directory.jdbm;

import fr.exemole.bdfserver.api.storage.BdfStorageException;
import java.io.IOException;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import jdbm.RecordManager;
import net.fichotheque.corpus.Corpus;
import net.fichotheque.corpus.FicheMeta;
import net.fichotheque.corpus.fiche.Fiche;
import net.fichotheque.corpus.fiche.FicheAPI;


/**
 *
 * @author Vincent Calame
 */
public class FichothequeJdbm {

    private final static long JDBM_VERSION = 6L;
    private final static String FIELDKEY_CODER_NAME = "fieldkey_coder";
    private final static String SUBSETITEM_RECORDID_MAP_NAME = "subsetitem_recordid_map";
    private final FieldKeyCoder fieldKeyCoder;
    private final SubsetItemRecordIdMap subsetItemRecordIdMap;
    private final EnteteFicheSerializer enteteFicheSerializer;
    private final CorpsFicheSerializer corpsFicheSerializer;
    private RecordManager recordManager;
    private int commitCounter = 0;

    private FichothequeJdbm(RecordManager recordManager) throws IOException {
        this.recordManager = recordManager;
        fieldKeyCoder = FieldKeyCoder.init(recordManager, FIELDKEY_CODER_NAME);
        subsetItemRecordIdMap = SubsetItemRecordIdMap.init(recordManager, SUBSETITEM_RECORDID_MAP_NAME);
        enteteFicheSerializer = new EnteteFicheSerializer(fieldKeyCoder);
        corpsFicheSerializer = new CorpsFicheSerializer(fieldKeyCoder);
    }

    public void setRecordManager(RecordManager recordManager) {
        this.recordManager = recordManager;
    }

    public static FichothequeJdbm checkJdbmVersion(RecordManager recordManager) {
        try {
            long recordId = recordManager.getNamedObject("fichotheque_jdbm_version");
            if (recordId == 0) {
                return null;
            }
            Object obj = recordManager.fetch(recordId);
            if (!(obj instanceof Long)) {
                return null;
            }
            long l = ((Long) obj);
            if (l != JDBM_VERSION) {
                return null;
            }
            return new FichothequeJdbm(recordManager);
        } catch (IOException ioe) {
            throw new BdfStorageException(ioe);
        }
    }

    public static FichothequeJdbm initEmptyInstance(RecordManager recordManager) {
        try {
            long recordId = recordManager.insert(JDBM_VERSION);
            recordManager.setNamedObject("fichotheque_jdbm_version", recordId);
            recordManager.commit();
            return new FichothequeJdbm(recordManager);
        } catch (IOException ioe) {
            throw new BdfStorageException(ioe);
        }
    }

    public Fiche getFiche(FicheMeta ficheMeta, boolean ficheComplete) {
        try {
            if (ficheComplete) {
                return getFicheComplete(ficheMeta);
            } else {
                EnteteFiche enteteFiche = getEnteteFiche(ficheMeta);
                return enteteFiche.getFiche();
            }
        } catch (IOException ioe) {
            throw new BdfStorageException(ioe);
        }
    }

    public void removeFiche(Corpus corpus, int id) {
        try {
            SubsetItemRecordId recordId = subsetItemRecordIdMap.removeSubsetItem(corpus, id);
            if (recordId == null) {
                return;
            }
            recordManager.delete(recordId.getRecid1());
            if (recordId.getType() == SubsetItemRecordId.DOUBLE_TYPE) {
                recordManager.delete(recordId.getRecid2());
            }
            testIntermediateCommit();
        } catch (IOException ioe) {
            throw new BdfStorageException(ioe);
        }
    }

    public void updateFiche(FicheMeta ficheMeta, FicheAPI fiche) {
        int ficheid = ficheMeta.getId();
        EnteteFicheSerializer.Input enteteInput = new EnteteFicheSerializer.Input(ficheid, fiche);
        CorpsFicheSerializer.Input corpsInput = new CorpsFicheSerializer.Input(fiche);
        try {
            SubsetItemRecordId recordIdObject = subsetItemRecordIdMap.getRecordId(ficheMeta);
            if (recordIdObject == null) {
                long recid1 = recordManager.insert(enteteInput, enteteFicheSerializer);
                long recid2 = recordManager.insert(corpsInput, corpsFicheSerializer);
                subsetItemRecordIdMap.add(ficheMeta.getCorpus(), ficheid, recid1, recid2);
            } else {
                long recid1 = recordIdObject.getRecid1();
                recordManager.update(recid1, enteteInput, enteteFicheSerializer);
                long recid2 = recordIdObject.getRecid2();
                recordManager.update(recid2, corpsInput, corpsFicheSerializer);
            }
            testIntermediateCommit();
        } catch (IOException ioe) {
            throw new BdfStorageException(ioe);
        }
    }

    public void commitChanges() {
        try {
            if (subsetItemRecordIdMap.isWithChange()) {
                subsetItemRecordIdMap.save(recordManager, SUBSETITEM_RECORDID_MAP_NAME);
            }
            if (fieldKeyCoder.isWithChange()) {
                fieldKeyCoder.save(recordManager, FIELDKEY_CODER_NAME);
            }
            recordManager.commit();
        } catch (IOException ioe) {
            throw new BdfStorageException(ioe);
        }
    }

    private EnteteFiche getEnteteFiche(FicheMeta ficheMeta) throws IOException {
        SubsetItemRecordId recordId = subsetItemRecordIdMap.getRecordId(ficheMeta);
        if (recordId == null) {
            return null;
        }
        EnteteFiche enteteFiche = (EnteteFiche) recordManager.fetch(recordId.getRecid1(), enteteFicheSerializer);
        return enteteFiche;
    }

    private CorpsFiche getCorpsFiche(FicheMeta ficheMeta) throws IOException {
        SubsetItemRecordId recordId = subsetItemRecordIdMap.getRecordId(ficheMeta);
        if (recordId == null) {
            return null;
        }
        CorpsFiche corpsFiche = (CorpsFiche) recordManager.fetch(recordId.getRecid2(), corpsFicheSerializer);
        return corpsFiche;
    }

    private Fiche getFicheComplete(FicheMeta ficheMeta) throws IOException {
        SubsetItemRecordId recordId = subsetItemRecordIdMap.getRecordId(ficheMeta);
        if (recordId == null) {
            return null;
        }
        EnteteFiche enteteFiche = (EnteteFiche) recordManager.fetch(recordId.getRecid1(), enteteFicheSerializer);
        CorpsFiche corpsFiche = (CorpsFiche) recordManager.fetch(recordId.getRecid2(), corpsFicheSerializer);
        Fiche fiche = enteteFiche.getFiche();
        corpsFiche.setCorpsTo(fiche);
        return fiche;
    }

    private void testIntermediateCommit() {
        commitCounter++;
        if (commitCounter > 500) {
            try {
                recordManager.commit();
            } catch (IOException ioe) {
                throw new BdfStorageException(ioe);
            }
            commitCounter = 0;
        }
    }

    public EnteteFiche[] getEnteteFicheArray(Corpus corpus) {
        Map<Integer, SubsetItemRecordId> map = subsetItemRecordIdMap.getSubsetItemMap(corpus);
        if ((map == null) || (map.isEmpty())) {
            return new EnteteFiche[0];
        }
        SortedMap<Integer, EnteteFiche> sortedMap = new TreeMap<Integer, EnteteFiche>();
        try {
            for (Map.Entry<Integer, SubsetItemRecordId> entry : map.entrySet()) {
                int id = entry.getKey();
                SubsetItemRecordId recordId = entry.getValue();
                EnteteFiche enteteFiche = (EnteteFiche) recordManager.fetch(recordId.getRecid1(), enteteFicheSerializer);
                sortedMap.put(id, enteteFiche);
            }
        } catch (IOException ioe) {
            throw new BdfStorageException(ioe);
        }
        EnteteFiche[] array = new EnteteFiche[sortedMap.size()];
        return sortedMap.values().toArray(array);
    }

}
