
#include "CesiumGeometry/Availability.h"
#include "CesiumGeometry/AxisAlignedBox.h"
#include "CesiumGeometry/OctreeAvailability.h"
#include "CesiumGeometry/OctreeTileID.h"
#include "CesiumGeometry/QuadtreeAvailability.h"
#include "CesiumGeometry/QuadtreeTileID.h"
#include "CesiumGeometry/Rectangle.h"
#include "CesiumGeometry/TileAvailabilityFlags.h"

#include <catch2/catch.hpp>
#include <gsl/span>

#include <algorithm>
#include <memory>
#include <vector>

using namespace CesiumGeometry;

TEST_CASE("Test AvailabilityUtilities") {
  SECTION("Test countOnesInByte") {
    uint8_t byte = static_cast<uint8_t>(0xFF);
    for (uint8_t i = 0; i <= 8; ++i) {
      REQUIRE(
          AvailabilityUtilities::countOnesInByte(
              static_cast<uint8_t>(byte >> i)) == (8 - i));
    }
  }

  SECTION("Test countOnesInBuffer") {
    std::vector<std::byte> buffer(64);
    for (size_t i = 0; i < 64U; ++i) {
      buffer[i] = static_cast<std::byte>(0xFC);
    }

    // Each byte is 0xFC which has 6 ones.
    // This means there are 6 x 64 = 384 ones total in the buffer.
    uint32_t onesInBuffer = AvailabilityUtilities::countOnesInBuffer(
        gsl::span<std::byte>(&buffer[0], 64));
    REQUIRE(onesInBuffer == 384U);
  }
}

TEST_CASE("Test AvailabilityAccessor") {
  std::vector<std::byte> availabilityBuffer(64);
  for (size_t i = 0; i < 64U; ++i) {
    availabilityBuffer[i] = static_cast<std::byte>(0xFC);
  }

  AvailabilitySubtree subtree{
      ConstantAvailability{true},
      SubtreeBufferView{0, 64, 0},
      ConstantAvailability{false},
      {std::move(availabilityBuffer)}};

  AvailabilityAccessor tileAvailabilityAccessor(
      subtree.tileAvailability,
      subtree);
  AvailabilityAccessor contentAvailabilityAccessor(
      subtree.contentAvailability,
      subtree);
  AvailabilityAccessor subtreeAvailabilityAccessor(
      subtree.subtreeAvailability,
      subtree);

  SECTION("Test constant availability") {
    REQUIRE(tileAvailabilityAccessor.isConstant());
    REQUIRE(tileAvailabilityAccessor.getConstant());
    REQUIRE(!tileAvailabilityAccessor.isBufferView());
    REQUIRE(subtreeAvailabilityAccessor.isConstant());
    REQUIRE(!subtreeAvailabilityAccessor.getConstant());
    REQUIRE(!subtreeAvailabilityAccessor.isBufferView());
  }

  SECTION("Test buffer availability") {
    REQUIRE(!contentAvailabilityAccessor.isConstant());
    REQUIRE(contentAvailabilityAccessor.isBufferView());
    REQUIRE(contentAvailabilityAccessor.size() == 64);
    for (size_t i = 0; i < 64U; ++i) {
      REQUIRE(contentAvailabilityAccessor[i] == static_cast<std::byte>(0xFC));
    }
  }

  SECTION("Test combined buffer availability") {
    // Now try sharing a single buffer between multiple views.
    subtree.tileAvailability = SubtreeBufferView{0, 32, 0};
    subtree.contentAvailability = SubtreeBufferView{32, 32, 0};

    // Recreate accessors.
    tileAvailabilityAccessor =
        AvailabilityAccessor(subtree.tileAvailability, subtree);
    contentAvailabilityAccessor =
        AvailabilityAccessor(subtree.contentAvailability, subtree);

    REQUIRE(!tileAvailabilityAccessor.isConstant());
    REQUIRE(tileAvailabilityAccessor.isBufferView());
    REQUIRE(tileAvailabilityAccessor.size() == 32U);

    REQUIRE(!contentAvailabilityAccessor.isConstant());
    REQUIRE(contentAvailabilityAccessor.isBufferView());
    REQUIRE(contentAvailabilityAccessor.size() == 32U);

    for (size_t i = 0; i < 32U; ++i) {
      REQUIRE(tileAvailabilityAccessor[i] == static_cast<std::byte>(0xFC));
      REQUIRE(contentAvailabilityAccessor[i] == static_cast<std::byte>(0xFC));
    }
  }
}

TEST_CASE("Test OctreeAvailability") {
  // We will test with an octree availability subtree with 3 levels.

  // All tiles in the root subtree will have tile availability.

  // The content availability will require a bitstream with 73 bits, but we
  // will need to align to an 8-byte boundary. So it will take 16 bytes.

  // The subtree availability bitstream will require 512 bits (64 bytes).

  // Tiles with morton index 12, 13, 14, and 15 will not have content.
  // These are tiles 3, 4, 5, 6 in level 2.
  // These are tile IDs (2, 1, 1, 0), (2, 0, 0, 1), (2, 1, 0, 1), and
  // (2, 0, 1, 1).
  std::vector<std::byte> contentAvailabilityBuffer(16);

  // Fill the first 72 bits with ones.
  for (size_t i = 0; i < 9U; ++i) {
    contentAvailabilityBuffer[i] = static_cast<std::byte>(0xFF);
  }

  // Fill just the 72nd bit with one.
  contentAvailabilityBuffer[9] = static_cast<std::byte>(0x01);

  // Set zeroes for bits 12, 13, 14, and 15.
  contentAvailabilityBuffer[1] = static_cast<std::byte>(0x0F);

  OctreeTileID unavailableContentIds[] = {
      OctreeTileID(2, 1, 1, 0),
      OctreeTileID(2, 0, 0, 1),
      OctreeTileID(2, 1, 0, 1),
      OctreeTileID(2, 0, 1, 1)};

  // Child subtrees 44, 45, 46, and 47 will be unavailable.
  // These correspond to tile IDs (3, 2, 0, 3), (3, 3, 0, 3), (3, 2, 1, 3), and
  // (3, 3, 1, 3).
  std::vector<std::byte> subtreeAvailabilityBuffer(64);

  // Fill all bits with ones.
  for (size_t i = 0; i < 64U; ++i) {
    subtreeAvailabilityBuffer[i] = static_cast<std::byte>(0xFF);
  }

  // Fill bits 44, 45, 46, and 47 with zeroes.
  subtreeAvailabilityBuffer[5] = static_cast<std::byte>(0x0F);

  OctreeTileID unavailableSubtreeIds[]{
      OctreeTileID(3, 2, 0, 3),
      OctreeTileID(3, 3, 0, 3),
      OctreeTileID(3, 2, 1, 3),
      OctreeTileID(3, 3, 1, 3)};

  AvailabilitySubtree subtree{
      ConstantAvailability{true},
      SubtreeBufferView{0, 16, 0},
      SubtreeBufferView{0, 64, 1},
      {contentAvailabilityBuffer, subtreeAvailabilityBuffer}};

  OctreeAvailability octreeAvailability(3, 5);
  octreeAvailability.addSubtree(OctreeTileID(0, 0, 0, 0), std::move(subtree));

  AvailabilityNode* pParentNode = octreeAvailability.getRootNode();

  SECTION("Test tile and content availability") {
    for (uint32_t level = 0; level < 3U; ++level) {
      for (uint32_t z = 0; z < (1U << level); ++z) {
        for (uint32_t y = 0; y < (1U << level); ++y) {
          for (uint32_t x = 0; x < (1U << level); ++x) {
            OctreeTileID id(level, x, y, z);
            uint8_t availability = octreeAvailability.computeAvailability(id);
            uint8_t availability2 =
                octreeAvailability.computeAvailability(id, pParentNode);

            REQUIRE(availability == availability2);

            // All tiles should be available.
            REQUIRE(availability & TileAvailabilityFlags::TILE_AVAILABLE);

            // Whether the content should be available
            bool contentShouldBeAvailable = true;
            for (const OctreeTileID& tileID : unavailableContentIds) {
              if (tileID == id) {
                contentShouldBeAvailable = false;
                break;
              }
            }

            REQUIRE(
                (bool)(availability & TileAvailabilityFlags::CONTENT_AVAILABLE) ==
                contentShouldBeAvailable);
          }
        }
      }
    }
  }

  SECTION("Test children subtree availability") {
    // Check child subtree availability, none are loaded yet.
    uint32_t componentLengthAtLevel = 1U << 3;
    for (uint32_t z = 0; z < componentLengthAtLevel; ++z) {
      for (uint32_t y = 0; y < componentLengthAtLevel; ++y) {
        for (uint32_t x = 0; x < componentLengthAtLevel; ++x) {
          OctreeTileID id(3, x, y, z);
          uint8_t availability = octreeAvailability.computeAvailability(id);
          std::optional<uint32_t> childIndex =
              octreeAvailability.findChildNodeIndex(id, pParentNode);

          bool subtreeShouldBeAvailable = true;
          for (const OctreeTileID& tileID : unavailableSubtreeIds) {
            if (tileID == id) {
              subtreeShouldBeAvailable = false;
              break;
            }
          }

          REQUIRE(
              (bool)(availability & TileAvailabilityFlags::SUBTREE_AVAILABLE) ==
              subtreeShouldBeAvailable);

          REQUIRE((childIndex != std::nullopt) == subtreeShouldBeAvailable);
        }
      }
    }
  }

  SECTION("Test children subtree loaded flag") {
    // Mock loaded child subtrees for tile IDs (3, 0, 0, 0), (3, 0, 1, 0), (3,
    // 0, 2, 0), and (3, 1, 2, 1).
    OctreeTileID mockChildrenSubtreeIds[]{
        OctreeTileID(3, 0, 0, 0),
        OctreeTileID(3, 0, 1, 0),
        OctreeTileID(3, 0, 2, 0),
        OctreeTileID(3, 1, 2, 1)};

    SECTION("Use addSubtree(id, newSubtree)") {
      for (const OctreeTileID& mockChildrenSubtreeId : mockChildrenSubtreeIds) {
        AvailabilitySubtree childSubtree{
            ConstantAvailability{true},
            ConstantAvailability{true},
            ConstantAvailability{false},
            {}};

        octreeAvailability.addSubtree(
            mockChildrenSubtreeId,
            std::move(childSubtree));
      }
    }

    SECTION("Use addNode and addLoadedSubtree") {
      for (const OctreeTileID& mockChildrenSubtreeId : mockChildrenSubtreeIds) {
        AvailabilitySubtree childSubtree{
            ConstantAvailability{true},
            ConstantAvailability{true},
            ConstantAvailability{false},
            {}};

        AvailabilityNode* pNode =
            octreeAvailability.addNode(mockChildrenSubtreeId, pParentNode);

        REQUIRE(pNode != nullptr);
        octreeAvailability.addLoadedSubtree(pNode, std::move(childSubtree));
      }
    }

    // Check that the correct child subtrees are noted to be loaded.
    uint32_t componentLengthAtLevel = 1U << 3;
    for (uint32_t z = 0; z < componentLengthAtLevel; ++z) {
      for (uint32_t y = 0; y < componentLengthAtLevel; ++y) {
        for (uint32_t x = 0; x < componentLengthAtLevel; ++x) {
          OctreeTileID id(3, x, y, z);

          // Test computeAvailability
          uint8_t availability = octreeAvailability.computeAvailability(id);

          // Test findChildNode
          AvailabilityNode* pChildNode =
              octreeAvailability.findChildNode(id, pParentNode);

          bool subtreeShouldBeLoaded = false;
          for (const OctreeTileID& tileID : mockChildrenSubtreeIds) {
            if (tileID == id) {
              subtreeShouldBeLoaded = true;
              break;
            }
          }

          REQUIRE(
              (bool)(availability & TileAvailabilityFlags::SUBTREE_LOADED) ==
              subtreeShouldBeLoaded);

          REQUIRE((pChildNode != nullptr) == subtreeShouldBeLoaded);
        }
      }
    }
  }
}

TEST_CASE("Test QuadtreeAvailability") {
  // We will test with a quadtree availability subtree with 3 levels.

  // All tiles in the root subtree will be available.

  // The content availability will require a bitstream with 21 bits, but we
  // will need to byte-align to 8 bytes.

  // The subtree availability bitstream will require 64 bits (exactly 8 bytes).

  // Tiles with morton index 12, 13, 14, and 15 will not have content.
  // These are tiles 7, 8, 9, 10 in level 2.
  // These are tile IDs (2, 3, 1), (2, 0, 2), (2, 1, 2), and (2, 0, 3).
  std::vector<std::byte> contentAvailabilityBuffer = {
      static_cast<std::byte>(0xFF),
      static_cast<std::byte>(0x0F),
      static_cast<std::byte>(0x3F),
      static_cast<std::byte>(0x00),
      static_cast<std::byte>(0x00),
      static_cast<std::byte>(0x00),
      static_cast<std::byte>(0x00),
      static_cast<std::byte>(0x00)};

  QuadtreeTileID unavailableContentIds[] = {
      QuadtreeTileID(2, 3, 1),
      QuadtreeTileID(2, 0, 2),
      QuadtreeTileID(2, 1, 2),
      QuadtreeTileID(2, 0, 3)};

  // Child subtrees 44, 45, 46, and 47 will be unavailable.
  // These correspond to tile IDs (3, 2, 6), (3, 3, 6), (3, 2, 7), and
  // (3, 3, 7).
  std::vector<std::byte> subtreeAvailabilityBuffer = {
      static_cast<std::byte>(0xFF),
      static_cast<std::byte>(0xFF),
      static_cast<std::byte>(0xFF),
      static_cast<std::byte>(0xFF),
      static_cast<std::byte>(0xFF),
      static_cast<std::byte>(0x0F),
      static_cast<std::byte>(0xFF),
      static_cast<std::byte>(0xFF)};

  QuadtreeTileID unavailableSubtreeIds[]{
      QuadtreeTileID(3, 2, 6),
      QuadtreeTileID(3, 3, 6),
      QuadtreeTileID(3, 2, 7),
      QuadtreeTileID(3, 3, 7)};

  AvailabilitySubtree subtree{
      ConstantAvailability{true},
      SubtreeBufferView{0, 8, 0},
      SubtreeBufferView{0, 8, 1},
      {contentAvailabilityBuffer, subtreeAvailabilityBuffer}};

  QuadtreeAvailability quadtreeAvailability(3, 5);
  quadtreeAvailability.addSubtree(QuadtreeTileID(0, 0, 0), std::move(subtree));

  AvailabilityNode* pParentNode = quadtreeAvailability.getRootNode();

  SECTION("Test tile and content availability") {
    for (uint32_t level = 0; level < 3U; ++level) {
      for (uint32_t y = 0; y < (1U << level); ++y) {
        for (uint32_t x = 0; x < (1U << level); ++x) {
          QuadtreeTileID id(level, x, y);
          uint8_t availability = quadtreeAvailability.computeAvailability(id);
          uint8_t availability2 =
              quadtreeAvailability.computeAvailability(id, pParentNode);

          REQUIRE(availability == availability2);

          // All tiles should be available.
          REQUIRE(availability & TileAvailabilityFlags::TILE_AVAILABLE);

          // Whether the content should be available
          bool contentShouldBeAvailable = true;
          for (const QuadtreeTileID& tileID : unavailableContentIds) {
            if (tileID == id) {
              contentShouldBeAvailable = false;
              break;
            }
          }

          REQUIRE(
              (bool)(availability & TileAvailabilityFlags::CONTENT_AVAILABLE) ==
              contentShouldBeAvailable);
        }
      }
    }
  }

  SECTION("Test children subtree availability") {
    // Check child subtree availability, none are loaded yet.
    uint32_t componentLengthAtLevel = 1U << 3;
    for (uint32_t y = 0; y < componentLengthAtLevel; ++y) {
      for (uint32_t x = 0; x < componentLengthAtLevel; ++x) {
        QuadtreeTileID id(3, x, y);
        uint8_t availability = quadtreeAvailability.computeAvailability(id);
        std::optional<uint32_t> childIndex =
            quadtreeAvailability.findChildNodeIndex(id, pParentNode);

        bool subtreeShouldBeAvailable = true;
        for (const QuadtreeTileID& tileID : unavailableSubtreeIds) {
          if (tileID == id) {
            subtreeShouldBeAvailable = false;
            break;
          }
        }

        REQUIRE(
            (bool)(availability & TileAvailabilityFlags::SUBTREE_AVAILABLE) ==
            subtreeShouldBeAvailable);

        REQUIRE((childIndex != std::nullopt) == subtreeShouldBeAvailable);
      }
    }
  }

  SECTION("Test children subtree loaded flag") {
    // Mock loaded child subtrees for tile IDs (3, 0, 0), (3, 0, 1), (3, 0, 2),
    // and (3, 1, 2).
    QuadtreeTileID mockChildrenSubtreeIds[]{
        QuadtreeTileID(3, 0, 0),
        QuadtreeTileID(3, 0, 1),
        QuadtreeTileID(3, 0, 2),
        QuadtreeTileID(3, 1, 2)};

    SECTION("Use addSubtree(id, newSubtree)") {
      for (const QuadtreeTileID& mockChildrenSubtreeId :
           mockChildrenSubtreeIds) {
        AvailabilitySubtree childSubtree{
            ConstantAvailability{true},
            ConstantAvailability{true},
            ConstantAvailability{false},
            {}};

        quadtreeAvailability.addSubtree(
            mockChildrenSubtreeId,
            std::move(childSubtree));
      }
    }

    SECTION("Use addNode and addLoadedSubtree") {
      for (const QuadtreeTileID& mockChildrenSubtreeId :
           mockChildrenSubtreeIds) {
        AvailabilitySubtree childSubtree{
            ConstantAvailability{true},
            ConstantAvailability{true},
            ConstantAvailability{false},
            {}};

        AvailabilityNode* pNode =
            quadtreeAvailability.addNode(mockChildrenSubtreeId, pParentNode);

        REQUIRE(pNode != nullptr);
        quadtreeAvailability.addLoadedSubtree(pNode, std::move(childSubtree));
      }
    }

    // Check that the correct child subtrees are noted to be loaded.
    uint32_t componentLengthAtLevel = 1U << 3;
    for (uint32_t y = 0; y < componentLengthAtLevel; ++y) {
      for (uint32_t x = 0; x < componentLengthAtLevel; ++x) {
        QuadtreeTileID id(3, x, y);

        // Test computeAvailability
        uint8_t availability = quadtreeAvailability.computeAvailability(id);

        // Test findChildNode
        AvailabilityNode* pChildNode =
            quadtreeAvailability.findChildNode(id, pParentNode);

        bool subtreeShouldBeLoaded = false;
        for (const QuadtreeTileID& tileID : mockChildrenSubtreeIds) {
          if (tileID == id) {
            subtreeShouldBeLoaded = true;
            break;
          }
        }

        REQUIRE(
            (bool)(availability & TileAvailabilityFlags::SUBTREE_LOADED) ==
            subtreeShouldBeLoaded);

        REQUIRE((pChildNode != nullptr) == subtreeShouldBeLoaded);
      }
    }
  }
}
