/*
 * Copyright 2018 Google LLC
 *
 * 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
 *
 *     https://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.google.cloud.bigtable.data.v2.it;

import static com.google.common.truth.Truth.assertThat;

import com.google.api.core.SettableApiFuture;
import com.google.api.gax.rpc.ResponseObserver;
import com.google.api.gax.rpc.StreamController;
import com.google.cloud.bigtable.data.v2.it.env.TestEnvRule;
import com.google.cloud.bigtable.data.v2.models.Query;
import com.google.cloud.bigtable.data.v2.models.Row;
import com.google.cloud.bigtable.data.v2.models.RowCell;
import com.google.cloud.bigtable.data.v2.models.RowMutation;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.protobuf.ByteString;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

@RunWith(JUnit4.class)
public class ReadIT {
  private String prefix;

  @ClassRule public static TestEnvRule testEnvRule = new TestEnvRule();

  @Before
  public void setUp() {
    prefix = testEnvRule.env().getRowPrefix();
  }

  @Test
  public void readEmpty() throws Throwable {
    String uniqueKey = prefix + "-readEmpty";

    Query query = Query.create(testEnvRule.env().getTableName().getTable()).rowKey(uniqueKey);

    // Sync
    ArrayList<Row> rows = Lists.newArrayList(testEnvRule.env().getDataClient().readRows(query));
    assertThat(rows).isEmpty();

    // Async
    AccumulatingObserver observer = new AccumulatingObserver();
    testEnvRule.env().getDataClient().readRowsAsync(query, observer);
    observer.awaitCompletion();
    assertThat(observer.responses).isEmpty();
  }

  @Test
  public void read() throws Throwable {
    int numRows = 5;
    List<Row> expectedRows = Lists.newArrayList();
    String uniqueKey = prefix + "-read";

    long timestampMicros = System.nanoTime() * 1_000;

    for (int i = 0; i < numRows; i++) {
      testEnvRule
          .env()
          .getDataClient()
          .mutateRowCallable()
          .call(
              RowMutation.create(testEnvRule.env().getTableName().getTable(), uniqueKey + "-" + i)
                  .setCell(testEnvRule.env().getFamilyId(), "q", timestampMicros, "my-value"));

      expectedRows.add(
          Row.create(
              ByteString.copyFromUtf8(uniqueKey + "-" + i),
              ImmutableList.of(
                  RowCell.create(
                      testEnvRule.env().getFamilyId(),
                      ByteString.copyFromUtf8("q"),
                      timestampMicros,
                      ImmutableList.<String>of(),
                      ByteString.copyFromUtf8("my-value")))));
    }

    // Sync
    Query query =
        Query.create(testEnvRule.env().getTableName().getTable())
            .range(uniqueKey + "-0", uniqueKey + "-" + numRows);
    ArrayList<Row> actualResults =
        Lists.newArrayList(testEnvRule.env().getDataClient().readRows(query));

    assertThat(actualResults).containsExactlyElementsIn(expectedRows);

    // Async
    AccumulatingObserver observer = new AccumulatingObserver();
    testEnvRule.env().getDataClient().readRowsAsync(query, observer);
    observer.awaitCompletion();
    assertThat(observer.responses).containsExactlyElementsIn(expectedRows);
  }

  static class AccumulatingObserver implements ResponseObserver<Row> {
    final List<Row> responses = Lists.newArrayList();
    final SettableApiFuture<Void> completionFuture = SettableApiFuture.create();

    void awaitCompletion() throws Throwable {
      try {
        completionFuture.get(10, TimeUnit.MINUTES);
      } catch (ExecutionException e) {
        throw e.getCause();
      }
    }

    @Override
    public void onStart(StreamController controller) {}

    @Override
    public void onResponse(Row row) {
      responses.add(row);
    }

    @Override
    public void onError(Throwable t) {
      completionFuture.setException(t);
    }

    @Override
    public void onComplete() {
      completionFuture.set(null);
    }
  }
}

@@ -688,24 +688,147 @@
 ssertThat;%0A%0A
+import com.google.api.core.ApiFuture;%0Aimport com.google.api.core.ApiFutureCallback;%0Aimport com.google.api.core.ApiFutures;%0A
 import com.g
@@ -1332,16 +1332,72 @@
 .Lists;%0A
+import com.google.common.util.concurrent.MoreExecutors;%0A
 import c
@@ -1427,16 +1427,16 @@
 String;%0A
-
 import j
@@ -1474,24 +1474,68 @@
 .util.List;%0A
+import java.util.concurrent.CountDownLatch;%0A
 import java.
@@ -1566,24 +1566,24 @@
 nException;%0A
-
 import java.
@@ -1608,16 +1608,66 @@
 meUnit;%0A
+import java.util.concurrent.atomic.AtomicBoolean;%0A
 import o
@@ -4208,16 +4208,758 @@
 );%0A  %7D%0A%0A
+  @Test%0A  public void readSingleNonexistentAsyncCallback() throws Exception %7B%0A    ApiFuture%3CRow%3E future = testEnvRule.env().getDataClient()%0A        .readRowAsync(testEnvRule.env().getTableName().getTable(), %22somenonexistentkey%22);%0A%0A    final AtomicBoolean found = new AtomicBoolean();%0A    final CountDownLatch latch = new CountDownLatch(1);%0A%0A    ApiFutures.addCallback(future, new ApiFutureCallback%3CRow%3E() %7B%0A      @Override%0A      public void onFailure(Throwable t) %7B%0A        latch.countDown();%0A      %7D%0A%0A      @Override%0A      public void onSuccess(Row result) %7B%0A        found.set(true);%0A        latch.countDown();%0A      %7D%0A    %7D, MoreExecutors.directExecutor());%0A%0A    latch.await(1, TimeUnit.MINUTES);%0A    assertThat(found.get()).isTrue();%0A  %7D%0A%0A
   static

Add integration test to test nonexistent row handling (#3870)