/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */

#include <stdint.h>

#include <string>  // for ::memset()
#define NOMINMAX
#if defined(NOMINMAX)
// ifnore unused macro warning
#endif
#include <cstdint>
#include <memory>

#include <gtest/gtest.h>

#include <geode/CacheFactory.hpp>
#include <geode/DataInput.hpp>

#include "ByteArrayFixture.hpp"
#include "DataInputInternal.hpp"
#include "DataOutputInternal.hpp"
#include "SerializationRegistry.hpp"

namespace {

using apache::geode::client::ByteArray;
using apache::geode::client::CacheableString;
using apache::geode::client::DataInputInternal;
using apache::geode::client::DataOutputInternal;
using apache::geode::client::Serializable;
using apache::geode::client::SerializationRegistry;

class DataInputUnderTest : public DataInputInternal {
 public:
  using DataInputInternal::DataInputInternal;
  ~DataInputUnderTest() noexcept override {}

  virtual const SerializationRegistry &getSerializationRegistry()
      const override {
    return m_serializationRegistry;
  }

 private:
  SerializationRegistry m_serializationRegistry;
};

class TestDataInput {
 public:
  explicit TestDataInput(const char *str)
      : m_byteArray(ByteArray::fromString(str)),
        m_dataInput(m_byteArray.get(), m_byteArray.size()) {}

  void read(uint8_t *value) { *value = m_dataInput.read(); }

  void read(int8_t *value) { *value = m_dataInput.read(); }

  void readBoolean(bool *value) { *value = m_dataInput.readBoolean(); }

  void readBytesOnly(uint8_t *buffer, uint32_t len) {
    m_dataInput.readBytesOnly(buffer, len);
  }

  void readBytesOnly(int8_t *buffer, uint32_t len) {
    m_dataInput.readBytesOnly(buffer, len);
  }

  void readBytes(uint8_t **buffer, int32_t *len) {
    m_dataInput.readBytes(buffer, len);
  }

  void readBytes(int8_t **buffer, int32_t *len) {
    m_dataInput.readBytes(buffer, len);
  }

  int16_t readInt16() { return m_dataInput.readInt16(); }

  int32_t readInt32() { return m_dataInput.readInt32(); }

  void readInt(uint64_t *value) { *value = m_dataInput.readInt64(); }

  int64_t readInt64() { return m_dataInput.readInt64(); }

  int32_t readArrayLen() { return m_dataInput.readArrayLength(); }

  int64_t readUnsignedVL() { return m_dataInput.readUnsignedVL(); }

  float readFloat() { return m_dataInput.readFloat(); }

  double readDouble() { return m_dataInput.readDouble(); }

  bool readNativeBool() { return m_dataInput.readNativeBool(); }

  int32_t readNativeInt32() { return m_dataInput.readNativeInt32(); }

  std::shared_ptr<Serializable> readDirectObject(int8_t typeId = -1) {
    return m_dataInput.readDirectObject(typeId);
  }

  std::shared_ptr<Serializable> readObject() {
    return m_dataInput.readObject();
  }

  void readObject(std::shared_ptr<Serializable> &ptr) {
    m_dataInput.readObject(ptr);
  }

  std::vector<char16_t> readCharArray() { return m_dataInput.readCharArray(); }

  std::vector<std::string> readStringArray() {
    return m_dataInput.readStringArray();
  }

  std::vector<bool> readBooleanArray() {
    return m_dataInput.readBooleanArray();
  }

  void readArrayOfByteArrays(int8_t ***arrayofBytearr, int32_t &arrayLength,
                             int32_t **elementLength) {
    m_dataInput.readArrayOfByteArrays(arrayofBytearr, arrayLength,
                                      elementLength);
  }

  size_t getBytesRead() const { return m_dataInput.getBytesRead(); }

  size_t getBytesRemaining() const { return m_dataInput.getBytesRemaining(); }

  void advanceCursor(int32_t offset) { m_dataInput.advanceCursor(offset); }

  void rewindCursor(int32_t offset) { m_dataInput.rewindCursor(offset); }

  void reset() { m_dataInput.reset(); }

  void setBuffer() { m_dataInput.setBuffer(); }

  template <class CharT = char, class... Tail>
  inline std::basic_string<CharT, Tail...> readUTF() {
    return m_dataInput.readUTF<CharT, Tail...>();
  }

  template <class CharT = char, class... Tail>
  inline std::basic_string<CharT, Tail...> readString() {
    return m_dataInput.readString<CharT, Tail...>();
  }

 private:
  ByteArray m_byteArray;

  DataInputUnderTest m_dataInput;
};

class DataInputTest : public ::testing::Test, protected ByteArrayFixture {
 public:
  ~DataInputTest() noexcept override {}
};

TEST_F(DataInputTest, ThrowsWhenReadingInputWithSizeZero) {
  TestDataInput dataInput("");

  uint8_t aByte = 0U;
  ASSERT_THROW(dataInput.read(&aByte),
               apache::geode::client::OutOfRangeException);
}

TEST_F(DataInputTest, ThrowsWhenReadingMoreBytesThanSizePassedToConstructor) {
  TestDataInput dataInput("01");

  uint8_t aByte = 0U;
  dataInput.read(&aByte);
  EXPECT_EQ(1U, aByte);

  aByte = 0U;
  ASSERT_THROW(dataInput.read(&aByte),
               apache::geode::client::OutOfRangeException);
}

TEST_F(DataInputTest, CanReadUnsignedBytesFromInput) {
  TestDataInput dataInput("FF00");

  uint8_t aByte = 0U;
  dataInput.read(&aByte);
  EXPECT_EQ(aByte, 255);

  aByte = 0U;
  dataInput.read(&aByte);
  EXPECT_EQ(aByte, 0);
}

TEST_F(DataInputTest, CanReadSignedBytesFromInput) {
  TestDataInput dataInput("807F");

  int8_t aByte = 0U;
  dataInput.read(&aByte);
  EXPECT_EQ(aByte, -128);

  aByte = 0;
  dataInput.read(&aByte);
  EXPECT_EQ(aByte, 127);
}

TEST_F(DataInputTest, CanReadABooleanFromInput) {
  bool boolArray[2] = {true, false};
  DataInputUnderTest dataInput(reinterpret_cast<uint8_t *>(boolArray), 2);

  auto aBool = dataInput.readBoolean();
  EXPECT_EQ(aBool, true);

  aBool = dataInput.readBoolean();
  EXPECT_EQ(aBool, false);
}

TEST_F(DataInputTest, CanReadAnArrayOfBytesFromInput) {
  TestDataInput dataInput("010203");

  uint8_t byteArrayCopy[4];
  dataInput.readBytesOnly(byteArrayCopy, 3);
  EXPECT_EQ(byteArrayCopy[0], 1);
  EXPECT_EQ(byteArrayCopy[1], 2);
  EXPECT_EQ(byteArrayCopy[2], 3);
}

TEST_F(DataInputTest,
       ThrowsWhenReadingMoreContinuousBytesThanSizePassedToConstructor) {
  TestDataInput dataInput("010203");

  // fails to read 4 bytes from 3 byte buffer
  uint8_t byteArrayCopy[4];
  ASSERT_THROW(dataInput.readBytesOnly(byteArrayCopy, 4),
               apache::geode::client::OutOfRangeException);
}

TEST_F(DataInputTest, CanReadIntWithAMaxSizeUnsigned64BitIntInput) {
  uint64_t intArray[1] = {std::numeric_limits<uint64_t>::max()};
  DataInputUnderTest dataInput(reinterpret_cast<uint8_t *>(intArray),
                               sizeof(intArray));

  uint64_t aInt = dataInput.readInt64();
  EXPECT_EQ(aInt, std::numeric_limits<uint64_t>::max());
}

TEST_F(DataInputTest, CanReadUTFWithAnUTFStringInput) {
  const std::string expectedString = "foobar";
  DataOutputInternal stream;
  stream.writeUTF(expectedString);

  size_t outputBufferSize;
  auto outputBuffer = stream.getBuffer(&outputBufferSize);

  auto buffer = std::unique_ptr<uint8_t, decltype(free) *>{
      reinterpret_cast<uint8_t *>(
          std::malloc(sizeof(uint8_t) * outputBufferSize)),
      std::free};
  std::memcpy(buffer.get(), outputBuffer, outputBufferSize);

  DataInputUnderTest dataInput(buffer.get(), stream.getBufferLength());
  auto actualString = dataInput.readUTF();

  EXPECT_TRUE(expectedString == actualString);
}

TEST_F(DataInputTest, InputResetCausesPristineRead) {
  TestDataInput dataInput("010203");

  // 1) read byte off buffer
  // 2) then reset buffer back
  uint8_t aByte = 0U;
  dataInput.read(&aByte);
  dataInput.reset();

  // 3) next byte read should be start byte
  aByte = 0U;
  dataInput.read(&aByte);
  EXPECT_EQ(aByte, 1);
}

TEST_F(DataInputTest, InputRewindCausesReplayedRead) {
  TestDataInput dataInput("010203");

  uint8_t aByte = 0U;
  dataInput.read(&aByte);
  dataInput.read(&aByte);
  dataInput.read(&aByte);

  dataInput.rewindCursor(1);

  dataInput.read(&aByte);
  EXPECT_EQ(aByte, 3);
}

TEST_F(DataInputTest, TestReadUint8) {
  TestDataInput dataInput("37");
  uint8_t value = 0U;
  dataInput.read(&value);
  EXPECT_EQ(static_cast<int8_t>(55U), value) << "Correct uint8_t";
}

TEST_F(DataInputTest, TestReadInt8) {
  TestDataInput dataInput("37");
  int8_t value = 0;
  dataInput.read(&value);
  EXPECT_EQ(static_cast<int8_t>(55), value) << "Correct int8_t";
}

TEST_F(DataInputTest, TestReadBoolean) {
  TestDataInput dataInput("01");
  bool value = false;
  dataInput.readBoolean(&value);
  EXPECT_EQ(true, value) << "Correct bool";
}

TEST_F(DataInputTest, TestReadUint8BytesOnly) {
  TestDataInput dataInput("BABEFACE");
  uint8_t buffer[4];
  ::memset(buffer, 0U, 4 * sizeof(uint8_t));
  dataInput.readBytesOnly(buffer, 4);
  EXPECT_EQ(static_cast<uint8_t>(186U), buffer[0]) << "Correct zeroth uint8_t";
  EXPECT_EQ(static_cast<uint8_t>(190U), buffer[1]) << "Correct first uint8_t";
  EXPECT_EQ(static_cast<uint8_t>(250U), buffer[2]) << "Correct second uint8_t";
  EXPECT_EQ(static_cast<uint8_t>(206U), buffer[3]) << "Correct third uint8_t";
}

TEST_F(DataInputTest, TestReadInt8BytesOnly) {
  TestDataInput dataInput("DEADBEEF");
  int8_t buffer[4];
  ::memset(buffer, 0, 4 * sizeof(int8_t));
  dataInput.readBytesOnly(buffer, 4);
  EXPECT_EQ(static_cast<int8_t>(-34), buffer[0]) << "Correct zeroth int8_t";
  EXPECT_EQ(static_cast<int8_t>(-83), buffer[1]) << "Correct first int8_t";
  EXPECT_EQ(static_cast<int8_t>(-66), buffer[2]) << "Correct second int8_t";
  EXPECT_EQ(static_cast<int8_t>(-17), buffer[3]) << "Correct third int8_t";
}

TEST_F(DataInputTest, TestReadUint8Bytes) {
  TestDataInput dataInput("04BABEFACE");
  uint8_t *buffer = nullptr;
  int32_t len = 0;
  dataInput.readBytes(&buffer, &len);
  auto bufferGuard = std::unique_ptr<uint8_t[]>{buffer};
  ASSERT_NE(nullptr, buffer) << "Non-null buffer";
  ASSERT_EQ(4, len) << "Correct length";
  if (buffer) {
    EXPECT_EQ(static_cast<uint8_t>(186U), buffer[0])
        << "Correct zeroth uint8_t";
    EXPECT_EQ(static_cast<uint8_t>(190U), buffer[1]) << "Correct first uint8_t";
    EXPECT_EQ(static_cast<uint8_t>(250U), buffer[2])
        << "Correct second uint8_t";
    EXPECT_EQ(static_cast<uint8_t>(206U), buffer[3]) << "Correct third uint8_t";
  }
}

TEST_F(DataInputTest, TestReadInt8Bytes) {
  TestDataInput dataInput("04DEADBEEF");
  int8_t *buffer = nullptr;
  int32_t len = 0;
  dataInput.readBytes(&buffer, &len);
  auto bufferGuard = std::unique_ptr<int8_t[]>{buffer};
  ASSERT_NE(nullptr, buffer) << "Non-null buffer";
  ASSERT_EQ(4, len) << "Correct length";
  if (buffer) {
    EXPECT_EQ(static_cast<int8_t>(-34), buffer[0]) << "Correct zeroth int8_t";
    EXPECT_EQ(static_cast<int8_t>(-83), buffer[1]) << "Correct first int8_t";
    EXPECT_EQ(static_cast<int8_t>(-66), buffer[2]) << "Correct second int8_t";
    EXPECT_EQ(static_cast<int8_t>(-17), buffer[3]) << "Correct third int8_t";
  }
}

TEST_F(DataInputTest, TestReadIntUint16) {
  TestDataInput dataInput("123456789ABCDEF0");
  uint16_t value = dataInput.readInt16();
  EXPECT_EQ(static_cast<uint16_t>(4660U), value) << "Correct uint16_t";
}

TEST_F(DataInputTest, TestReadIntInt16) {
  TestDataInput dataInput("123456789ABCDEF0");
  int16_t value = dataInput.readInt16();
  EXPECT_EQ(static_cast<int16_t>(4660), value) << "Correct int16_t";
}

TEST_F(DataInputTest, TestReadIntUint32) {
  TestDataInput dataInput("123456789ABCDEF0");
  uint32_t value = dataInput.readInt32();
  EXPECT_EQ(static_cast<uint32_t>(305419896U), value) << "Correct uint32_t";
}

TEST_F(DataInputTest, TestReadIntInt32) {
  TestDataInput dataInput("123456789ABCDEF0");
  int32_t value = dataInput.readInt32();
  EXPECT_EQ(static_cast<int32_t>(305419896), value) << "Correct int32_t";
}

TEST_F(DataInputTest, TestReadIntUint64) {
  TestDataInput dataInput("123456789ABCDEF0");
  uint64_t value = 0U;
  dataInput.readInt(&value);
  EXPECT_EQ(static_cast<uint64_t>(1311768467463790320U), value)
      << "Correct uint64_t";
}

TEST_F(DataInputTest, TestReadIntInt64) {
  TestDataInput dataInput("123456789ABCDEF0");
  int64_t value = dataInput.readInt64();
  EXPECT_EQ(static_cast<int64_t>(1311768467463790320), value)
      << "Correct int64_t";
}

TEST_F(DataInputTest, TestReadArrayLen) {
  int32_t len = 0;

  TestDataInput dataInput0("FF12345678");
  len = dataInput0.readArrayLen();
  EXPECT_EQ(-1, len) << "Correct length for 0xFF";

  TestDataInput dataInput1("FE12345678");
  len = dataInput1.readArrayLen();
  EXPECT_EQ(4660, len) << "Correct length for 0xFE";

  TestDataInput dataInput2("FD12345678");
  len = dataInput2.readArrayLen();
  EXPECT_EQ(305419896, len) << "Correct length for 0xFD";

  TestDataInput dataInput3("FC12345678");
  len = dataInput3.readArrayLen();
  EXPECT_EQ(252, len) << "Correct length for 0xFC";
}

TEST_F(DataInputTest, TestReadUnsignedVL) {
  // DataInput::readUnsignedVL() uses a variable-length encoding
  // that uses the top bit of a byte to indicate whether another
  // byte follows. Thus, the 64 bits must be split into 7-bit
  // groups and then the follow bit applied. Note that integer
  // is encoded byte-at-a-time from the smaller end.
  //
  //  0001  0010  0011  0100  0101  0110  0111  1000  1001  1010  1011  1100
  //  1101  1110  1111  0000
  //  0  0010010  0011010  0010101  1001111  0001001  1010101  1110011  0111101
  //  1110000
  // 00    12       1A       15       4F       09       55       73       3D 70
  // 00    92       9A       95       CF       89       D5       F3       BD F0

  TestDataInput dataInput("F0BDF3D589CF959A9200");
  int64_t value = dataInput.readUnsignedVL();
  EXPECT_EQ(static_cast<int64_t>(1311768467463790320), value)
      << "Correct int64_t";
}

TEST_F(DataInputTest, TestReadFloat) {
  TestDataInput dataInput("123456789ABCDEF0");
  float value = dataInput.readFloat();
  EXPECT_FLOAT_EQ(5.6904566e-28F, value) << "Correct float";
}

TEST_F(DataInputTest, TestReadDouble) {
  TestDataInput dataInput("123456789ABCDEF0");
  double value = 0.;
  value = dataInput.readDouble();
  EXPECT_DOUBLE_EQ(5.626349274901198e-221, value) << "Correct double";
}

TEST_F(DataInputTest, TestReadUTFNarrow) {
  TestDataInput dataInput(
      "001B596F7520686164206D65206174206D65617420746F726E61646F2E");
  auto value = dataInput.readUTF();
  ASSERT_EQ(static_cast<size_t>(27), value.length()) << "Correct length";
  EXPECT_EQ("You had me at meat tornado.", value) << "Correct string";
}

TEST_F(DataInputTest, TestReadObjectSharedPtr) {
  TestDataInput dataInput(
      "57001B596F7520686164206D65206174206D65617420746F726E61646F2E");
  auto objptr =
      std::dynamic_pointer_cast<CacheableString>(dataInput.readObject());
  EXPECT_EQ("You had me at meat tornado.", objptr->value())
      << "Correct const char *";
}

TEST_F(DataInputTest, TestReadNativeBool) {
  TestDataInput dataInput("0001");
  const bool value = dataInput.readNativeBool();
  EXPECT_EQ(true, value) << "Correct bool";
}

TEST_F(DataInputTest, TestReadNativeInt32) {
  TestDataInput dataInput("0012345678");
  const int32_t value = dataInput.readNativeInt32();
  EXPECT_EQ(static_cast<int32_t>(305419896), value) << "Correct int32_t";
}

TEST_F(DataInputTest, TestReadDirectObject) {
  TestDataInput dataInput(
      "57001B596F7520686164206D65206174206D65617420746F726E61646F2E");
  auto objptr = dataInput.readDirectObject();
  EXPECT_EQ("You had me at meat tornado.",
            std::dynamic_pointer_cast<CacheableString>(objptr)->toString())
      << "Correct const char *";
}

TEST_F(DataInputTest, TestReadObjectSerializablePtr) {
  TestDataInput dataInput(
      "57001B596F7520686164206D65206174206D65617420746F726E61646F2E");
  std::shared_ptr<Serializable> objptr;
  dataInput.readObject(objptr);
  EXPECT_EQ("You had me at meat tornado.",
            std::dynamic_pointer_cast<CacheableString>(objptr)->toString())
      << "Correct const char *";
}

TEST_F(DataInputTest, TestReadCharArray) {
  TestDataInput dataInput(
      "1C0059006F007500200068006100640020006D00650020006100740020006D0065006100"
      "7400200074006F0072006E00610064006F002E0000");
  auto value = dataInput.readCharArray();
  ASSERT_EQ(static_cast<size_t>(28), value.size()) << "Correct length";
  EXPECT_EQ(std::u16string(u"You had me at meat tornado."),
            std::u16string(value.data()))
      << "Correct const char *";
}

TEST_F(DataInputTest, TestReadString) {
  TestDataInput dataInput(
      "57001B596F7520686164206D65206174206D65617420746F726E61646F2E");
  auto value = dataInput.readString();
  EXPECT_EQ("You had me at meat tornado.", value) << "Correct std::string";
}

TEST_F(DataInputTest, TestReadStringToUtf8String) {
  auto expected = std::string(u8"You had me at");
  expected.push_back(0);
  expected.append(u8"meat tornad\u00F6!\U000F0000");

  TestDataInput dataInput(
      "2A0023596F7520686164206D65206174C0806D65617420746F726E6164C3B621EDAE80ED"
      "B080");
  auto str = dataInput.readString();

  EXPECT_EQ(expected, str);
}

TEST_F(DataInputTest, TestReadStringToUtf16String) {
  auto expected = std::u16string(u"You had me at");
  expected.push_back(0);
  expected.append(u"meat tornad\u00F6!\U000F0000");

  TestDataInput dataInput(
      "2A0023596F7520686164206D65206174C0806D65617420746F726E6164C3B621EDAE80ED"
      "B080");
  auto str = dataInput.readString<char16_t>();

  EXPECT_EQ(expected, str);
}

TEST_F(DataInputTest, TestReadStringToUcs4String) {
  auto expected = std::u32string(U"You had me at");
  expected.push_back(0);
  expected.append(U"meat tornad\u00F6!\U000F0000");

  TestDataInput dataInput(
      "2A0023596F7520686164206D65206174C0806D65617420746F726E6164C3B621EDAE80ED"
      "B080");
  auto str = dataInput.readString<char32_t>();

  EXPECT_EQ(expected, str);
}

TEST_F(DataInputTest, TestReadStringToWideString) {
  auto expected = std::wstring(L"You had me at");
  expected.push_back(0);
  expected.append(L"meat tornad\u00F6!\U000F0000");

  TestDataInput dataInput(
      "2A0023596F7520686164206D65206174C0806D65617420746F726E6164C3B621EDAE80ED"
      "B080");
  auto str = dataInput.readString<wchar_t>();

  EXPECT_EQ(expected, str);
}

TEST_F(DataInputTest, TestReadStringArray) {
  TestDataInput dataInput(
      "0157001B596F7520686164206D65206174206D65617420746F726E61646F2E");
  auto value = dataInput.readStringArray();
  ASSERT_EQ(static_cast<size_t>(1), value.size()) << "Correct length";
  EXPECT_EQ("You had me at meat tornado.", value[0]) << "Correct char *";
}

TEST_F(DataInputTest, TestReadArrayOfByteArrays) {
  TestDataInput dataInput("0104DEADBEEF");
  int8_t **arrayOfByteArrays = nullptr;
  int32_t arrayLength = 0;
  int32_t *elementLength = nullptr;
  dataInput.readArrayOfByteArrays(&arrayOfByteArrays, arrayLength,
                                  &elementLength);
  auto arrayOfByteArraysGuard =
      std::unique_ptr<int8_t *[]> { arrayOfByteArrays };
  auto elementLengthGuard = std::unique_ptr<int32_t[]>{elementLength};

  ASSERT_NE(nullptr, arrayOfByteArrays) << "Non-null array of byte arrays";
  ASSERT_EQ(1, arrayLength) << "Correct array length";
  if (arrayOfByteArrays) {
    EXPECT_NE(static_cast<int8_t *>(nullptr), arrayOfByteArrays[0])
        << "Non-null first byte array";
    ASSERT_EQ(4, elementLength[0]) << "Correct length";
    EXPECT_EQ(static_cast<int8_t>(-34), arrayOfByteArrays[0][0])
        << "Correct zeroth int8_t";
    EXPECT_EQ(static_cast<int8_t>(-83), arrayOfByteArrays[0][1])
        << "Correct first int8_t";
    EXPECT_EQ(static_cast<int8_t>(-66), arrayOfByteArrays[0][2])
        << "Correct second int8_t";
    EXPECT_EQ(static_cast<int8_t>(-17), arrayOfByteArrays[0][3])
        << "Correct third int8_t";
  }
}

TEST_F(DataInputTest, TestGetBytesRead) {
  TestDataInput dataInput("123456789ABCDEF0");
  EXPECT_EQ(static_cast<size_t>(0), dataInput.getBytesRead())
      << "Correct bytes read before any reads";
  uint8_t value = 0U;
  dataInput.read(&value);
  dataInput.read(&value);
  dataInput.read(&value);
  dataInput.read(&value);
  EXPECT_EQ(static_cast<size_t>(4), dataInput.getBytesRead())
      << "Correct bytes read after half of the reads";
  dataInput.read(&value);
  dataInput.read(&value);
  dataInput.read(&value);
  dataInput.read(&value);
  EXPECT_EQ(static_cast<size_t>(8), dataInput.getBytesRead())
      << "Correct bytes read after all of the reads";
}

TEST_F(DataInputTest, TestGetBytesRemaining) {
  TestDataInput dataInput("123456789ABCDEF0");
  EXPECT_EQ(static_cast<size_t>(8), dataInput.getBytesRemaining())
      << "Correct bytes remaining before any reads";
  uint8_t value = 0U;
  dataInput.read(&value);
  dataInput.read(&value);
  dataInput.read(&value);
  dataInput.read(&value);
  EXPECT_EQ(static_cast<size_t>(4), dataInput.getBytesRemaining())
      << "Correct bytes remaining after half of the reads";
  dataInput.read(&value);
  dataInput.read(&value);
  dataInput.read(&value);
  dataInput.read(&value);
  EXPECT_EQ(static_cast<size_t>(0), dataInput.getBytesRemaining())
      << "Correct bytes remaining after all of the reads";
}

TEST_F(DataInputTest, TestAdvanceCursor) {
  TestDataInput dataInput("123456789ABCDEF0");
  EXPECT_EQ(static_cast<size_t>(0), dataInput.getBytesRead())
      << "Correct bytes read before any advancement";
  EXPECT_EQ(static_cast<size_t>(8), dataInput.getBytesRemaining())
      << "Correct bytes remaining before any advancement";
  dataInput.advanceCursor(5);
  EXPECT_EQ(static_cast<size_t>(5), dataInput.getBytesRead())
      << "Correct bytes read after forward advancement";
  EXPECT_EQ(static_cast<size_t>(3), dataInput.getBytesRemaining())
      << "Correct bytes remaining after forward advancement";
  dataInput.advanceCursor(-3);
  EXPECT_EQ(static_cast<size_t>(2), dataInput.getBytesRead())
      << "Correct bytes read after rearward advancement";
  EXPECT_EQ(static_cast<size_t>(6), dataInput.getBytesRemaining())
      << "Correct bytes remaining after rearward advancement";
}

TEST_F(DataInputTest, TestRewindCursor) {
  TestDataInput dataInput("123456789ABCDEF0");
  EXPECT_EQ(static_cast<size_t>(0), dataInput.getBytesRead())
      << "Correct bytes read before any rewinding";
  EXPECT_EQ(static_cast<size_t>(8), dataInput.getBytesRemaining())
      << "Correct bytes remaining before any rewinding";
  dataInput.rewindCursor(-5);
  EXPECT_EQ(static_cast<size_t>(5), dataInput.getBytesRead())
      << "Correct bytes read after forward rewinding";
  EXPECT_EQ(static_cast<size_t>(3), dataInput.getBytesRemaining())
      << "Correct bytes remaining after forward rewinding";
  dataInput.rewindCursor(3);
  EXPECT_EQ(static_cast<size_t>(2), dataInput.getBytesRead())
      << "Correct bytes read after rearward rewinding";
  EXPECT_EQ(static_cast<size_t>(6), dataInput.getBytesRemaining())
      << "Correct bytes remaining after rearward rewinding";
}

TEST_F(DataInputTest, TestReset) {
  TestDataInput dataInput("123456789ABCDEF0");
  EXPECT_EQ(static_cast<size_t>(0), dataInput.getBytesRead())
      << "Correct bytes read before any reads";
  EXPECT_EQ(static_cast<size_t>(8), dataInput.getBytesRemaining())
      << "Correct bytes remaining before any reads";
  uint8_t value = 0U;
  dataInput.read(&value);
  dataInput.read(&value);
  dataInput.read(&value);
  dataInput.read(&value);
  EXPECT_EQ(static_cast<size_t>(4), dataInput.getBytesRead())
      << "Correct bytes read after the reads";
  EXPECT_EQ(static_cast<size_t>(4), dataInput.getBytesRemaining())
      << "Correct bytes remaining after the reads";
  dataInput.reset();
  EXPECT_EQ(static_cast<size_t>(0), dataInput.getBytesRead())
      << "Correct bytes read after the reset";
  EXPECT_EQ(static_cast<size_t>(8), dataInput.getBytesRemaining())
      << "Correct bytes remaining after the reset";
}

TEST_F(DataInputTest, TestSetBuffer) {
  TestDataInput dataInput("123456789ABCDEF0");
  EXPECT_EQ(static_cast<size_t>(0), dataInput.getBytesRead())
      << "Correct bytes read before any reads";
  EXPECT_EQ(static_cast<size_t>(8), dataInput.getBytesRemaining())
      << "Correct bytes remaining before any reads";
  uint8_t value = 0U;
  dataInput.read(&value);
  dataInput.read(&value);
  dataInput.read(&value);
  dataInput.read(&value);
  EXPECT_EQ(static_cast<size_t>(4), dataInput.getBytesRead())
      << "Correct bytes read after the reads";
  EXPECT_EQ(static_cast<size_t>(4), dataInput.getBytesRemaining())
      << "Correct bytes remaining after the reads";
  dataInput.setBuffer();
  EXPECT_EQ(static_cast<size_t>(4), dataInput.getBytesRead())
      << "Correct bytes read after the setting";
  EXPECT_EQ(static_cast<size_t>(0), dataInput.getBytesRemaining())
      << "Correct bytes remaining after the setting";
}

TEST_F(DataInputTest, TestReadNullArray) {
  TestDataInput dataInput("FF12345678");

  EXPECT_EQ(static_cast<size_t>(0), dataInput.readBooleanArray().size());
}

}  // namespace
