/*
 * Copyright (C) 2015 Square, 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 okhttp3;

import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import okhttp3.internal.Version;
import okio.ByteString;

/**
 * Exercises the web socket implementation against the <a
 * href="http://autobahn.ws/testsuite/">Autobahn Testsuite</a>.
 */
public final class AutobahnTester {
  private static final String HOST = "ws://localhost:9099";

  public static void main(String... args) throws IOException {
    new AutobahnTester().run();
  }

  final OkHttpClient client = new OkHttpClient();

  private WebSocket newWebSocket(String path, WebSocketListener listener) {
    Request request = new Request.Builder().url(HOST + path).build();
    return client.newWebSocket(request, listener);
  }

  public void run() throws IOException {
    try {
      long count = getTestCount();
      System.out.println("Test count: " + count);

      for (long number = 1; number <= count; number++) {
        runTest(number, count);
      }

      updateReports();
    } finally {
      client.dispatcher().executorService().shutdown();
    }
  }

  private void runTest(final long number, final long count) {
    final CountDownLatch latch = new CountDownLatch(1);
    final AtomicLong startNanos = new AtomicLong();
    newWebSocket("/runCase?case=" + number + "&agent=okhttp", new WebSocketListener() {
      @Override public void onOpen(WebSocket webSocket, Response response) {
        System.out.println("Executing test case " + number + "/" + count);
        startNanos.set(System.nanoTime());
      }

      @Override public void onMessage(final WebSocket webSocket, final ByteString bytes) {
        webSocket.send(bytes);
      }

      @Override public void onMessage(final WebSocket webSocket, final String text) {
        webSocket.send(text);
      }

      @Override public void onClosing(WebSocket webSocket, int code, String reason) {
        webSocket.close(1000, null);
        latch.countDown();
      }

      @Override public void onFailure(WebSocket webSocket, Throwable t, Response response) {
        t.printStackTrace(System.out);
        latch.countDown();
      }
    });
    try {
      if (!latch.await(30, TimeUnit.SECONDS)) {
        throw new IllegalStateException("Timed out waiting for test " + number + " to finish.");
      }
    } catch (InterruptedException e) {
      throw new AssertionError();
    }

    long endNanos = System.nanoTime();
    long tookMs = TimeUnit.NANOSECONDS.toMillis(endNanos - startNanos.get());
    System.out.println("Took " + tookMs + "ms");
  }

  private long getTestCount() throws IOException {
    final CountDownLatch latch = new CountDownLatch(1);
    final AtomicLong countRef = new AtomicLong();
    final AtomicReference<Throwable> failureRef = new AtomicReference<>();
    newWebSocket("/getCaseCount", new WebSocketListener() {
      @Override public void onMessage(WebSocket webSocket, String text) {
        countRef.set(Long.parseLong(text));
      }

      @Override public void onClosing(WebSocket webSocket, int code, String reason) {
        webSocket.close(1000, null);
        latch.countDown();
      }

      @Override public void onFailure(WebSocket webSocket, Throwable t, Response response) {
        failureRef.set(t);
        latch.countDown();
      }
    });
    try {
      if (!latch.await(10, TimeUnit.SECONDS)) {
        throw new IllegalStateException("Timed out waiting for count.");
      }
    } catch (InterruptedException e) {
      throw new AssertionError();
    }
    Throwable failure = failureRef.get();
    if (failure != null) {
      throw new RuntimeException(failure);
    }
    return countRef.get();
  }

  private void updateReports() {
    final CountDownLatch latch = new CountDownLatch(1);
    newWebSocket("/updateReports?agent=" + Version.userAgent(), new WebSocketListener() {
      @Override public void onClosing(WebSocket webSocket, int code, String reason) {
        webSocket.close(1000, null);
        latch.countDown();
      }

      @Override public void onFailure(WebSocket webSocket, Throwable t, Response response) {
        latch.countDown();
      }
    });
    try {
      if (!latch.await(10, TimeUnit.SECONDS)) {
        throw new IllegalStateException("Timed out waiting for count.");
      }
    } catch (InterruptedException e) {
      throw new AssertionError();
    }
  }
}
