/* BdfServer - Copyright (c) 2014-2024 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.impl;

import fr.exemole.bdfserver.api.balayage.BalayageSession;
import fr.exemole.bdfserver.api.balayage.LockedBalayageException;
import fr.exemole.bdfserver.api.managers.BalayageManager;
import fr.exemole.bdfserver.api.storage.BalayageStorage;
import fr.exemole.bdfserver.api.storage.BdfStorageException;
import fr.exemole.bdfserver.api.storage.StorageContent;
import fr.exemole.bdfserver.tools.balayage.BalayageCompiler;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import net.fichotheque.EditOrigin;
import net.fichotheque.exportation.balayage.BalayageDef;
import net.fichotheque.exportation.balayage.BalayageDescription;
import net.fichotheque.utils.BalayageUtils;
import net.mapeadores.util.io.IOUtils;


/**
 *
 * @author Vincent Calame
 */
class BalayageManagerImpl implements BalayageManager {

    private final static long ENDED_INTERVAL = 30 * 1000;
    private final static long FREE_INTERVAL = 60 * 60 * 1000;
    private final BalayageStorage balayageStorage;
    private final SortedMap<String, BalayageDescription> balayageDescriptionMap = new TreeMap<String, BalayageDescription>();
    private final Map<String, BalayageLock> lockMap = new HashMap<String, BalayageLock>();
    private List<BalayageDescription> balayageDescriptionList = Collections.emptyList();

    BalayageManagerImpl(BalayageStorage balayageStorage) {
        this.balayageStorage = balayageStorage;
    }

    @Override
    public synchronized void update() {
        balayageDescriptionMap.clear();
        BalayageStorage.Unit[] array = balayageStorage.checkStorage();
        for (BalayageStorage.Unit storageUnit : array) {
            BalayageDescription balayageDescription = BalayageCompiler.compile(storageUnit);
            balayageDescriptionMap.put(balayageDescription.getName(), balayageDescription);
        }
        updateList();
    }

    @Override
    public synchronized List<BalayageDescription> getBalayageDescriptionList() {
        return balayageDescriptionList;
    }

    @Override
    public synchronized BalayageDescription getBalayage(String name) {
        return balayageDescriptionMap.get(name);
    }

    @Override
    public synchronized String getBalayageContent(String balayageName, String contentPath) {
        StorageContent storageContent = balayageStorage.getStorageContent(balayageName, contentPath);
        if (storageContent != null) {
            try (InputStream is = storageContent.getInputStream()) {
                return IOUtils.toString(is, "UTF-8");
            } catch (IOException ioe) {
                throw new BdfStorageException(ioe);
            }
        } else {
            return null;
        }
    }

    @Override
    public BalayageSession openBalayageSession(String name) throws LockedBalayageException {
        synchronized (lockMap) {
            BalayageLock balayageLock = lockMap.get(name);
            if ((balayageLock != null) && (!balayageLock.canFree())) {
                throw new LockedBalayageException(name);
            }
            balayageLock = new BalayageLock();
            lockMap.put(name, balayageLock);
            return new InternalBalayageSession(balayageLock);
        }
    }

    @Override
    public synchronized BalayageDescription putBalayageDef(BalayageDef balayageDef, EditOrigin editOrigin) {
        String balayageName = balayageDef.getName();
        balayageStorage.saveBalayageDef(balayageDef, editOrigin);
        return reinitBalayage(balayageName);
    }

    @Override
    public synchronized BalayageDescription putBalayageContent(String balayageName, String contentPath, InputStream inputStream, EditOrigin editOrigin) throws IOException {
        balayageStorage.saveStorageContent(balayageName, contentPath, inputStream, editOrigin);
        return reinitBalayage(balayageName);
    }

    @Override
    public synchronized BalayageDescription removeBalayageContent(String balayageName, String contentPath, EditOrigin editOrigin) {
        boolean done = balayageStorage.removeStorageContent(balayageName, contentPath, editOrigin);
        if (done) {
            return reinitBalayage(balayageName);
        } else {
            return null;
        }
    }

    @Override
    public synchronized void removeBalayage(String balayageName, EditOrigin editOrigin) {
        boolean done = balayageStorage.removeBalayage(balayageName, editOrigin);
        if (done) {
            update();
        }
    }


    private BalayageDescription reinitBalayage(String balayageName) {
        BalayageStorage.Unit storageUnit = balayageStorage.getBalayageStorageUnit(balayageName);
        BalayageDescription balayageDescription = BalayageCompiler.compile(storageUnit);
        balayageDescriptionMap.put(balayageName, balayageDescription);
        updateList();
        return balayageDescription;
    }

    private void updateList() {
        balayageDescriptionList = BalayageUtils.wrap(balayageDescriptionMap.values().toArray(new BalayageDescription[balayageDescriptionMap.size()]));
    }


    private static class BalayageLock {

        private boolean ended;
        private long time;

        private BalayageLock() {
            this.ended = false;
            this.time = System.currentTimeMillis();
        }

        private void end() {
            if (!ended) {
                this.ended = true;
                this.time = System.currentTimeMillis();
            }
        }

        private boolean canFree() {
            long delay = (System.currentTimeMillis()) - time;
            if (ended) {
                return (delay > ENDED_INTERVAL);
            } else {
                return (delay > FREE_INTERVAL);
            }
        }

    }


    private class InternalBalayageSession implements BalayageSession {

        private final BalayageLock balayageLock;

        private InternalBalayageSession(BalayageLock balayageLock) {
            this.balayageLock = balayageLock;
        }

        @Override
        public void close() {
            this.balayageLock.end();
        }

    }

}
