// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/zucchini/encoded_view.h"

#include <iterator>
#include <numeric>
#include <vector>

#include "components/zucchini/image_index.h"
#include "components/zucchini/test_disassembler.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace zucchini {

namespace {

constexpr size_t PADDING = kReferencePaddingProjection;

template <class It1, class It2>
void TestInputIterator(It1 first_expected,
                       It1 last_expected,
                       It2 first_input,
                       It2 last_input) {
  while (first_expected != last_expected && first_input != last_input) {
    EXPECT_EQ(*first_expected, *first_input);
    ++first_expected;
    ++first_input;
  }
  EXPECT_EQ(last_input, first_input);
  EXPECT_EQ(last_expected, first_expected);
}

template <class It1, class It2>
void TestForwardIterator(It1 first_expected,
                         It1 last_expected,
                         It2 first_input,
                         It2 last_input) {
  TestInputIterator(first_expected, last_expected, first_input, last_input);

  while (first_expected != last_expected && first_input != last_input) {
    EXPECT_EQ(*(first_expected++), *(first_input++));
  }
  EXPECT_EQ(last_input, first_input);
  EXPECT_EQ(last_expected, first_expected);
}

template <class It1, class It2>
void TestBidirectionalIterator(It1 first_expected,
                               It1 last_expected,
                               It2 first_input,
                               It2 last_input) {
  TestForwardIterator(first_expected, last_expected, first_input, last_input);

  while (first_expected != last_expected && first_input != last_input) {
    EXPECT_EQ(*(--last_expected), *(--last_input));
  }
  EXPECT_EQ(last_input, first_input);
  EXPECT_EQ(last_expected, first_expected);
}

template <class It1, class It2>
void TestRandomAccessIterator(It1 first_expected,
                              It1 last_expected,
                              It2 first_input,
                              It2 last_input) {
  TestBidirectionalIterator(first_expected, last_expected, first_input,
                            last_input);

  using difference_type = typename std::iterator_traits<It1>::difference_type;

  difference_type expected_size = last_expected - first_expected;
  difference_type input_size = last_input - first_input;
  EXPECT_EQ(expected_size, input_size);

  for (difference_type i = 0; i < expected_size; ++i) {
    EXPECT_EQ(*(first_expected + i), *(first_input + i));
    EXPECT_EQ(first_expected[i], first_input[i]);

    EXPECT_EQ(0 < i, first_input < first_input + i);
    EXPECT_EQ(0 > i, first_input > first_input + i);
    EXPECT_EQ(0 <= i, first_input <= first_input + i);
    EXPECT_EQ(0 >= i, first_input >= first_input + i);

    EXPECT_EQ(expected_size < i, last_input < first_input + i);
    EXPECT_EQ(expected_size > i, last_input > first_input + i);
    EXPECT_EQ(expected_size <= i, last_input <= first_input + i);
    EXPECT_EQ(expected_size >= i, last_input >= first_input + i);

    It2 input = first_input;
    input += i;
    EXPECT_EQ(*input, first_expected[i]);
    input -= i;
    EXPECT_EQ(first_input, input);
    input += i;

    EXPECT_EQ(0 < i, first_input < input);
    EXPECT_EQ(0 > i, first_input > input);
    EXPECT_EQ(0 <= i, first_input <= input);
    EXPECT_EQ(0 >= i, first_input >= input);

    EXPECT_EQ(expected_size < i, last_input < input);
    EXPECT_EQ(expected_size > i, last_input > input);
    EXPECT_EQ(expected_size <= i, last_input <= input);
    EXPECT_EQ(expected_size >= i, last_input >= input);
  }
}

}  // namespace

class EncodedViewTest : public testing::Test {
 protected:
  EncodedViewTest()
      : buffer_(20),
        image_index_(ConstBufferView(buffer_.data(), buffer_.size())) {
    std::iota(buffer_.begin(), buffer_.end(), 0);
    TestDisassembler disasm({2, TypeTag(0), PoolTag(0)},
                            {{1, 0}, {8, 1}, {10, 2}},
                            {4, TypeTag(1), PoolTag(0)}, {{3, 3}},
                            {3, TypeTag(2), PoolTag(1)}, {{12, 4}, {17, 5}});
    image_index_.Initialize(&disasm);
  }

  void CheckView(std::vector<size_t> expected,
                 const EncodedView& encoded_view) const {
    for (offset_t i = 0; i < encoded_view.size(); ++i) {
      EXPECT_EQ(expected[i], encoded_view.Projection(i)) << i;
    }
    TestRandomAccessIterator(expected.begin(), expected.end(),
                             encoded_view.begin(), encoded_view.end());
  }

  std::vector<uint8_t> buffer_;
  ImageIndex image_index_;
};

TEST_F(EncodedViewTest, Unlabeled) {
  EncodedView encoded_view(image_index_);

  encoded_view.SetLabels(PoolTag(0), {0, 0, 0, 0}, 1);
  encoded_view.SetLabels(PoolTag(1), {0, 0}, 1);

  std::vector<size_t> expected = {
      0,                                     // raw
      kBaseReferenceProjection + 0 + 0 * 3,  // ref 0
      PADDING,
      kBaseReferenceProjection + 1 + 0 * 3,  // ref 1
      PADDING,
      PADDING,
      PADDING,
      7,                                     // raw
      kBaseReferenceProjection + 0 + 0 * 3,  // ref 0
      PADDING,
      kBaseReferenceProjection + 0 + 0 * 3,  // ref 0
      PADDING,
      kBaseReferenceProjection + 2 + 0 * 3,  // ref 2
      PADDING,
      PADDING,
      15,  // raw
      16,
      kBaseReferenceProjection + 2 + 0 * 3,  // ref 2
      PADDING,
      PADDING,
  };
  EXPECT_EQ(kBaseReferenceProjection + 3 * 1, encoded_view.Cardinality());
  CheckView(expected, encoded_view);
}

TEST_F(EncodedViewTest, Labeled) {
  EncodedView encoded_view(image_index_);

  encoded_view.SetLabels(PoolTag(0), {0, 2, 1, 2}, 3);
  encoded_view.SetLabels(PoolTag(1), {0, 0}, 1);

  std::vector<size_t> expected = {
      0,                                     // raw
      kBaseReferenceProjection + 0 + 0 * 3,  // ref 0
      PADDING,
      kBaseReferenceProjection + 1 + 2 * 3,  // ref 1
      PADDING,
      PADDING,
      PADDING,
      7,                                     // raw
      kBaseReferenceProjection + 0 + 2 * 3,  // ref 0
      PADDING,
      kBaseReferenceProjection + 0 + 1 * 3,  // ref 0
      PADDING,
      kBaseReferenceProjection + 2 + 0 * 3,  // ref 2
      PADDING,
      PADDING,
      15,  // raw
      16,
      kBaseReferenceProjection + 2 + 0 * 3,  // ref 2
      PADDING,
      PADDING,
  };
  EXPECT_EQ(kBaseReferenceProjection + 3 * 3, encoded_view.Cardinality());
  CheckView(expected, encoded_view);
}

}  // namespace zucchini
