/**
 * This file is part of the CernVM File System.
 */

#include <gtest/gtest.h>

#include <algorithm>
#include <vector>

#include "ingestion/chunk_detector.h"
#include "ingestion/item.h"
#include "ingestion/item_mem.h"
#include "util/prng.h"


class T_ChunkDetectors : public ::testing::Test {
 protected:
  static const size_t data_size_ = 104857600;  // 100 MiB

  void CreateBuffers(const size_t buffer_size) {
    ClearBuffers();

    // make sure we always produce the same test data
    rng_.InitSeed(42);

    // produce some test data
    size_t i = 0;
    while (i < data_size()) {
      BlockItem *buffer = new BlockItem(&item_allocator_);
      buffer->MakeData(std::min(data_size() - i, buffer_size));
      for (size_t j = 0; j < buffer->capacity(); ++j) {
        *(buffer->data() + j) = static_cast<unsigned char>(rng_.Next(256));
      }
      buffer->set_size(buffer->capacity());

      buffers_.push_back(buffer);
      i += buffer->size();
    }
  }

  void CreateZeroBuffers(const size_t buffer_size) {
    ClearBuffers();

    size_t i = 0;
    while (i < data_size()) {
      BlockItem *buffer = new BlockItem(&item_allocator_);
      buffer->MakeData(std::min(data_size() - i, buffer_size));
      memset(buffer->data(), 0, buffer->capacity());
      buffer->set_size(buffer->capacity());

      buffers_.push_back(buffer);
      i += buffer->size();
    }
  }

  virtual void TearDown() { ClearBuffers(); }

  size_t data_size() const { return data_size_; }

 private:
  void ClearBuffers() {
    Buffers::const_iterator i = buffers_.begin();
    Buffers::const_iterator iend = buffers_.end();
    for (; i != iend; ++i) {
      delete *i;
    }
    buffers_.clear();
  }

 protected:
  typedef std::vector<BlockItem *> Buffers;
  Buffers buffers_;

 private:
  Prng rng_;
  ItemAllocator item_allocator_;
};

TEST_F(T_ChunkDetectors, StaticOffsetChunkDetectorSlow) {
  const uint64_t static_chunk_size = 1024;

  StaticOffsetDetector static_offset_detector(static_chunk_size);
  EXPECT_FALSE(static_offset_detector.MightFindChunks(static_chunk_size));
  EXPECT_TRUE(static_offset_detector.MightFindChunks(static_chunk_size + 1));

  unsigned char bytes[static_chunk_size / 2];
  ItemAllocator allocator;
  BlockItem buffer(&allocator);
  buffer.MakeDataCopy(bytes, static_chunk_size / 2);

  uint64_t next_cut_mark = static_offset_detector.FindNextCutMark(&buffer);
  EXPECT_EQ(0U, next_cut_mark);

  next_cut_mark = static_offset_detector.FindNextCutMark(&buffer);
  EXPECT_EQ(0U, next_cut_mark);

  next_cut_mark = static_offset_detector.FindNextCutMark(&buffer);
  EXPECT_EQ(static_cast<uint64_t>(static_chunk_size), next_cut_mark);

  next_cut_mark = static_offset_detector.FindNextCutMark(&buffer);
  EXPECT_EQ(0U, next_cut_mark);

  CreateBuffers(1048576);

  uint64_t next_cut = 0;
  unsigned runs = 2;
  Buffers::const_iterator i = buffers_.begin();
  Buffers::const_iterator iend = buffers_.end();
  for (; i != iend; ++i) {
    while ((next_cut = static_offset_detector.FindNextCutMark(*i)) != 0) {
      EXPECT_EQ(static_cast<uint64_t>(static_chunk_size) * runs, next_cut);
      ++runs;
    }
  }
  EXPECT_EQ(data_size_ / static_chunk_size, runs - 2);
}


TEST_F(T_ChunkDetectors, Xor32) {
  // chunk sizes are not important here!
  Xor32Detector xor32_detector(Xor32Detector::kXor32Window,
                               Xor32Detector::kXor32Window + 1,
                               Xor32Detector::kXor32Window + 2);

  // table of test data:
  //   <input value> , <expected xor32 value>
  const uint32_t value_count = 96;
  uint32_t values[value_count] = {
      5u,   5u,          255u, 245u,        0u,   490u,
      32u,  1012u,       27u,  2035u,       11u,  4077u,
      87u,  8077u,       128u, 16282u,      127u, 32587u,
      224u, 65142u,      63u,  130259u,     11u,  260525u,
      1u,   521051u,     103u, 1042129u,    73u,  2084331u,
      22u,  4168640u,    235u, 8337259u,    17u,  16674503u,
      3u,   33349005u,   90u,  66698048u,   210u, 133396050u,
      163u, 266791943u,  12u,  533583874u,  79u,  1067167819u,
      53u,  2134335651u, 2u,   4268671300u, 100u, 4242375404u,
      193u, 4189783321u, 64u,  4084599410u, 242u, 3874231318u,
      14u,  3453495330u, 111u, 2612023339u, 83u,  929079301u,
      253u, 1858158839u, 207u, 3716317473u, 172u, 3137667822u,
      62u,  1980368354u, 190u, 3960736634u, 114u, 3626505862u,
      39u,  2958044459u, 6u,   1621121616u, 175u, 3242243087u,
      93u,  2189518915u, 238u, 84070504u,   184u, 168140904u,
      84u,  336281732u,  29u,  672563477u,  200u, 1345127138u};

  for (unsigned int i = 0; i < value_count; i += 2) {
    xor32_detector.xor32(static_cast<unsigned char>(values[i]));
    EXPECT_EQ(values[i + 1], xor32_detector.xor32_);
  }
}


TEST_F(T_ChunkDetectors, Xor32ChunkDetectorSlow) {
  const size_t base = 512000;
  const size_t min_chk_size = base;
  const size_t avg_chk_size = base * 2;
  const size_t max_chk_size = base * 4;
  Xor32Detector xor32_detector(min_chk_size, avg_chk_size, max_chk_size);

  EXPECT_FALSE(xor32_detector.MightFindChunks(0));
  EXPECT_FALSE(xor32_detector.MightFindChunks(base));
  EXPECT_TRUE(xor32_detector.MightFindChunks(base + 1));
  EXPECT_TRUE(xor32_detector.MightFindChunks(base * 2));
  EXPECT_TRUE(xor32_detector.MightFindChunks(base * 3));
  EXPECT_TRUE(xor32_detector.MightFindChunks(base * 3 + 1));

  // expected cut marks
  const off_t expected[] = {
      742752,   2790752,  3521796,  4188893,   4940543,   5884591,  7131564,
      8520617,  9159233,  9999783,  11202120,  11778185,  13781177, 15331152,
      16497018, 17410317, 19391774, 20086877,  20763223,  21346744, 22075173,
      22705444, 23557037, 24461261, 25118505,  25663669,  26333579, 27763062,
      28369273, 29586929, 30171732, 30794768,  31811364,  32408599, 33689064,
      34678262, 36410687, 37096663, 37682005,  38269900,  39931592, 41347607,
      42701845, 44350286, 44925654, 45893350,  47770173,  48332690, 49303250,
      49861260, 50727512, 51578678, 52176356,  52949949,  54228704, 54843051,
      55796214, 56694940, 58092222, 59294196,  60381155,  61509784, 62850141,
      63505051, 64168046, 64925423, 66252082,  67232078,  68005369, 68615410,
      69454686, 70276575, 71308716, 73356716,  73946267,  74775664, 75509293,
      77487075, 78606807, 79732133, 80467148,  81185709,  82588680, 83547234,
      84220971, 85992169, 86979928, 87845294,  88392648,  89241756, 89980293,
      91129752, 91891024, 92493793, 93792263,  94393106,  95782791, 96813116,
      97723756, 98618891, 99707336, 101755336, 103405168, 104269441};

  std::vector<size_t> buffer_sizes;
  buffer_sizes.push_back(102400);    // 100kB
  buffer_sizes.push_back(base);      // same as minimal chunk size
  buffer_sizes.push_back(base * 2);  // same as average chunk size
  buffer_sizes.push_back(10485760);  // 10MB

  std::vector<size_t>::const_iterator i = buffer_sizes.begin();
  std::vector<size_t>::const_iterator iend = buffer_sizes.end();
  for (; i != iend; ++i) {
    // create the test data chunked into buffers of size *i
    CreateBuffers(*i);

    // go through all the buffers and check if the chunker produces the
    // expected results
    Xor32Detector detector(base, base * 2, base * 4);
    off_t next_cut = 0;
    off_t last_cut = 0;
    int cut = 0;
    bool fail = false;

    Buffers::const_iterator j = buffers_.begin();
    Buffers::const_iterator jend = buffers_.end();
    for (; !fail && j != jend; ++j) {
      while ((next_cut = detector.FindNextCutMark(*j)) != 0) {
        // check that the chunk size lies in the legal boundaries
        size_t chunk_size = next_cut - last_cut;
        if (max_chk_size < chunk_size) {
          EXPECT_GE(max_chk_size, chunk_size)
              << "too large chunk with buffer size " << *i << " bytes...";
          fail = true;
          break;
        }

        if (min_chk_size > chunk_size) {
          EXPECT_LE(min_chk_size, chunk_size)
              << "too small chunk with buffer size " << *i << " bytes...";
          fail = true;
          break;
        }

        // check that chunk boundary is correct
        const int index = cut++;
        if (expected[index] != next_cut) {
          EXPECT_EQ(expected[index], next_cut)
              << "unexpected cut mark with buffer size " << *i << " bytes...";
          fail = true;
          break;
        }

        last_cut = next_cut;
      }
    }
  }
}


TEST_F(T_ChunkDetectors, Xor32ChunkDetectorZerosBufferPowerOfTwo) {
  // This is a regression test for CVM-957, describing a bug in the XOR 32 chunk
  // detector that crashes with certain input. Namely, if the provided data does
  // not contain any XOR32 cutmarks (i.e. it is cut at 'max chunk size') and the
  // number of bytes are an exact multiple of 'max chunk size'.

  ASSERT_EQ(0u, data_size() % 16);
  ASSERT_EQ(0u, data_size() % 32);
  ASSERT_EQ(0u, data_size() % 64);

  const size_t min_chk_size = data_size() / 64;
  const size_t avg_chk_size = data_size() / 32;
  const size_t max_chk_size = data_size() / 16;
  Xor32Detector xor32_detector(min_chk_size, avg_chk_size, max_chk_size);

  CreateZeroBuffers(512000);

  off_t next_cut = 0;
  bool fail = false;
  Buffers::const_iterator j = buffers_.begin();
  Buffers::const_iterator jend = buffers_.end();
  for (; !fail && j != jend; ++j) {
    while ((next_cut = xor32_detector.FindNextCutMark(*j)) != 0) {
      ASSERT_LE(0u, next_cut);
      EXPECT_EQ(0u, next_cut % max_chk_size);
      // ChunkDetector might decide to cut right in the end of a file. This is
      // because it works on CharBuffer-level and doesn't have a notion about
      // the actual file's size.  Hence: EXPECT_GreaterEqual()
      EXPECT_GE(data_size(), static_cast<size_t>(next_cut));
    }
  }
}
