package org.jivesoftware.smack;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smack.packet.PlainStreamElement;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.TopLevelStreamElement;

public class SynchronizationPoint<E extends Exception> {
   private static final Logger LOGGER = Logger.getLogger(SynchronizationPoint.class.getName());
   private final AbstractXMPPConnection connection;
   private final Lock connectionLock;
   private final Condition condition;
   private SynchronizationPoint.State state;
   private E failureException;

   public SynchronizationPoint(AbstractXMPPConnection connection) {
      this.connection = connection;
      this.connectionLock = connection.getConnectionLock();
      this.condition = connection.getConnectionLock().newCondition();
      this.init();
   }

   public void init() {
      this.connectionLock.lock();
      this.state = SynchronizationPoint.State.Initial;
      this.failureException = null;
      this.connectionLock.unlock();
   }

   public void sendAndWaitForResponse(TopLevelStreamElement request) throws SmackException.NoResponseException, SmackException.NotConnectedException, InterruptedException {
      assert this.state == SynchronizationPoint.State.Initial;

      this.connectionLock.lock();

      try {
         if (request != null) {
            if (request instanceof Stanza) {
               this.connection.sendStanza((Stanza)request);
            } else {
               if (!(request instanceof PlainStreamElement)) {
                  throw new IllegalStateException("Unsupported element type");
               }

               this.connection.send((PlainStreamElement)request);
            }

            this.state = SynchronizationPoint.State.RequestSent;
         }

         this.waitForConditionOrTimeout();
      } finally {
         this.connectionLock.unlock();
      }

      this.checkForResponse();
   }

   public void sendAndWaitForResponseOrThrow(PlainStreamElement request) throws E, SmackException.NoResponseException, SmackException.NotConnectedException, InterruptedException {
      this.sendAndWaitForResponse(request);
      switch(this.state) {
      case Failure:
         if (this.failureException != null) {
            throw this.failureException;
         }
      default:
      }
   }

   public void checkIfSuccessOrWaitOrThrow() throws SmackException.NoResponseException, E {
      this.checkIfSuccessOrWait();
      if (this.state == SynchronizationPoint.State.Failure) {
         throw this.failureException;
      }
   }

   public void checkIfSuccessOrWait() throws SmackException.NoResponseException {
      this.connectionLock.lock();

      label39: {
         try {
            if (this.state != SynchronizationPoint.State.Success) {
               this.waitForConditionOrTimeout();
               break label39;
            }
         } finally {
            this.connectionLock.unlock();
         }

         return;
      }

      this.checkForResponse();
   }

   public void reportSuccess() {
      this.connectionLock.lock();

      try {
         this.state = SynchronizationPoint.State.Success;
         this.condition.signal();
      } finally {
         this.connectionLock.unlock();
      }

   }

   public void reportFailure() {
      this.reportFailure((Exception)null);
   }

   public void reportFailure(E failureException) {
      this.connectionLock.lock();

      try {
         this.state = SynchronizationPoint.State.Failure;
         this.failureException = failureException;
         this.condition.signal();
      } finally {
         this.connectionLock.unlock();
      }

   }

   public boolean wasSuccessful() {
      this.connectionLock.lock();

      boolean var1;
      try {
         var1 = this.state == SynchronizationPoint.State.Success;
      } finally {
         this.connectionLock.unlock();
      }

      return var1;
   }

   public boolean requestSent() {
      this.connectionLock.lock();

      boolean var1;
      try {
         var1 = this.state == SynchronizationPoint.State.RequestSent;
      } finally {
         this.connectionLock.unlock();
      }

      return var1;
   }

   private void waitForConditionOrTimeout() {
      long remainingWait = TimeUnit.MILLISECONDS.toNanos(this.connection.getPacketReplyTimeout());

      while(this.state == SynchronizationPoint.State.RequestSent || this.state == SynchronizationPoint.State.Initial) {
         try {
            remainingWait = this.condition.awaitNanos(remainingWait);
            if (remainingWait <= 0L) {
               this.state = SynchronizationPoint.State.NoResponse;
               break;
            }
         } catch (InterruptedException var4) {
            LOGGER.log(Level.WARNING, "Thread interrupt while waiting for condition or timeout ignored", var4);
         }
      }

   }

   private void checkForResponse() throws SmackException.NoResponseException {
      switch(this.state) {
      case Initial:
      case NoResponse:
      case RequestSent:
         throw SmackException.NoResponseException.newWith(this.connection);
      default:
      }
   }

   private static enum State {
      Initial,
      RequestSent,
      NoResponse,
      Success,
      Failure;
   }
}
