/**
 * Copyright 2013 Twitter, Inc.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 **/

package com.twitter.hbc.httpclient;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.twitter.hbc.RateTracker;
import com.twitter.hbc.ReconnectionManager;
import com.twitter.hbc.SitestreamController;
import com.twitter.hbc.core.Client;
import com.twitter.hbc.core.Hosts;
import com.twitter.hbc.core.StatsReporter;
import com.twitter.hbc.core.endpoint.StreamingEndpoint;
import com.twitter.hbc.core.event.Event;
import com.twitter.hbc.core.processor.HosebirdMessageProcessor;
import com.twitter.hbc.httpclient.auth.Authentication;
import org.apache.http.HttpException;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.DecompressingHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HttpContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import java.io.IOException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * This class and classes in this package will depend on HttpClient
 */

public class BasicClient implements Client {

  private final static int DEFAULT_STOP_TIMEOUT_MILLIS = 5000;

  private final ExecutorService executorService;

  private final ClientBase clientBase;
  private final AtomicBoolean canRun;

  private final static Logger logger = LoggerFactory.getLogger(BasicClient.class);

  public BasicClient(String name, Hosts hosts, StreamingEndpoint endpoint, Authentication auth, boolean enableGZip, HosebirdMessageProcessor processor,
                     ReconnectionManager reconnectionManager, RateTracker rateTracker, ExecutorService executorService,
                     @Nullable BlockingQueue<Event> eventsQueue, final String userAgent, HttpParams params) {
    Preconditions.checkNotNull(auth);
    HttpClient client;
    DefaultHttpClient defaultClient = new DefaultHttpClient(new PoolingClientConnectionManager(), params);

    /** User-Agent processor */
    defaultClient.addRequestInterceptor(new HttpRequestInterceptor() {
      @Override
      public void process(HttpRequest httpRequest, HttpContext httpContext) throws HttpException, IOException {
        httpRequest.addHeader(HttpHeaders.USER_AGENT, userAgent);
      }
    });

    /** Set auth **/
    auth.setupConnection(defaultClient);

    /** Set up gzip if necessary **/
    if (enableGZip) {
      client = new DecompressingHttpClient(defaultClient);
    } else {
      client = defaultClient;
    }

    this.canRun = new AtomicBoolean(true);
    this.executorService = executorService;
    this.clientBase = new ClientBase(name, client, hosts, endpoint, auth, processor, reconnectionManager, rateTracker, eventsQueue);
  }

  /**
   * For testing only
   */
  @VisibleForTesting
  BasicClient(final ClientBase clientBase, ExecutorService executorService) {
    this.canRun = new AtomicBoolean(true);
    this.clientBase = clientBase;
    this.executorService = executorService;
  }

  /**
   * Forks a new thread to do the IO in.
   * Should handle unexpected disconnects by reconnecting with appropriate backoffs/backfill param
   * Should only be called once.
   **/
  @Override
  public void connect() {
    if (!canRun.get() || clientBase.isDone()) {
      throw new IllegalStateException("There is already a connection thread running for " + this.clientBase);
    }
    canRun.set(false);
    executorService.execute(clientBase);
    logger.info("New connection executed: {}", this.clientBase);
  }

  @Override
  public SitestreamController createSitestreamController() {
    return clientBase.getSitestreamController();
  }

  @Override
  public StatsReporter.StatsTracker getStatsTracker() {
    return clientBase.getStatsTracker();
  }

  public boolean isDone() {
    return clientBase.isDone();
  }

  /**
   * This method should only be called after the client is done
   */
  public Event getExitEvent() {
    return clientBase.getExitEvent();
  }

  /**
   * Stops the current connection. No reconnecting will occur. Kills thread + cleanup.
   * Waits for the thread to end
   **/
  @Override
  public void stop() {
    stop(DEFAULT_STOP_TIMEOUT_MILLIS);
  }

  /**
   * @param waitMillis milliseconds to wait for the client to stop
   */
  @Override
  public void stop(int waitMillis) {
    logger.info("Stopping the client: " + this.clientBase);
    try {
      clientBase.stop(waitMillis);
      logger.info("Successfully stopped the client: {}", this.clientBase);
    } catch (InterruptedException e) {
      logger.info("Thread interrupted when attempting to stop the client: {}", this.clientBase);
    }
    executorService.shutdown();
  }

  @Override
  public String getName() {
    return clientBase.getName();
  }

  @Override
  public StreamingEndpoint getEndpoint() {
    return clientBase.getEndpoint();
  }

  @Override
  public void reconnect() {
    clientBase.reconnect();
  }

  @VisibleForTesting
  boolean waitForFinish(int millis) throws InterruptedException {
    return clientBase.waitForFinish(millis);
  }
}
@@ -4120,12 +4120,33 @@
 Run.
-get(
+compareAndSet(true, false
 ) %7C%7C
@@ -4289,31 +4289,8 @@
   %7D%0A
-    canRun.set(false);%0A
     
@@ -5937,8 +5937,9 @@
 );%0A  %7D%0A%7D
+%0A

Use AtomicBoolean in atomic way