package org.infinispan.extendedstats.wrappers;

import java.util.Collection;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import org.infinispan.context.InvocationContext;
import org.infinispan.extendedstats.topK.StreamSummaryContainer;
import org.infinispan.util.concurrent.locks.KeyAwareLockPromise;
import org.infinispan.util.concurrent.locks.LockManager;
import org.infinispan.util.concurrent.locks.LockState;
import org.infinispan.util.concurrent.locks.impl.InfinispanLock;

/**
 * Top-key stats about locks.
 *
 * @author Pedro Ruivo
 * @since 6.0
 */
public class TopKeyLockManager implements LockManager {

   private final LockManager current;
   private final StreamSummaryContainer container;

   public TopKeyLockManager(LockManager current, StreamSummaryContainer container) {
      this.current = current;
      this.container = container;
   }

   @Override
   public KeyAwareLockPromise lock(Object key, Object lockOwner, long time, TimeUnit unit) {
      if (lockOwnerAlreadyExists(key, lockOwner)) {
         return current.lock(key, lockOwner, time, unit);
      }
      KeyAwareLockPromise lockPromise = current.lock(key, lockOwner, time, unit);
      final boolean contented = !lockOwner.equals(current.getOwner(key));
      lockPromise.addListener(state -> container.addLockInformation(key, contented, state != LockState.ACQUIRED));
      return lockPromise;
   }

   @Override
   public KeyAwareLockPromise lockAll(Collection<?> keys, Object lockOwner, long time, TimeUnit unit) {
      final Set<Object> keysToTrack = keys.stream().filter(key -> !lockOwnerAlreadyExists(key, lockOwner)).collect(Collectors.toSet());
      final KeyAwareLockPromise lockPromise = current.lockAll(keys, lockOwner, time, unit);
      final Set<Object> contentedKeys = keys.stream().filter(key -> !lockOwner.equals(current.getOwner(key))).collect(Collectors.toSet());
      lockPromise.addListener((lockedKey, state) -> {
         if (keysToTrack.contains(lockedKey)) {
            container.addLockInformation(lockedKey, contentedKeys.contains(lockedKey), state != LockState.ACQUIRED);
         }
      });
      return lockPromise;
   }

   @Override
   public void unlock(Object key, Object lockOwner) {
      current.unlock(key, lockOwner);
   }

   @Override
   public void unlockAll(Collection<?> keys, Object lockOwner) {
      current.unlockAll(keys, lockOwner);
   }

   @Override
   public void unlockAll(InvocationContext ctx) {
      current.unlockAll(ctx);
   }

   @Override
   public boolean ownsLock(Object key, Object owner) {
      return current.ownsLock(key, owner);
   }

   @Override
   public boolean isLocked(Object key) {
      return current.isLocked(key);
   }

   @Override
   public Object getOwner(Object key) {
      return current.getOwner(key);
   }

   @Override
   public String printLockInfo() {
      return current.printLockInfo();
   }

   @Override
   public int getNumberOfLocksHeld() {
      return current.getNumberOfLocksHeld();
   }

   @Override
   public InfinispanLock getLock(Object key) {
      return current.getLock(key);
   }

   private boolean lockOwnerAlreadyExists(Object key, Object lockOwner) {
      final InfinispanLock lock = current.getLock(key);
      return lock != null && lock.containsLockOwner(lockOwner);
   }
}
