/* 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.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;
import jdbm.RecordManager;
import jdbm.helper.Serializer;
import net.fichotheque.Subset;
import net.fichotheque.SubsetItem;
import net.fichotheque.SubsetKey;
import net.mapeadores.util.io.output.ByteArrayOutputStream;


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

    private static final ClassSerializer classSerializer = new ClassSerializer();
    private final Map<SubsetKey, Map<Integer, SubsetItemRecordId>> globalMap = new HashMap<SubsetKey, Map<Integer, SubsetItemRecordId>>();
    private boolean withChange = false;

    public SubsetItemRecordId getRecordId(SubsetItem subsetItem) {
        return getRecordId(subsetItem.getSubset(), subsetItem.getId());
    }

    public SubsetItemRecordId getRecordId(Subset subset, int id) {
        Map<Integer, SubsetItemRecordId> map = globalMap.get(subset.getSubsetKey());
        if (map == null) {
            return null;
        }
        return map.get(id);
    }

    public Map<Integer, SubsetItemRecordId> getSubsetItemMap(Subset subset) {
        return globalMap.get(subset.getSubsetKey());
    }

    public SubsetItemRecordId removeSubsetItem(Subset subset, int id) {
        Map<Integer, SubsetItemRecordId> map = globalMap.get(subset.getSubsetKey());
        if (map == null) {
            return null;
        }
        withChange = true;
        return map.remove(id);
    }

    public void add(Subset subset, int id, long recid) {
        Map<Integer, SubsetItemRecordId> map = globalMap.get(subset.getSubsetKey());
        if (map == null) {
            map = new HashMap<Integer, SubsetItemRecordId>();
            globalMap.put(subset.getSubsetKey(), map);
        }
        map.put(id, new SubsetItemRecordId(recid));
        withChange = true;
    }

    public void add(Subset subset, int id, long recid1, long recid2) {
        Map<Integer, SubsetItemRecordId> map = globalMap.get(subset.getSubsetKey());
        if (map == null) {
            map = new HashMap<Integer, SubsetItemRecordId>();
            globalMap.put(subset.getSubsetKey(), map);
        }
        map.put(id, new SubsetItemRecordId(recid1, recid2));
        withChange = true;
    }

    private void add(SubsetKey subsetKey, Map<Integer, SubsetItemRecordId> subsetMap) {
        globalMap.put(subsetKey, subsetMap);
    }

    private byte[] serialize() throws IOException {
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        DataOutputStream dataOutput = new DataOutputStream(output);
        dataOutput.writeInt(globalMap.size());
        for (Map.Entry<SubsetKey, Map<Integer, SubsetItemRecordId>> entry : globalMap.entrySet()) {
            dataOutput.writeUTF(entry.getKey().getKeyString());
            Map<Integer, SubsetItemRecordId> subsetMap = entry.getValue();
            dataOutput.writeInt(subsetMap.size());
            for (Map.Entry<Integer, SubsetItemRecordId> itemEntry : subsetMap.entrySet()) {
                dataOutput.writeInt(itemEntry.getKey().intValue());
                SubsetItemRecordId id = itemEntry.getValue();
                byte b = id.getType();
                dataOutput.writeByte(b);
                dataOutput.writeLong(id.getRecid1());
                if (b == SubsetItemRecordId.DOUBLE_TYPE) {
                    dataOutput.writeLong(id.getRecid2());
                }
            }
        }
        dataOutput.flush();
        return output.toByteArray();
    }

    public boolean isWithChange() {
        return withChange;
    }

    public void save(RecordManager recordManager, String objectName) throws IOException {
        long recordId = recordManager.getNamedObject(objectName);
        recordManager.update(recordId, this, classSerializer);
        withChange = false;
    }

    public static SubsetItemRecordIdMap init(RecordManager recordManager, String objectName) throws IOException {
        long recordId = recordManager.getNamedObject(objectName);
        if (recordId == 0) {
            SubsetItemRecordIdMap subsetItemRecordIdMap = new SubsetItemRecordIdMap();
            recordId = recordManager.insert(subsetItemRecordIdMap, classSerializer);
            recordManager.setNamedObject(objectName, recordId);
            return subsetItemRecordIdMap;
        } else {
            SubsetItemRecordIdMap subsetItemRecordIdMap = (SubsetItemRecordIdMap) recordManager.fetch(recordId, classSerializer);
            return subsetItemRecordIdMap;
        }
    }


    private static class ClassSerializer implements Serializer {

        private ClassSerializer() {
        }

        @Override
        public byte[] serialize(Object obj) throws IOException {
            SubsetItemRecordIdMap subsetItemRecordIdMap = (SubsetItemRecordIdMap) obj;
            return subsetItemRecordIdMap.serialize();
        }

        @Override
        public Object deserialize(byte[] serialized) {
            ByteArrayInputStream input = new ByteArrayInputStream(serialized);
            DataInputStream dataInput = new DataInputStream(input);
            try {
                SubsetItemRecordIdMap subsetItemRecordIdMap = new SubsetItemRecordIdMap();
                int size = dataInput.readInt();
                for (int i = 0; i < size; i++) {
                    String subsetKeyString = dataInput.readUTF();
                    SubsetKey subsetKey = SubsetKey.parse(subsetKeyString);
                    int subsetSize = dataInput.readInt();
                    Map<Integer, SubsetItemRecordId> map = new HashMap<Integer, SubsetItemRecordId>();
                    for (int j = 0; j < subsetSize; j++) {
                        int id = dataInput.readInt();
                        byte type = dataInput.readByte();
                        long recid1 = dataInput.readLong();
                        if (type == SubsetItemRecordId.DOUBLE_TYPE) {
                            long recid2 = dataInput.readLong();
                            map.put(id, new SubsetItemRecordId(recid1, recid2));
                        } else {
                            map.put(id, new SubsetItemRecordId(recid1));
                        }

                    }
                    subsetItemRecordIdMap.add(subsetKey, map);
                }
                return subsetItemRecordIdMap;
            } catch (IOException ioe) {
                throw new BdfStorageException(ioe);
            } catch (ParseException pe) {
                throw new BdfStorageException(pe);
            }
        }

    }

}
