package com.google.common.util.concurrent;

import com.google.common.annotations.Beta;
import com.google.common.base.Preconditions;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.Immutable;

@Beta
public abstract class AbstractService implements Service {
   private static final ListenerCallQueue.Callback<Service.Listener> STARTING_CALLBACK = new ListenerCallQueue.Callback<Service.Listener>("starting()") {
      void call(Service.Listener listener) {
         listener.starting();
      }
   };
   private static final ListenerCallQueue.Callback<Service.Listener> RUNNING_CALLBACK = new ListenerCallQueue.Callback<Service.Listener>("running()") {
      void call(Service.Listener listener) {
         listener.running();
      }
   };
   private static final ListenerCallQueue.Callback<Service.Listener> STOPPING_FROM_STARTING_CALLBACK;
   private static final ListenerCallQueue.Callback<Service.Listener> STOPPING_FROM_RUNNING_CALLBACK;
   private static final ListenerCallQueue.Callback<Service.Listener> TERMINATED_FROM_NEW_CALLBACK;
   private static final ListenerCallQueue.Callback<Service.Listener> TERMINATED_FROM_RUNNING_CALLBACK;
   private static final ListenerCallQueue.Callback<Service.Listener> TERMINATED_FROM_STOPPING_CALLBACK;
   private final Monitor monitor = new Monitor();
   private final Monitor.Guard isStartable;
   private final Monitor.Guard isStoppable;
   private final Monitor.Guard hasReachedRunning;
   private final Monitor.Guard isStopped;
   @GuardedBy("monitor")
   private final List<ListenerCallQueue<Service.Listener>> listeners;
   @GuardedBy("monitor")
   private volatile AbstractService.StateSnapshot snapshot;

   private static ListenerCallQueue.Callback<Service.Listener> terminatedCallback(final Service.State from) {
      String var1 = String.valueOf(String.valueOf(from));
      return new ListenerCallQueue.Callback<Service.Listener>((new StringBuilder(21 + var1.length())).append("terminated({from = ").append(var1).append("})").toString()) {
         void call(Service.Listener listener) {
            listener.terminated(from);
         }
      };
   }

   private static ListenerCallQueue.Callback<Service.Listener> stoppingCallback(final Service.State from) {
      String var1 = String.valueOf(String.valueOf(from));
      return new ListenerCallQueue.Callback<Service.Listener>((new StringBuilder(19 + var1.length())).append("stopping({from = ").append(var1).append("})").toString()) {
         void call(Service.Listener listener) {
            listener.stopping(from);
         }
      };
   }

   protected AbstractService() {
      this.isStartable = new Monitor.Guard(this.monitor) {
         public boolean isSatisfied() {
            return AbstractService.this.state() == Service.State.NEW;
         }
      };
      this.isStoppable = new Monitor.Guard(this.monitor) {
         public boolean isSatisfied() {
            return AbstractService.this.state().compareTo(Service.State.RUNNING) <= 0;
         }
      };
      this.hasReachedRunning = new Monitor.Guard(this.monitor) {
         public boolean isSatisfied() {
            return AbstractService.this.state().compareTo(Service.State.RUNNING) >= 0;
         }
      };
      this.isStopped = new Monitor.Guard(this.monitor) {
         public boolean isSatisfied() {
            return AbstractService.this.state().isTerminal();
         }
      };
      this.listeners = Collections.synchronizedList(new ArrayList());
      this.snapshot = new AbstractService.StateSnapshot(Service.State.NEW);
   }

   protected abstract void doStart();

   protected abstract void doStop();

   public final Service startAsync() {
      if (this.monitor.enterIf(this.isStartable)) {
         try {
            this.snapshot = new AbstractService.StateSnapshot(Service.State.STARTING);
            this.starting();
            this.doStart();
         } catch (Throwable var5) {
            this.notifyFailed(var5);
         } finally {
            this.monitor.leave();
            this.executeListeners();
         }

         return this;
      } else {
         String var1 = String.valueOf(String.valueOf(this));
         throw new IllegalStateException((new StringBuilder(33 + var1.length())).append("Service ").append(var1).append(" has already been started").toString());
      }
   }

   public final Service stopAsync() {
      if (this.monitor.enterIf(this.isStoppable)) {
         try {
            Service.State previous = this.state();
            switch(previous) {
            case NEW:
               this.snapshot = new AbstractService.StateSnapshot(Service.State.TERMINATED);
               this.terminated(Service.State.NEW);
               break;
            case STARTING:
               this.snapshot = new AbstractService.StateSnapshot(Service.State.STARTING, true, (Throwable)null);
               this.stopping(Service.State.STARTING);
               break;
            case RUNNING:
               this.snapshot = new AbstractService.StateSnapshot(Service.State.STOPPING);
               this.stopping(Service.State.RUNNING);
               this.doStop();
               break;
            case STOPPING:
            case TERMINATED:
            case FAILED:
               String var2 = String.valueOf(String.valueOf(previous));
               throw new AssertionError((new StringBuilder(45 + var2.length())).append("isStoppable is incorrectly implemented, saw: ").append(var2).toString());
            default:
               String var3 = String.valueOf(String.valueOf(previous));
               throw new AssertionError((new StringBuilder(18 + var3.length())).append("Unexpected state: ").append(var3).toString());
            }
         } catch (Throwable var7) {
            this.notifyFailed(var7);
         } finally {
            this.monitor.leave();
            this.executeListeners();
         }
      }

      return this;
   }

   public final void awaitRunning() {
      this.monitor.enterWhenUninterruptibly(this.hasReachedRunning);

      try {
         this.checkCurrentState(Service.State.RUNNING);
      } finally {
         this.monitor.leave();
      }

   }

   public final void awaitRunning(long timeout, TimeUnit unit) throws TimeoutException {
      if (this.monitor.enterWhenUninterruptibly(this.hasReachedRunning, timeout, unit)) {
         try {
            this.checkCurrentState(Service.State.RUNNING);
         } finally {
            this.monitor.leave();
         }

      } else {
         String var4 = String.valueOf(String.valueOf(this));
         String var5 = String.valueOf(String.valueOf(this.state()));
         throw new TimeoutException((new StringBuilder(66 + var4.length() + var5.length())).append("Timed out waiting for ").append(var4).append(" to reach the RUNNING state. ").append("Current state: ").append(var5).toString());
      }
   }

   public final void awaitTerminated() {
      this.monitor.enterWhenUninterruptibly(this.isStopped);

      try {
         this.checkCurrentState(Service.State.TERMINATED);
      } finally {
         this.monitor.leave();
      }

   }

   public final void awaitTerminated(long timeout, TimeUnit unit) throws TimeoutException {
      if (this.monitor.enterWhenUninterruptibly(this.isStopped, timeout, unit)) {
         try {
            this.checkCurrentState(Service.State.TERMINATED);
         } finally {
            this.monitor.leave();
         }

      } else {
         String var4 = String.valueOf(String.valueOf(this));
         String var5 = String.valueOf(String.valueOf(this.state()));
         throw new TimeoutException((new StringBuilder(65 + var4.length() + var5.length())).append("Timed out waiting for ").append(var4).append(" to reach a terminal state. ").append("Current state: ").append(var5).toString());
      }
   }

   @GuardedBy("monitor")
   private void checkCurrentState(Service.State expected) {
      Service.State actual = this.state();
      if (actual != expected) {
         String var3;
         if (actual == Service.State.FAILED) {
            var3 = String.valueOf(String.valueOf(expected));
            throw new IllegalStateException((new StringBuilder(55 + var3.length())).append("Expected the service to be ").append(var3).append(", but the service has FAILED").toString(), this.failureCause());
         } else {
            var3 = String.valueOf(String.valueOf(expected));
            String var4 = String.valueOf(String.valueOf(actual));
            throw new IllegalStateException((new StringBuilder(37 + var3.length() + var4.length())).append("Expected the service to be ").append(var3).append(", but was ").append(var4).toString());
         }
      }
   }

   protected final void notifyStarted() {
      this.monitor.enter();

      try {
         if (this.snapshot.state != Service.State.STARTING) {
            String var2 = String.valueOf(String.valueOf(this.snapshot.state));
            IllegalStateException failure = new IllegalStateException((new StringBuilder(43 + var2.length())).append("Cannot notifyStarted() when the service is ").append(var2).toString());
            this.notifyFailed(failure);
            throw failure;
         }

         if (this.snapshot.shutdownWhenStartupFinishes) {
            this.snapshot = new AbstractService.StateSnapshot(Service.State.STOPPING);
            this.doStop();
         } else {
            this.snapshot = new AbstractService.StateSnapshot(Service.State.RUNNING);
            this.running();
         }
      } finally {
         this.monitor.leave();
         this.executeListeners();
      }

   }

   protected final void notifyStopped() {
      this.monitor.enter();

      try {
         Service.State previous = this.snapshot.state;
         if (previous != Service.State.STOPPING && previous != Service.State.RUNNING) {
            String var3 = String.valueOf(String.valueOf(previous));
            IllegalStateException failure = new IllegalStateException((new StringBuilder(43 + var3.length())).append("Cannot notifyStopped() when the service is ").append(var3).toString());
            this.notifyFailed(failure);
            throw failure;
         }

         this.snapshot = new AbstractService.StateSnapshot(Service.State.TERMINATED);
         this.terminated(previous);
      } finally {
         this.monitor.leave();
         this.executeListeners();
      }

   }

   protected final void notifyFailed(Throwable cause) {
      Preconditions.checkNotNull(cause);
      this.monitor.enter();

      try {
         Service.State previous = this.state();
         switch(previous) {
         case NEW:
         case TERMINATED:
            String var3 = String.valueOf(String.valueOf(previous));
            throw new IllegalStateException((new StringBuilder(22 + var3.length())).append("Failed while in state:").append(var3).toString(), cause);
         case STARTING:
         case RUNNING:
         case STOPPING:
            this.snapshot = new AbstractService.StateSnapshot(Service.State.FAILED, false, cause);
            this.failed(previous, cause);
         case FAILED:
            break;
         default:
            String var4 = String.valueOf(String.valueOf(previous));
            throw new AssertionError((new StringBuilder(18 + var4.length())).append("Unexpected state: ").append(var4).toString());
         }
      } finally {
         this.monitor.leave();
         this.executeListeners();
      }

   }

   public final boolean isRunning() {
      return this.state() == Service.State.RUNNING;
   }

   public final Service.State state() {
      return this.snapshot.externalState();
   }

   public final Throwable failureCause() {
      return this.snapshot.failureCause();
   }

   public final void addListener(Service.Listener listener, Executor executor) {
      Preconditions.checkNotNull(listener, "listener");
      Preconditions.checkNotNull(executor, "executor");
      this.monitor.enter();

      try {
         if (!this.state().isTerminal()) {
            this.listeners.add(new ListenerCallQueue(listener, executor));
         }
      } finally {
         this.monitor.leave();
      }

   }

   public String toString() {
      String var1 = String.valueOf(String.valueOf(this.getClass().getSimpleName()));
      String var2 = String.valueOf(String.valueOf(this.state()));
      return (new StringBuilder(3 + var1.length() + var2.length())).append(var1).append(" [").append(var2).append("]").toString();
   }

   private void executeListeners() {
      if (!this.monitor.isOccupiedByCurrentThread()) {
         for(int i = 0; i < this.listeners.size(); ++i) {
            ((ListenerCallQueue)this.listeners.get(i)).execute();
         }
      }

   }

   @GuardedBy("monitor")
   private void starting() {
      STARTING_CALLBACK.enqueueOn(this.listeners);
   }

   @GuardedBy("monitor")
   private void running() {
      RUNNING_CALLBACK.enqueueOn(this.listeners);
   }

   @GuardedBy("monitor")
   private void stopping(Service.State from) {
      if (from == Service.State.STARTING) {
         STOPPING_FROM_STARTING_CALLBACK.enqueueOn(this.listeners);
      } else {
         if (from != Service.State.RUNNING) {
            throw new AssertionError();
         }

         STOPPING_FROM_RUNNING_CALLBACK.enqueueOn(this.listeners);
      }

   }

   @GuardedBy("monitor")
   private void terminated(Service.State from) {
      switch(from) {
      case NEW:
         TERMINATED_FROM_NEW_CALLBACK.enqueueOn(this.listeners);
         break;
      case STARTING:
      case TERMINATED:
      case FAILED:
      default:
         throw new AssertionError();
      case RUNNING:
         TERMINATED_FROM_RUNNING_CALLBACK.enqueueOn(this.listeners);
         break;
      case STOPPING:
         TERMINATED_FROM_STOPPING_CALLBACK.enqueueOn(this.listeners);
      }

   }

   @GuardedBy("monitor")
   private void failed(final Service.State from, final Throwable cause) {
      String var3 = String.valueOf(String.valueOf(from));
      String var4 = String.valueOf(String.valueOf(cause));
      (new ListenerCallQueue.Callback<Service.Listener>((new StringBuilder(27 + var3.length() + var4.length())).append("failed({from = ").append(var3).append(", cause = ").append(var4).append("})").toString()) {
         void call(Service.Listener listener) {
            listener.failed(from, cause);
         }
      }).enqueueOn(this.listeners);
   }

   static {
      STOPPING_FROM_STARTING_CALLBACK = stoppingCallback(Service.State.STARTING);
      STOPPING_FROM_RUNNING_CALLBACK = stoppingCallback(Service.State.RUNNING);
      TERMINATED_FROM_NEW_CALLBACK = terminatedCallback(Service.State.NEW);
      TERMINATED_FROM_RUNNING_CALLBACK = terminatedCallback(Service.State.RUNNING);
      TERMINATED_FROM_STOPPING_CALLBACK = terminatedCallback(Service.State.STOPPING);
   }

   @Immutable
   private static final class StateSnapshot {
      final Service.State state;
      final boolean shutdownWhenStartupFinishes;
      @Nullable
      final Throwable failure;

      StateSnapshot(Service.State internalState) {
         this(internalState, false, (Throwable)null);
      }

      StateSnapshot(Service.State internalState, boolean shutdownWhenStartupFinishes, @Nullable Throwable failure) {
         Preconditions.checkArgument(!shutdownWhenStartupFinishes || internalState == Service.State.STARTING, "shudownWhenStartupFinishes can only be set if state is STARTING. Got %s instead.", internalState);
         Preconditions.checkArgument(!(failure != null ^ internalState == Service.State.FAILED), "A failure cause should be set if and only if the state is failed.  Got %s and %s instead.", internalState, failure);
         this.state = internalState;
         this.shutdownWhenStartupFinishes = shutdownWhenStartupFinishes;
         this.failure = failure;
      }

      Service.State externalState() {
         return this.shutdownWhenStartupFinishes && this.state == Service.State.STARTING ? Service.State.STOPPING : this.state;
      }

      Throwable failureCause() {
         Preconditions.checkState(this.state == Service.State.FAILED, "failureCause() is only valid if the service has failed, service is %s", this.state);
         return this.failure;
      }
   }
}
