// Copyright 2018 The PDFium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stdint.h>

#include <array>
#include <vector>

#include "core/fxcrt/raw_span.h"
#include "core/fxcrt/span.h"
#include "core/fxcrt/unowned_ptr.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using ::testing::AnyOf;
using ::testing::ElementsAre;
using ::testing::ElementsAreArray;

// Tests PDFium-modifications to base::span. The name of this file is
// chosen to avoid collisions with base's span_unittest.cc

TEST(PdfiumSpan, EmptySpan) {
  int stuff[] = {1, 2, 3};
  pdfium::span<int> null_span;
  pdfium::span<int> stuff_span(stuff);
  EXPECT_TRUE(null_span.empty());
  EXPECT_FALSE(stuff_span.empty());
  {
    pdfium::span<int> empty_first_span = stuff_span.first(0u);
    pdfium::span<int> empty_last_span = stuff_span.last(0u);
    pdfium::span<int> empty_sub_span1 = stuff_span.subspan(0u, 0u);
    pdfium::span<int> empty_sub_span2 = stuff_span.subspan(3u, 0u);
    pdfium::span<int> empty_sub_span3 = stuff_span.subspan(3u);
    EXPECT_TRUE(empty_first_span.empty());
    EXPECT_TRUE(empty_last_span.empty());
    EXPECT_TRUE(empty_sub_span1.empty());
    EXPECT_TRUE(empty_sub_span2.empty());
    EXPECT_TRUE(empty_sub_span3.empty());
  }
  {
    pdfium::span<int> empty_first_span = stuff_span.first<0>();
    pdfium::span<int> empty_last_span = stuff_span.last<0>();
    pdfium::span<int> empty_sub_span1 = stuff_span.subspan<0, 0>();
    pdfium::span<int> empty_sub_span2 = stuff_span.subspan<3, 0>();
    pdfium::span<int> empty_sub_span3 = stuff_span.subspan<3>();
    EXPECT_TRUE(empty_first_span.empty());
    EXPECT_TRUE(empty_last_span.empty());
    EXPECT_TRUE(empty_sub_span1.empty());
    EXPECT_TRUE(empty_sub_span2.empty());
    EXPECT_TRUE(empty_sub_span3.empty());
  }
}

TEST(PdfiumSpan, ValidSpan) {
  int stuff[] = {1, 2, 3};
  pdfium::span<int> stuff_span(stuff);
  EXPECT_FALSE(stuff_span.empty());
  {
    pdfium::span<int> first_span = stuff_span.first(2u);
    pdfium::span<int> last_span = stuff_span.last(2u);
    pdfium::span<int> sub_span1 = stuff_span.subspan(1u, 1u);
    pdfium::span<int> sub_span2 = stuff_span.subspan(2u, 1u);
    pdfium::span<int> sub_span3 = stuff_span.subspan(1u);
    EXPECT_THAT(first_span, ElementsAre(1, 2));
    EXPECT_THAT(last_span, ElementsAre(2, 3));
    EXPECT_THAT(sub_span1, ElementsAre(2));
    EXPECT_THAT(sub_span2, ElementsAre(3));
    EXPECT_THAT(sub_span3, ElementsAre(2, 3));
  }
  {
    pdfium::span<int> first_span = stuff_span.first<2>();
    pdfium::span<int> last_span = stuff_span.last<2>();
    pdfium::span<int> sub_span1 = stuff_span.subspan<1, 1>();
    pdfium::span<int> sub_span2 = stuff_span.subspan<2, 1>();
    pdfium::span<int> sub_span3 = stuff_span.subspan<1>();
    EXPECT_THAT(first_span, ElementsAre(1, 2));
    EXPECT_THAT(last_span, ElementsAre(2, 3));
    EXPECT_THAT(sub_span1, ElementsAre(2));
    EXPECT_THAT(sub_span2, ElementsAre(3));
    EXPECT_THAT(sub_span3, ElementsAre(2, 3));
  }
}

// Custom implementation of front()/back().
TEST(PdfiumSpan, FrontBack) {
  int one[] = {1};
  int stuff[] = {1, 2, 3};
  pdfium::span<int> one_span(one);
  pdfium::span<int> stuff_span(stuff);
  EXPECT_EQ(one_span.front(), 1);
  EXPECT_EQ(one_span.back(), 1);
  EXPECT_EQ(stuff_span.front(), 1);
  EXPECT_EQ(stuff_span.back(), 3);
}

TEST(PdfiumSpan, AsByteSpan) {
  int32_t c_style[] = {1, 2};
  const int32_t const_c_style[] = {3, 4};
  std::array<int32_t, 2> cxx_style = {1, 2};
  std::array<const int32_t, 2> const_cxx_style = {3, 4};

  // Don't assume a specific endian architecture.
  EXPECT_THAT(pdfium::as_byte_span(c_style),
              AnyOf(ElementsAre(0, 0, 0, 1, 0, 0, 0, 2),
                    ElementsAre(1, 0, 0, 0, 2, 0, 0, 0)));
  EXPECT_THAT(pdfium::as_writable_byte_span(c_style),
              AnyOf(ElementsAre(0, 0, 0, 1, 0, 0, 0, 2),
                    ElementsAre(1, 0, 0, 0, 2, 0, 0, 0)));
  EXPECT_THAT(pdfium::as_byte_span(cxx_style),
              AnyOf(ElementsAre(0, 0, 0, 1, 0, 0, 0, 2),
                    ElementsAre(1, 0, 0, 0, 2, 0, 0, 0)));
  EXPECT_THAT(pdfium::as_writable_byte_span(cxx_style),
              AnyOf(ElementsAre(0, 0, 0, 1, 0, 0, 0, 2),
                    ElementsAre(1, 0, 0, 0, 2, 0, 0, 0)));
  EXPECT_THAT(pdfium::as_byte_span(const_c_style),
              AnyOf(ElementsAre(0, 0, 0, 3, 0, 0, 0, 4),
                    ElementsAre(3, 0, 0, 0, 4, 0, 0, 0)));
  EXPECT_THAT(pdfium::as_byte_span(const_cxx_style),
              AnyOf(ElementsAre(0, 0, 0, 3, 0, 0, 0, 4),
                    ElementsAre(3, 0, 0, 0, 4, 0, 0, 0)));
}

TEST(PdfiumSpan, GMockMacroCompatibility) {
  int arr1[] = {1, 3, 5};
  int arr2[] = {1, 3, 5};
  std::vector vec1(std::begin(arr1), std::end(arr1));
  std::vector vec2(std::begin(arr2), std::end(arr2));
  pdfium::span<int, 3> static_span1(arr1);
  pdfium::span<int, 3> static_span2(arr2);
  pdfium::span<int> dynamic_span1(vec1);
  pdfium::span<int> dynamic_span2(vec2);

  EXPECT_THAT(arr1, ElementsAreArray(static_span2));
  EXPECT_THAT(arr1, ElementsAreArray(dynamic_span2));

  EXPECT_THAT(vec1, ElementsAreArray(static_span2));
  EXPECT_THAT(vec1, ElementsAreArray(dynamic_span2));

  EXPECT_THAT(static_span1, ElementsAre(1, 3, 5));
  EXPECT_THAT(static_span1, ElementsAreArray(arr2));
  EXPECT_THAT(static_span1, ElementsAreArray(static_span2));
  EXPECT_THAT(static_span1, ElementsAreArray(dynamic_span2));
  EXPECT_THAT(static_span1, ElementsAreArray(vec2));

  EXPECT_THAT(dynamic_span1, ElementsAre(1, 3, 5));
  EXPECT_THAT(dynamic_span1, ElementsAreArray(arr2));
  EXPECT_THAT(dynamic_span1, ElementsAreArray(static_span2));
  EXPECT_THAT(dynamic_span1, ElementsAreArray(dynamic_span2));
  EXPECT_THAT(dynamic_span1, ElementsAreArray(vec2));
}

TEST(PdfiumSpanDeathTest, EmptySpanIndex) {
  pdfium::span<int> empty_span;
  EXPECT_DEATH(empty_span[0] += 1, "");
}

TEST(PdfiumSpanDeathTest, EmptySpanFront) {
  pdfium::span<int> empty_span;
  EXPECT_DEATH(empty_span.front() += 1, "");
}

TEST(PdfiumSpanDeathTest, EmptySpanBack) {
  pdfium::span<int> empty_span;
  EXPECT_DEATH(empty_span.back() += 1, "");
}

#if defined(UNOWNED_PTR_DANGLING_CHECKS)
namespace {

void CreateDanglingSpan() {
  pdfium::raw_span<int> data_span;
  {
    std::vector<int> data(4);
    data_span = pdfium::span(data);
  }
}

}  // namespace

TEST(PdfiumSpanDeathTest, DanglingReference) {
  EXPECT_DEATH(CreateDanglingSpan(), "");
}
#endif  // defined(UNOWNED_PTR_DANGLING_CHECKS)
