#include <private/dvr/buffer_hub_queue_producer.h>

#include <base/logging.h>
#include <gui/IProducerListener.h>
#include <gui/Surface.h>

#include <gtest/gtest.h>

namespace android {
namespace dvr {

namespace {

// Default dimensions before setDefaultBufferSize is called by the consumer.
constexpr uint32_t kDefaultWidth = 1;
constexpr uint32_t kDefaultHeight = 1;

// Default format before setDefaultBufferFormat is called by the consumer.
constexpr PixelFormat kDefaultFormat = HAL_PIXEL_FORMAT_RGBA_8888;
constexpr int kDefaultConsumerUsageBits = 0;

// Default transform hint before setTransformHint is called by the consumer.
constexpr uint32_t kDefaultTransformHint = 0;

constexpr int kTestApi = NATIVE_WINDOW_API_CPU;
constexpr int kTestApiOther = NATIVE_WINDOW_API_EGL;
constexpr int kTestApiInvalid = 0xDEADBEEF;
constexpr int kTestProducerUsageBits = 0;
constexpr bool kTestControlledByApp = true;

// Builder pattern to slightly vary *almost* correct input
// -- avoids copying and pasting
struct QueueBufferInputBuilder {
  IGraphicBufferProducer::QueueBufferInput build() {
    return IGraphicBufferProducer::QueueBufferInput(
        mTimestamp, mIsAutoTimestamp, mDataSpace, mCrop, mScalingMode,
        mTransform, mFence);
  }

  QueueBufferInputBuilder& setTimestamp(int64_t timestamp) {
    this->mTimestamp = timestamp;
    return *this;
  }

  QueueBufferInputBuilder& setIsAutoTimestamp(bool isAutoTimestamp) {
    this->mIsAutoTimestamp = isAutoTimestamp;
    return *this;
  }

  QueueBufferInputBuilder& setDataSpace(android_dataspace dataSpace) {
    this->mDataSpace = dataSpace;
    return *this;
  }

  QueueBufferInputBuilder& setCrop(Rect crop) {
    this->mCrop = crop;
    return *this;
  }

  QueueBufferInputBuilder& setScalingMode(int scalingMode) {
    this->mScalingMode = scalingMode;
    return *this;
  }

  QueueBufferInputBuilder& setTransform(uint32_t transform) {
    this->mTransform = transform;
    return *this;
  }

  QueueBufferInputBuilder& setFence(sp<Fence> fence) {
    this->mFence = fence;
    return *this;
  }

 private:
  int64_t mTimestamp{1384888611};
  bool mIsAutoTimestamp{false};
  android_dataspace mDataSpace{HAL_DATASPACE_UNKNOWN};
  Rect mCrop{Rect(kDefaultWidth, kDefaultHeight)};
  int mScalingMode{0};
  uint32_t mTransform{0};
  sp<Fence> mFence{Fence::NO_FENCE};
};

// This is a test that covers our implementation of bufferhubqueue-based
// IGraphicBufferProducer.
class BufferHubQueueProducerTest : public ::testing::Test {
 protected:
  virtual void SetUp() {
    const ::testing::TestInfo* const testInfo =
        ::testing::UnitTest::GetInstance()->current_test_info();
    ALOGD_IF(TRACE, "Begin test: %s.%s", testInfo->test_case_name(),
             testInfo->name());

    auto core = BufferHubQueueCore::Create();
    mProducer = new BufferHubQueueProducer(core);
    ASSERT_TRUE(mProducer != nullptr);
    mSurface = new Surface(mProducer, true);
    ASSERT_TRUE(mSurface != nullptr);
  }

  // Connect to a producer in a 'correct' fashion.
  void ConnectProducer() {
    IGraphicBufferProducer::QueueBufferOutput output;
    // Can connect the first time.
    ASSERT_EQ(NO_ERROR, mProducer->connect(kDummyListener, kTestApi,
                                           kTestControlledByApp, &output));
  }

  // Dequeue a buffer in a 'correct' fashion.
  //   Precondition: Producer is connected.
  void DequeueBuffer(int* outSlot) {
    sp<Fence> fence;
    ASSERT_NO_FATAL_FAILURE(DequeueBuffer(outSlot, &fence));
  }

  void DequeueBuffer(int* outSlot, sp<Fence>* outFence) {
    ASSERT_NE(nullptr, outSlot);
    ASSERT_NE(nullptr, outFence);

    int ret = mProducer->dequeueBuffer(outSlot, outFence, kDefaultWidth,
                                       kDefaultHeight, kDefaultFormat,
                                       kTestProducerUsageBits, nullptr);
    // BUFFER_NEEDS_REALLOCATION can be either on or off.
    ASSERT_EQ(0, ~IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION & ret);

    // Slot number should be in boundary.
    ASSERT_LE(0, *outSlot);
    ASSERT_GT(BufferQueueDefs::NUM_BUFFER_SLOTS, *outSlot);
  }

  // Create a generic "valid" input for queueBuffer
  // -- uses the default buffer format, width, etc.
  static IGraphicBufferProducer::QueueBufferInput CreateBufferInput() {
    return QueueBufferInputBuilder().build();
  }

  const sp<IProducerListener> kDummyListener{new DummyProducerListener};

  sp<BufferHubQueueProducer> mProducer;
  sp<Surface> mSurface;
};

TEST_F(BufferHubQueueProducerTest, ConnectFirst_ReturnsError) {
  IGraphicBufferProducer::QueueBufferOutput output;

  // NULL output returns BAD_VALUE
  EXPECT_EQ(BAD_VALUE, mProducer->connect(kDummyListener, kTestApi,
                                          kTestControlledByApp, nullptr));

  // Invalid API returns bad value
  EXPECT_EQ(BAD_VALUE, mProducer->connect(kDummyListener, kTestApiInvalid,
                                          kTestControlledByApp, &output));
}

TEST_F(BufferHubQueueProducerTest, ConnectAgain_ReturnsError) {
  ASSERT_NO_FATAL_FAILURE(ConnectProducer());

  // Can't connect when there is already a producer connected.
  IGraphicBufferProducer::QueueBufferOutput output;
  EXPECT_EQ(BAD_VALUE, mProducer->connect(kDummyListener, kTestApi,
                                          kTestControlledByApp, &output));
}

TEST_F(BufferHubQueueProducerTest, Disconnect_Succeeds) {
  ASSERT_NO_FATAL_FAILURE(ConnectProducer());

  ASSERT_EQ(NO_ERROR, mProducer->disconnect(kTestApi));
}

TEST_F(BufferHubQueueProducerTest, Disconnect_ReturnsError) {
  ASSERT_NO_FATAL_FAILURE(ConnectProducer());

  // Must disconnect with same API number
  EXPECT_EQ(BAD_VALUE, mProducer->disconnect(kTestApiOther));
  // API must not be out of range
  EXPECT_EQ(BAD_VALUE, mProducer->disconnect(kTestApiInvalid));
}

TEST_F(BufferHubQueueProducerTest, Query_Succeeds) {
  ASSERT_NO_FATAL_FAILURE(ConnectProducer());

  int32_t value = -1;
  EXPECT_EQ(NO_ERROR, mProducer->query(NATIVE_WINDOW_WIDTH, &value));
  EXPECT_EQ(kDefaultWidth, static_cast<uint32_t>(value));

  EXPECT_EQ(NO_ERROR, mProducer->query(NATIVE_WINDOW_HEIGHT, &value));
  EXPECT_EQ(kDefaultHeight, static_cast<uint32_t>(value));

  EXPECT_EQ(NO_ERROR, mProducer->query(NATIVE_WINDOW_FORMAT, &value));
  EXPECT_EQ(kDefaultFormat, value);

  EXPECT_EQ(NO_ERROR,
            mProducer->query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &value));
  EXPECT_LE(0, value);
  EXPECT_GE(BufferQueueDefs::NUM_BUFFER_SLOTS, static_cast<size_t>(value));

  EXPECT_EQ(NO_ERROR,
            mProducer->query(NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &value));
  EXPECT_FALSE(value);  // Can't run behind when we haven't touched the queue

  EXPECT_EQ(NO_ERROR,
            mProducer->query(NATIVE_WINDOW_CONSUMER_USAGE_BITS, &value));
  EXPECT_EQ(kDefaultConsumerUsageBits, value);
}

TEST_F(BufferHubQueueProducerTest, Query_ReturnsError) {
  ASSERT_NO_FATAL_FAILURE(ConnectProducer());

  // One past the end of the last 'query' enum value. Update this if we add more
  // enums.
  const int NATIVE_WINDOW_QUERY_LAST_OFF_BY_ONE = NATIVE_WINDOW_BUFFER_AGE + 1;

  int value;
  // What was out of range
  EXPECT_EQ(BAD_VALUE, mProducer->query(/*what*/ -1, &value));
  EXPECT_EQ(BAD_VALUE, mProducer->query(/*what*/ 0xDEADBEEF, &value));
  EXPECT_EQ(BAD_VALUE,
            mProducer->query(NATIVE_WINDOW_QUERY_LAST_OFF_BY_ONE, &value));

  // Some enums from window.h are 'invalid'
  EXPECT_EQ(BAD_VALUE,
            mProducer->query(NATIVE_WINDOW_QUEUES_TO_WINDOW_COMPOSER, &value));
  EXPECT_EQ(BAD_VALUE, mProducer->query(NATIVE_WINDOW_CONCRETE_TYPE, &value));
  EXPECT_EQ(BAD_VALUE, mProducer->query(NATIVE_WINDOW_DEFAULT_WIDTH, &value));
  EXPECT_EQ(BAD_VALUE, mProducer->query(NATIVE_WINDOW_DEFAULT_HEIGHT, &value));
  EXPECT_EQ(BAD_VALUE, mProducer->query(NATIVE_WINDOW_TRANSFORM_HINT, &value));

  // Value was NULL
  EXPECT_EQ(BAD_VALUE, mProducer->query(NATIVE_WINDOW_FORMAT, /*value*/ NULL));
}

TEST_F(BufferHubQueueProducerTest, Queue_Succeeds) {
  int slot = -1;

  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
  ASSERT_NO_FATAL_FAILURE(DequeueBuffer(&slot));

  // Request the buffer (pre-requisite for queueing)
  sp<GraphicBuffer> buffer;
  ASSERT_EQ(NO_ERROR, mProducer->requestBuffer(slot, &buffer));

  // A generic "valid" input
  IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput();
  IGraphicBufferProducer::QueueBufferOutput output;

  // Queue the buffer back into the BQ
  ASSERT_EQ(NO_ERROR, mProducer->queueBuffer(slot, input, &output));

  EXPECT_EQ(kDefaultWidth, output.width);
  EXPECT_EQ(kDefaultHeight, output.height);
  EXPECT_EQ(kDefaultTransformHint, output.transformHint);

  // BufferHubQueue delivers buffers to consumer immediately.
  EXPECT_EQ(0u, output.numPendingBuffers);

  // Note that BufferHubQueue doesn't support nextFrameNumber as it seems to
  // be a SurfaceFlinger specific optimization.
  EXPECT_EQ(0u, output.nextFrameNumber);

  // Buffer was not in the dequeued state
  EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(slot, input, &output));
}

// Test invalid slot number
TEST_F(BufferHubQueueProducerTest, QueueInvalidSlot_ReturnsError) {
  ASSERT_NO_FATAL_FAILURE(ConnectProducer());

  // A generic "valid" input
  IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput();
  IGraphicBufferProducer::QueueBufferOutput output;

  EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(/*slot*/ -1, input, &output));
  EXPECT_EQ(BAD_VALUE,
            mProducer->queueBuffer(/*slot*/ 0xDEADBEEF, input, &output));
  EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(BufferQueueDefs::NUM_BUFFER_SLOTS,
                                              input, &output));
}

// Slot was not in the dequeued state (all slots start out in Free state)
TEST_F(BufferHubQueueProducerTest, QueueNotDequeued_ReturnsError) {
  ASSERT_NO_FATAL_FAILURE(ConnectProducer());

  IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput();
  IGraphicBufferProducer::QueueBufferOutput output;

  EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(/*slot*/ 0, input, &output));
}

// Slot was enqueued without requesting a buffer
TEST_F(BufferHubQueueProducerTest, QueueNotRequested_ReturnsError) {
  int slot = -1;

  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
  ASSERT_NO_FATAL_FAILURE(DequeueBuffer(&slot));

  IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput();
  IGraphicBufferProducer::QueueBufferOutput output;

  EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(slot, input, &output));
}

// Test when fence was NULL
TEST_F(BufferHubQueueProducerTest, QueueNoFence_ReturnsError) {
  int slot = -1;

  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
  ASSERT_NO_FATAL_FAILURE(DequeueBuffer(&slot));

  sp<GraphicBuffer> buffer;
  ASSERT_EQ(NO_ERROR, mProducer->requestBuffer(slot, &buffer));

  sp<Fence> nullFence = NULL;

  IGraphicBufferProducer::QueueBufferInput input =
      QueueBufferInputBuilder().setFence(nullFence).build();
  IGraphicBufferProducer::QueueBufferOutput output;

  EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(slot, input, &output));
}

// Test scaling mode was invalid
TEST_F(BufferHubQueueProducerTest, QueueTestInvalidScalingMode_ReturnsError) {
  int slot = -1;

  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
  ASSERT_NO_FATAL_FAILURE(DequeueBuffer(&slot));

  sp<GraphicBuffer> buffer;
  ASSERT_EQ(NO_ERROR, mProducer->requestBuffer(slot, &buffer));

  IGraphicBufferProducer::QueueBufferInput input =
      QueueBufferInputBuilder().setScalingMode(-1).build();
  IGraphicBufferProducer::QueueBufferOutput output;

  EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(slot, input, &output));

  input = QueueBufferInputBuilder().setScalingMode(0xDEADBEEF).build();

  EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(slot, input, &output));
}

// Test crop rect is out of bounds of the buffer dimensions
TEST_F(BufferHubQueueProducerTest, QueueCropOutOfBounds_ReturnsError) {
  int slot = -1;

  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
  ASSERT_NO_FATAL_FAILURE(DequeueBuffer(&slot));

  sp<GraphicBuffer> buffer;
  ASSERT_EQ(NO_ERROR, mProducer->requestBuffer(slot, &buffer));

  IGraphicBufferProducer::QueueBufferInput input =
      QueueBufferInputBuilder()
          .setCrop(Rect(kDefaultWidth + 1, kDefaultHeight + 1))
          .build();
  IGraphicBufferProducer::QueueBufferOutput output;

  EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(slot, input, &output));
}

TEST_F(BufferHubQueueProducerTest, CancelBuffer_Succeeds) {
  int slot = -1;
  sp<Fence> fence;

  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
  ASSERT_NO_FATAL_FAILURE(DequeueBuffer(&slot, &fence));

  // Should be able to cancel buffer after a dequeue.
  EXPECT_EQ(NO_ERROR, mProducer->cancelBuffer(slot, fence));
}

TEST_F(BufferHubQueueProducerTest, SetMaxDequeuedBufferCount_Succeeds) {
  return;
  ASSERT_NO_FATAL_FAILURE(ConnectProducer());

  int minUndequeuedBuffers;
  ASSERT_EQ(NO_ERROR, mProducer->query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
                                       &minUndequeuedBuffers));

  const int minBuffers = 1;
  const int maxBuffers =
      BufferQueueDefs::NUM_BUFFER_SLOTS - minUndequeuedBuffers;

  ASSERT_EQ(NO_ERROR, mProducer->setAsyncMode(false))
      << "async mode: " << false;
  ASSERT_EQ(NO_ERROR, mProducer->setMaxDequeuedBufferCount(minBuffers))
      << "bufferCount: " << minBuffers;

  // Should now be able to dequeue up to minBuffers times
  // Should now be able to dequeue up to maxBuffers times
  int slot = -1;
  for (int i = 0; i < minBuffers; ++i) {
    ASSERT_NO_FATAL_FAILURE(DequeueBuffer(&slot));
  }

  ASSERT_EQ(NO_ERROR, mProducer->setMaxDequeuedBufferCount(maxBuffers));

  // queue the first buffer to enable max dequeued buffer count checking
  IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput();
  IGraphicBufferProducer::QueueBufferOutput output;
  sp<GraphicBuffer> buffer;
  ASSERT_EQ(NO_ERROR, mProducer->requestBuffer(slot, &buffer));
  ASSERT_EQ(NO_ERROR, mProducer->queueBuffer(slot, input, &output));

  sp<Fence> fence;
  for (int i = 0; i < maxBuffers; ++i) {
    ASSERT_NO_FATAL_FAILURE(DequeueBuffer(&slot, &fence));
  }

  // Cancel a buffer, so we can decrease the buffer count
  ASSERT_EQ(NO_ERROR, mProducer->cancelBuffer(slot, fence));

  // Should now be able to decrease the max dequeued count by 1
  ASSERT_EQ(NO_ERROR, mProducer->setMaxDequeuedBufferCount(maxBuffers - 1));
}

TEST_F(BufferHubQueueProducerTest, SetMaxDequeuedBufferCount_Fails) {
  ASSERT_NO_FATAL_FAILURE(ConnectProducer());

  int minUndequeuedBuffers;
  ASSERT_EQ(NO_ERROR, mProducer->query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
                                       &minUndequeuedBuffers));

  const int minBuffers = 1;
  const int maxBuffers =
      BufferQueueDefs::NUM_BUFFER_SLOTS - minUndequeuedBuffers;

  ASSERT_EQ(NO_ERROR, mProducer->setAsyncMode(false))
      << "async mode: " << false;
  // Buffer count was out of range
  EXPECT_EQ(BAD_VALUE, mProducer->setMaxDequeuedBufferCount(0))
      << "bufferCount: " << 0;
  EXPECT_EQ(BAD_VALUE, mProducer->setMaxDequeuedBufferCount(maxBuffers + 1))
      << "bufferCount: " << maxBuffers + 1;

  // Set max dequeue count to 2
  ASSERT_EQ(NO_ERROR, mProducer->setMaxDequeuedBufferCount(2));
  // Dequeue 2 buffers
  int slot = -1;
  sp<Fence> fence;
  for (int i = 0; i < 2; i++) {
    ASSERT_EQ(OK, ~IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION &
                      (mProducer->dequeueBuffer(
                          &slot, &fence, kDefaultWidth, kDefaultHeight,
                          kDefaultFormat, kTestProducerUsageBits, nullptr)))
        << "slot: " << slot;
  }

  // Client has too many buffers dequeued
  EXPECT_EQ(BAD_VALUE, mProducer->setMaxDequeuedBufferCount(1))
      << "bufferCount: " << minBuffers;
}

TEST_F(BufferHubQueueProducerTest,
       DisconnectedProducerReturnsError_dequeueBuffer) {
  int slot = -1;
  sp<Fence> fence;

  ASSERT_EQ(NO_INIT, mProducer->dequeueBuffer(&slot, &fence, kDefaultWidth,
                                              kDefaultHeight, kDefaultFormat,
                                              kTestProducerUsageBits, nullptr));
}

TEST_F(BufferHubQueueProducerTest,
       DisconnectedProducerReturnsError_requestBuffer) {
  int slot = -1;
  sp<GraphicBuffer> buffer;

  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
  ASSERT_NO_FATAL_FAILURE(DequeueBuffer(&slot));

  // Shouldn't be able to request buffer after disconnect.
  ASSERT_EQ(NO_ERROR, mProducer->disconnect(kTestApi));
  ASSERT_EQ(NO_INIT, mProducer->requestBuffer(slot, &buffer));
}

TEST_F(BufferHubQueueProducerTest,
       DisconnectedProducerReturnsError_queueBuffer) {
  int slot = -1;
  sp<GraphicBuffer> buffer;

  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
  ASSERT_NO_FATAL_FAILURE(DequeueBuffer(&slot));
  ASSERT_EQ(NO_ERROR, mProducer->requestBuffer(slot, &buffer));

  // A generic "valid" input
  IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput();
  IGraphicBufferProducer::QueueBufferOutput output;

  // Shouldn't be able to queue buffer after disconnect.
  ASSERT_EQ(NO_ERROR, mProducer->disconnect(kTestApi));
  ASSERT_EQ(NO_INIT, mProducer->queueBuffer(slot, input, &output));
}

TEST_F(BufferHubQueueProducerTest,
       DisconnectedProducerReturnsError_cancelBuffer) {
  int slot = -1;
  sp<GraphicBuffer> buffer;

  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
  ASSERT_NO_FATAL_FAILURE(DequeueBuffer(&slot));
  ASSERT_EQ(NO_ERROR, mProducer->requestBuffer(slot, &buffer));

  // Shouldn't be able to cancel buffer after disconnect.
  ASSERT_EQ(NO_ERROR, mProducer->disconnect(kTestApi));
  ASSERT_EQ(NO_INIT, mProducer->cancelBuffer(slot, Fence::NO_FENCE));
}

}  // namespace

}  // namespace dvr
}  // namespace android
