/*-
 * ~~~~~~licensing~~~~~~
 * cluster-persistence-service
 * ==========
 * Copyright (C) 2020 - 2025 EmDev LLC
 * ==========
 * You may not use this file except in accordance with the License Terms of the Copyright
 * Holder located at: https://entaxy.ru/eula . All copyrights, all intellectual property
 * rights to the Software and any copies are the property of the Copyright Holder. Unless
 * it is explicitly allowed the Copyright Holder, the User is prohibited from using the
 * Software for commercial purposes to provide services to third parties.
 * 
 * The Copyright Holder hereby declares that the Software is provided on an "AS IS".
 * Under no circumstances does the Copyright Holder guarantee or promise that the
 * Software provided by him will be suitable or not suitable for the specific purposes
 * of the User, that the Software will meet all commercial and personal subjective
 * expectations of the User, that the Software will work properly, without technical
 * errors, quickly and uninterruptedly.
 * 
 * Under no circumstances shall the Copyright Holder or its Affiliates is not liable
 * to the User for any direct or indirect losses of the User, his expenses or actual
 * damage, including, downtime; loss of bussines; lost profit; lost earnings; loss
 * or damage to data, property, etc.
 * ~~~~~~/licensing~~~~~~
 */
package ru.entaxy.esb.platform.core.cluster.persistence;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PersistenceManager<T> {

    private static final Logger log = LoggerFactory.getLogger(PersistenceManager.class);


    private static class UpdateMarker {

        private boolean updated = false;

        public void update() {
            this.updated = true;
        }

        public void clear() {
            this.updated = false;
        }

        public boolean isUpdated() {
            return updated;
        }

    }


    private static interface UpdateTaskCallback {
        public void executed();
    }


    private class Executor implements UpdateTaskCallback {

        private Boolean isRunning = false;

        private ExecutorService threadPool = Executors.newSingleThreadExecutor();

        public synchronized void run() {
            synchronized (isRunning) {
                if (this.isRunning)
                    return;
                this.isRunning = true;
                log.debug("~~> EXECUTOR :: RUN");
                UpdateTask ut = new UpdateTask(PersistenceManager.this.helper, this);
                PersistenceManager.this.clear();
                threadPool.execute(ut);
            }
        }

        @Override
        public synchronized void executed() {
            log.debug("~~> EXECUTOR :: EXECUTED");
            synchronized (isRunning) {
                this.isRunning = false;
            }
            PersistenceManager.this.executed();
        }

    }


    private static class UpdateTask implements Runnable {

        private Helper<?> helper;
        private UpdateTaskCallback callback;

        public UpdateTask(Helper<?> helper, UpdateTaskCallback callback) {
            this.helper = helper;
            this.callback = callback;
        }

        @Override
        public void run() {
            log.debug("~~> UPDATE_TASK :: RUN");
            // TODO add support for different groups
            this.helper.saveClusterState("default");
            log.debug("~~> UPDATE_TASK :: RUN :: processed");
            this.callback.executed();
        }

    }


    private final UpdateMarker marker = new UpdateMarker();

    private Helper<T> helper;

    private Executor executor = new Executor();

    private List<PersistenceManager<?>> dependents = new ArrayList<>();

    private Object dependentsLock = new Object();

    public PersistenceManager(ServiceProvider serviceProvider, String mapBaseName, String classifier,
            Class<T> dataClass) {

        helper = new Helper<>(serviceProvider, mapBaseName, classifier, dataClass);

    }

    public void addDependent(PersistenceManager<?> manager) {
        synchronized (dependentsLock) {
            dependents.add(manager);
        }
    }

    public void restore() {
        // this is called only once before the consumer started
        // so we can call helper directly
        helper.loadClusterState("default");
    }

    public void persist() {
        this.updated();
    }

    protected void runExecutor() {
        synchronized (executor) {
            if (!executor.isRunning)
                executor.run();
        }
    }

    public void updated() {
        log.debug("~~> UPDATED");
        synchronized (marker) {
            marker.update();
        }
        runExecutor();
        synchronized (dependentsLock) {
            for (PersistenceManager<?> dep : dependents)
                dep.updated();
        }
    }

    protected synchronized void clear() {
        synchronized (marker) {
            marker.clear();
        }
    }

    protected synchronized void executed() {
        boolean updated = false;
        synchronized (marker) {
            updated = marker.isUpdated();
        }
        if (updated)
            runExecutor();
    }
}
