package org.jivesoftware.smack.util;

import java.util.AbstractQueue;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ArrayBlockingQueueWithShutdown<E> extends AbstractQueue<E> implements BlockingQueue<E> {
   private final E[] items;
   private int takeIndex;
   private int putIndex;
   private int count;
   private final ReentrantLock lock;
   private final Condition notEmpty;
   private final Condition notFull;
   private volatile boolean isShutdown;

   private final int inc(int i) {
      ++i;
      return i == this.items.length ? 0 : i;
   }

   private final void insert(E e) {
      this.items[this.putIndex] = e;
      this.putIndex = this.inc(this.putIndex);
      ++this.count;
      this.notEmpty.signal();
   }

   private final E extract() {
      E e = this.items[this.takeIndex];
      this.items[this.takeIndex] = null;
      this.takeIndex = this.inc(this.takeIndex);
      --this.count;
      this.notFull.signal();
      return e;
   }

   private final void removeAt(int i) {
      if (i == this.takeIndex) {
         this.items[this.takeIndex] = null;
         this.takeIndex = this.inc(this.takeIndex);
      } else {
         while(true) {
            int nexti = this.inc(i);
            if (nexti == this.putIndex) {
               this.items[i] = null;
               this.putIndex = i;
               break;
            }

            this.items[i] = this.items[nexti];
            i = nexti;
         }
      }

      --this.count;
      this.notFull.signal();
   }

   private static final void checkNotNull(Object o) {
      if (o == null) {
         throw new NullPointerException();
      }
   }

   private final void checkNotShutdown() throws InterruptedException {
      if (this.isShutdown) {
         throw new InterruptedException();
      }
   }

   private final boolean hasNoElements() {
      return this.count == 0;
   }

   private final boolean hasElements() {
      return !this.hasNoElements();
   }

   private final boolean isFull() {
      return this.count == this.items.length;
   }

   private final boolean isNotFull() {
      return !this.isFull();
   }

   public ArrayBlockingQueueWithShutdown(int capacity) {
      this(capacity, false);
   }

   public ArrayBlockingQueueWithShutdown(int capacity, boolean fair) {
      this.isShutdown = false;
      if (capacity <= 0) {
         throw new IllegalArgumentException();
      } else {
         this.items = (Object[])(new Object[capacity]);
         this.lock = new ReentrantLock(fair);
         this.notEmpty = this.lock.newCondition();
         this.notFull = this.lock.newCondition();
      }
   }

   public void shutdown() {
      this.lock.lock();

      try {
         this.isShutdown = true;
         this.notEmpty.signalAll();
         this.notFull.signalAll();
      } finally {
         this.lock.unlock();
      }

   }

   public void start() {
      this.lock.lock();

      try {
         this.isShutdown = false;
      } finally {
         this.lock.unlock();
      }

   }

   public boolean isShutdown() {
      this.lock.lock();

      boolean var1;
      try {
         var1 = this.isShutdown;
      } finally {
         this.lock.unlock();
      }

      return var1;
   }

   public E poll() {
      this.lock.lock();

      Object var2;
      try {
         Object e;
         if (this.hasNoElements()) {
            e = null;
            return e;
         }

         e = this.extract();
         var2 = e;
      } finally {
         this.lock.unlock();
      }

      return var2;
   }

   public E peek() {
      this.lock.lock();

      Object var1;
      try {
         var1 = this.hasNoElements() ? null : this.items[this.takeIndex];
      } finally {
         this.lock.unlock();
      }

      return var1;
   }

   public boolean offer(E e) {
      checkNotNull(e);
      this.lock.lock();

      boolean var2;
      try {
         if (this.isFull() || this.isShutdown) {
            var2 = false;
            return var2;
         }

         this.insert(e);
         var2 = true;
      } finally {
         this.lock.unlock();
      }

      return var2;
   }

   public void put(E e) throws InterruptedException {
      checkNotNull(e);
      this.lock.lockInterruptibly();

      try {
         while(this.isFull()) {
            try {
               this.notFull.await();
               this.checkNotShutdown();
            } catch (InterruptedException var6) {
               this.notFull.signal();
               throw var6;
            }
         }

         this.insert(e);
      } finally {
         this.lock.unlock();
      }

   }

   public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException {
      checkNotNull(e);
      long nanos = unit.toNanos(timeout);
      this.lock.lockInterruptibly();

      boolean var7;
      try {
         while(!this.isNotFull()) {
            if (nanos <= 0L) {
               var7 = false;
               return var7;
            }

            try {
               nanos = this.notFull.awaitNanos(nanos);
               this.checkNotShutdown();
            } catch (InterruptedException var11) {
               this.notFull.signal();
               throw var11;
            }
         }

         this.insert(e);
         var7 = true;
      } finally {
         this.lock.unlock();
      }

      return var7;
   }

   public E take() throws InterruptedException {
      this.lock.lockInterruptibly();

      Object var2;
      try {
         this.checkNotShutdown();

         try {
            while(this.hasNoElements()) {
               this.notEmpty.await();
               this.checkNotShutdown();
            }
         } catch (InterruptedException var6) {
            this.notEmpty.signal();
            throw var6;
         }

         E e = this.extract();
         var2 = e;
      } finally {
         this.lock.unlock();
      }

      return var2;
   }

   public E poll(long timeout, TimeUnit unit) throws InterruptedException {
      long nanos = unit.toNanos(timeout);
      this.lock.lockInterruptibly();

      try {
         this.checkNotShutdown();

         Object e;
         while(!this.hasElements()) {
            if (nanos <= 0L) {
               e = null;
               return e;
            }

            try {
               nanos = this.notEmpty.awaitNanos(nanos);
               this.checkNotShutdown();
            } catch (InterruptedException var11) {
               this.notEmpty.signal();
               throw var11;
            }
         }

         e = this.extract();
         Object var7 = e;
         return var7;
      } finally {
         this.lock.unlock();
      }
   }

   public int remainingCapacity() {
      this.lock.lock();

      int var1;
      try {
         var1 = this.items.length - this.count;
      } finally {
         this.lock.unlock();
      }

      return var1;
   }

   public int drainTo(Collection<? super E> c) {
      checkNotNull(c);
      if (c == this) {
         throw new IllegalArgumentException();
      } else {
         this.lock.lock();

         int var4;
         try {
            int i = this.takeIndex;

            int n;
            for(n = 0; n < this.count; ++n) {
               c.add(this.items[i]);
               this.items[i] = null;
               i = this.inc(i);
            }

            if (n > 0) {
               this.count = 0;
               this.putIndex = 0;
               this.takeIndex = 0;
               this.notFull.signalAll();
            }

            var4 = n;
         } finally {
            this.lock.unlock();
         }

         return var4;
      }
   }

   public int drainTo(Collection<? super E> c, int maxElements) {
      checkNotNull(c);
      if (c == this) {
         throw new IllegalArgumentException();
      } else if (maxElements <= 0) {
         return 0;
      } else {
         this.lock.lock();

         int var6;
         try {
            int i = this.takeIndex;
            int n = 0;

            for(int max = maxElements < this.count ? maxElements : this.count; n < max; ++n) {
               c.add(this.items[i]);
               this.items[i] = null;
               i = this.inc(i);
            }

            if (n > 0) {
               this.count -= n;
               this.takeIndex = i;
               this.notFull.signalAll();
            }

            var6 = n;
         } finally {
            this.lock.unlock();
         }

         return var6;
      }
   }

   public int size() {
      this.lock.lock();

      int var1;
      try {
         var1 = this.count;
      } finally {
         this.lock.unlock();
      }

      return var1;
   }

   public Iterator<E> iterator() {
      this.lock.lock();

      ArrayBlockingQueueWithShutdown.Itr var1;
      try {
         var1 = new ArrayBlockingQueueWithShutdown.Itr();
      } finally {
         this.lock.unlock();
      }

      return var1;
   }

   private class Itr implements Iterator<E> {
      private int nextIndex;
      private E nextItem;
      private int lastRet = -1;

      Itr() {
         if (ArrayBlockingQueueWithShutdown.this.count == 0) {
            this.nextIndex = -1;
         } else {
            this.nextIndex = ArrayBlockingQueueWithShutdown.this.takeIndex;
            this.nextItem = ArrayBlockingQueueWithShutdown.this.items[ArrayBlockingQueueWithShutdown.this.takeIndex];
         }

      }

      public boolean hasNext() {
         return this.nextIndex >= 0;
      }

      private void checkNext() {
         if (this.nextIndex == ArrayBlockingQueueWithShutdown.this.putIndex) {
            this.nextIndex = -1;
            this.nextItem = null;
         } else {
            this.nextItem = ArrayBlockingQueueWithShutdown.this.items[this.nextIndex];
            if (this.nextItem == null) {
               this.nextIndex = -1;
            }
         }

      }

      public E next() {
         ArrayBlockingQueueWithShutdown.this.lock.lock();

         Object var2;
         try {
            if (this.nextIndex < 0) {
               throw new NoSuchElementException();
            }

            this.lastRet = this.nextIndex;
            E e = this.nextItem;
            this.nextIndex = ArrayBlockingQueueWithShutdown.this.inc(this.nextIndex);
            this.checkNext();
            var2 = e;
         } finally {
            ArrayBlockingQueueWithShutdown.this.lock.unlock();
         }

         return var2;
      }

      public void remove() {
         ArrayBlockingQueueWithShutdown.this.lock.lock();

         try {
            int i = this.lastRet;
            if (i < 0) {
               throw new IllegalStateException();
            }

            this.lastRet = -1;
            int ti = ArrayBlockingQueueWithShutdown.this.takeIndex;
            ArrayBlockingQueueWithShutdown.this.removeAt(i);
            this.nextIndex = i == ti ? ArrayBlockingQueueWithShutdown.this.takeIndex : i;
            this.checkNext();
         } finally {
            ArrayBlockingQueueWithShutdown.this.lock.unlock();
         }

      }
   }
}
