/*-
 * ~~~~~~licensing~~~~~~
 * cluster-persistence-service
 * ==========
 * Copyright (C) 2020 - 2021 EmDev LLC
 * ==========
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ~~~~~~/licensing~~~~~~
 */
package ru.entaxy.esb.platform.core.cluster.persistence;

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.info("~~> EXECUTOR :: RUN");
				UpdateTask ut = new UpdateTask(PersistenceManager.this.helper, this);
				PersistenceManager.this.clear();
				threadPool.execute(ut);
			}
		}

		@Override
		public synchronized void executed() {
			log.info("~~> 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");
			try {
				log.debug("~~> UPDATE_TASK :: RUN :: sleeping");
				Thread.currentThread().sleep(15000);
				log.debug("~~> UPDATE_TASK :: RUN :: processig");
				// TODO add support for different groups
				this.helper.saveClusterState("default");
				log.debug("~~> UPDATE_TASK :: RUN :: processed");
				this.callback.executed();
			} catch (InterruptedException e) {
				log.error("UpdateTask", e);
			}
		}
		
	}
	
	private final UpdateMarker marker = new UpdateMarker();
	
	private Helper<T> helper; 
	
	private Executor executor = new Executor();
	
	public PersistenceManager(ServiceProvider serviceProvider, String mapBaseName, String classifier, Class<T> dataClass) {
		
		helper = new Helper<>(serviceProvider, mapBaseName, classifier, dataClass);
		
	}
	
	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.info("~~> UPDATED");
		synchronized (marker) {
			marker.update();
		}
		runExecutor();
	}
	
	protected synchronized void clear() {
		synchronized (marker) {
			marker.clear();
		}
	}
	
	protected synchronized void executed() {
		boolean updated = false;
		synchronized (marker) {
			updated = marker.isUpdated();
		}
		if (updated)
			runExecutor();
	}
}
