/**
 * Copyright © 2017 by Data Virtuality GmbH
 */
package com.datavirtuality.dv.core.interfaces.concurrent;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * A wrapper for a ReentrantReadWriteLock which also tracks additional properties.
 * 
 * @author Roman Pedchenko
 * @date 2013.05.17
 */
public class RefreshSemaphore {
	public interface Lock extends AutoCloseable {
		void close();
	}

	public interface ReadLock extends Lock {
	}

	public static class WriteLock implements Lock {
		public WriteLock() {
		}

		public boolean canSkip() {
			return false;
		}

		@Override
		public void close() {
		}
	}

	public final class RealWriteLock extends WriteLock {
		private final boolean fullProcessing;
		private final boolean freeway;

		public RealWriteLock(boolean freeway, boolean fullProcessing) {
            this.freeway = freeway;
            this.fullProcessing = fullProcessing;
		}

		public boolean canSkip() {
			return !freeway && RefreshSemaphore.this.fullProcessing.get();
		}

		@Override
		public void close() {
			RefreshSemaphore.this.fullProcessing.set(fullProcessing);
			releaseWriteLock();
		}
	}

    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(true);
	private final String schemaName;
	private final AtomicBoolean fullProcessing = new AtomicBoolean();

	public static WriteLock writeLock(RefreshSemaphore sem, boolean fullProcessing) {
		if (sem == null)
			return new WriteLock();
		return sem.acquireWriteLock(fullProcessing);
	}

	public RefreshSemaphore(String schemaName) {
		this.schemaName = schemaName;
	}

	private WriteLock acquireWriteLock(boolean fullProcessing) {
        final WriteLock writeLock = new RealWriteLock(!rwLock.isWriteLocked(), fullProcessing);
        rwLock.writeLock().lock();
        return writeLock;
	}

	private void releaseWriteLock() {
        rwLock.writeLock().unlock();
	}

	public static ReadLock readLock(RefreshSemaphore sem) {
		if (sem == null)
			return () -> {};
        sem.rwLock.readLock().lock();
        return sem::releaseReadLock;
	}

    private void releaseReadLock() {
        rwLock.readLock().unlock();
	}

	@Override
	public String toString() {
        return super.toString() + "[ Permits: schema: " + schemaName +
               "; writeLock: " + (rwLock.isWriteLocked() ? "LOCKED" : "FREE") +
               ", readers: " + rwLock.getReadLockCount();
	}
}
