package org.jivesoftware.smack;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.jivesoftware.smack.filter.StanzaFilter;
import org.jivesoftware.smack.packet.Stanza;

public class PacketCollector {
   private final StanzaFilter packetFilter;
   private final ArrayBlockingQueue<Stanza> resultQueue;
   private final PacketCollector collectorToReset;
   private final XMPPConnection connection;
   private boolean cancelled = false;
   private volatile long waitStart;

   protected PacketCollector(XMPPConnection connection, PacketCollector.Configuration configuration) {
      this.connection = connection;
      this.packetFilter = configuration.packetFilter;
      this.resultQueue = new ArrayBlockingQueue(configuration.size);
      this.collectorToReset = configuration.collectorToReset;
   }

   public void cancel() {
      if (!this.cancelled) {
         this.cancelled = true;
         this.connection.removePacketCollector(this);
      }

   }

   /** @deprecated */
   @Deprecated
   public StanzaFilter getPacketFilter() {
      return this.getStanzaFilter();
   }

   public StanzaFilter getStanzaFilter() {
      return this.packetFilter;
   }

   public <P extends Stanza> P pollResult() {
      return (Stanza)this.resultQueue.poll();
   }

   public <P extends Stanza> P pollResultOrThrow() throws XMPPException.XMPPErrorException {
      P result = this.pollResult();
      if (result != null) {
         XMPPException.XMPPErrorException.ifHasErrorThenThrow(result);
      }

      return result;
   }

   public <P extends Stanza> P nextResultBlockForever() throws InterruptedException {
      this.throwIfCancelled();

      Stanza res;
      for(res = null; res == null; res = (Stanza)this.resultQueue.take()) {
      }

      return res;
   }

   public <P extends Stanza> P nextResult() throws InterruptedException {
      return this.nextResult(this.connection.getPacketReplyTimeout());
   }

   public <P extends Stanza> P nextResult(long timeout) throws InterruptedException {
      this.throwIfCancelled();
      P res = null;
      long remainingWait = timeout;
      this.waitStart = System.currentTimeMillis();

      do {
         res = (Stanza)this.resultQueue.poll(remainingWait, TimeUnit.MILLISECONDS);
         if (res != null) {
            return res;
         }

         remainingWait = timeout - (System.currentTimeMillis() - this.waitStart);
      } while(remainingWait > 0L);

      return null;
   }

   public <P extends Stanza> P nextResultOrThrow() throws SmackException.NoResponseException, XMPPException.XMPPErrorException, InterruptedException {
      return this.nextResultOrThrow(this.connection.getPacketReplyTimeout());
   }

   public <P extends Stanza> P nextResultOrThrow(long timeout) throws SmackException.NoResponseException, XMPPException.XMPPErrorException, InterruptedException {
      P result = this.nextResult(timeout);
      this.cancel();
      if (result == null) {
         throw SmackException.NoResponseException.newWith(this.connection, this);
      } else {
         XMPPException.XMPPErrorException.ifHasErrorThenThrow(result);
         return result;
      }
   }

   public int getCollectedCount() {
      return this.resultQueue.size();
   }

   protected void processPacket(Stanza packet) {
      if (this.packetFilter == null || this.packetFilter.accept(packet)) {
         while(true) {
            if (this.resultQueue.offer(packet)) {
               if (this.collectorToReset != null) {
                  this.collectorToReset.waitStart = System.currentTimeMillis();
               }
               break;
            }

            this.resultQueue.poll();
         }
      }

   }

   private final void throwIfCancelled() {
      if (this.cancelled) {
         throw new IllegalStateException("Packet collector already cancelled");
      }
   }

   public static PacketCollector.Configuration newConfiguration() {
      return new PacketCollector.Configuration();
   }

   public static class Configuration {
      private StanzaFilter packetFilter;
      private int size;
      private PacketCollector collectorToReset;

      private Configuration() {
         this.size = SmackConfiguration.getPacketCollectorSize();
      }

      /** @deprecated */
      @Deprecated
      public PacketCollector.Configuration setPacketFilter(StanzaFilter packetFilter) {
         return this.setStanzaFilter(packetFilter);
      }

      public PacketCollector.Configuration setStanzaFilter(StanzaFilter stanzaFilter) {
         this.packetFilter = stanzaFilter;
         return this;
      }

      public PacketCollector.Configuration setSize(int size) {
         this.size = size;
         return this;
      }

      public PacketCollector.Configuration setCollectorToReset(PacketCollector collector) {
         this.collectorToReset = collector;
         return this;
      }

      // $FF: synthetic method
      Configuration(Object x0) {
         this();
      }
   }
}
