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

// TODO(tsepez) this requires a lot more testing.

#include "core/fxcodec/jbig2/JBig2_Image.h"

#include <stdint.h>

#include "testing/gtest/include/gtest/gtest.h"

namespace {

const int32_t kWidthPixels = 80;
const int32_t kWidthBytes = 10;
const int32_t kStrideBytes = kWidthBytes + 2;  // For testing stride != width.
const int32_t kHeightLines = 20;
const int32_t kLargerHeightLines = 100;
const int32_t kTooLargeHeightLines = 40000000;

void CheckImageEq(CJBig2_Image* img1, CJBig2_Image* img2, int line) {
  EXPECT_EQ(img1->width(), img2->width());
  EXPECT_EQ(img1->height(), img2->height());
  for (int32_t y = 0; y < img1->height(); ++y) {
    pdfium::span<const uint8_t> img1_line = img1->GetLine(y);
    pdfium::span<const uint8_t> img2_line = img2->GetLine(y);
    for (int32_t x = 0; x < img1->width(); ++x) {
      EXPECT_EQ(img1->GetPixel(x, img1_line), img2->GetPixel(x, img2_line))
          << " at " << x << " " << y << " actual line " << line;
    }
  }
}

}  // namespace

TEST(fxcodec, EmptyImage) {
  CJBig2_Image empty(0, 0);
  EXPECT_EQ(empty.width(), 0);
  EXPECT_EQ(empty.height(), 0);

  // Out-of-bounds GetLine() returns empty.
  pdfium::span<uint8_t> line0 = empty.GetLine(0);
  pdfium::span<uint8_t> line1 = empty.GetLine(1);
  EXPECT_TRUE(line0.empty());
  EXPECT_TRUE(line1.empty());

  // Out-of-bounds SetPixel() is silent no-op.
  empty.SetPixel(0, line0, true);
  empty.SetPixel(1, line1, true);

  // Out-of-bounds GetPixel returns 0.
  EXPECT_EQ(empty.GetPixel(0, line0), 0);
  EXPECT_EQ(empty.GetPixel(1, line1), 0);
}

TEST(fxcodec, JBig2ImageCreate) {
  CJBig2_Image img(kWidthPixels, kHeightLines);
  EXPECT_EQ(kWidthPixels, img.width());
  EXPECT_EQ(kHeightLines, img.height());
  pdfium::span<uint8_t> line0 = img.GetLine(0);
  EXPECT_EQ(0, img.GetPixel(0, line0));
  ASSERT_EQ(static_cast<size_t>(kStrideBytes), line0.size());
  EXPECT_EQ(0, line0[0]);
  pdfium::span<uint8_t> line_last = img.GetLine(kHeightLines - 1);
  ASSERT_EQ(static_cast<size_t>(kStrideBytes), line_last.size());
  EXPECT_EQ(0, line_last[kWidthBytes - 1]);

  img.SetPixel(0, line0, true);
  img.SetPixel(kWidthPixels - 1, line_last, true);
  EXPECT_EQ(1, img.GetPixel(0, line0));
  EXPECT_EQ(1, img.GetPixel(kWidthPixels - 1, line_last));
  EXPECT_EQ(0x80, line0[0]);
  EXPECT_EQ(0x01, line_last[kWidthBytes - 1]);

  // Out-of-bounds SetPixel() is silent no-op.
  pdfium::span<uint8_t> line1 = img.GetLine(1);
  img.SetPixel(-1, line1, true);
  pdfium::span<uint8_t> line_oob = img.GetLine(kHeightLines);
  img.SetPixel(kWidthPixels, line_oob, true);

  // Out-of-bounds GetPixel returns 0.
  EXPECT_EQ(0, img.GetPixel(-1, img.GetLine(-1)));
  EXPECT_EQ(0, img.GetPixel(kWidthPixels, img.GetLine(kHeightLines)));

  // Out-of-bounds GetLine() returns empty.
  EXPECT_TRUE(img.GetLine(-1).empty());
  EXPECT_TRUE(img.GetLine(kHeightLines).empty());
}

TEST(fxcodec, JBig2ImageCreateTooBig) {
  CJBig2_Image img(kWidthPixels, kTooLargeHeightLines);
  EXPECT_EQ(0, img.width());
  EXPECT_EQ(0, img.height());
  EXPECT_FALSE(img.has_data());
  EXPECT_TRUE(img.span().empty());
}

TEST(fxcodec, JBig2ImageCreateExternal) {
  uint8_t buf[kHeightLines * kStrideBytes];
  CJBig2_Image img(kWidthPixels, kHeightLines, kStrideBytes, buf);
  pdfium::span<uint8_t> line0 = img.GetLine(0);
  pdfium::span<uint8_t> line_last = img.GetLine(kHeightLines - 1);
  img.SetPixel(0, line0, true);
  img.SetPixel(kWidthPixels - 1, line_last, false);
  EXPECT_EQ(kWidthPixels, img.width());
  EXPECT_EQ(kHeightLines, img.height());
  EXPECT_TRUE(img.GetPixel(0, line0));
  EXPECT_FALSE(img.GetPixel(kWidthPixels - 1, line_last));
}

TEST(fxcodec, JBig2ImageCreateExternalTooBig) {
  uint8_t buf[kHeightLines * kStrideBytes];
  CJBig2_Image img(kWidthPixels, kTooLargeHeightLines, kStrideBytes, buf);
  EXPECT_EQ(0, img.width());
  EXPECT_EQ(0, img.height());
  EXPECT_FALSE(img.has_data());
  EXPECT_TRUE(img.span().empty());
}

TEST(fxcodec, JBig2ImageCreateExternalSizeButNoData) {
  CJBig2_Image img(kWidthPixels, kHeightLines, kStrideBytes, {});
  EXPECT_EQ(0, img.width());
  EXPECT_EQ(0, img.height());
  EXPECT_FALSE(img.has_data());
  EXPECT_TRUE(img.span().empty());
}

TEST(fxcodec, JBig2ImageCreateExternalBadStride) {
  uint8_t buf[kHeightLines * kStrideBytes];
  CJBig2_Image img(kWidthPixels, kTooLargeHeightLines, kStrideBytes - 1, buf);
  EXPECT_EQ(0, img.width());
  EXPECT_EQ(0, img.height());
  EXPECT_FALSE(img.has_data());
  EXPECT_TRUE(img.span().empty());
}

TEST(fxcodec, JBig2ImageExpand) {
  CJBig2_Image img(kWidthPixels, kHeightLines);
  pdfium::span<uint8_t> line0 = img.GetLine(0);
  pdfium::span<uint8_t> line_last = img.GetLine(kHeightLines - 1);
  img.SetPixel(0, line0, true);
  img.SetPixel(kWidthPixels - 1, line_last, false);
  img.Expand(kLargerHeightLines, true);
  EXPECT_EQ(kWidthPixels, img.width());
  EXPECT_EQ(kLargerHeightLines, img.height());
  line0 = img.GetLine(0);
  line_last = img.GetLine(kHeightLines - 1);
  EXPECT_TRUE(img.GetPixel(0, line0));
  EXPECT_FALSE(img.GetPixel(kWidthPixels - 1, line_last));
  EXPECT_TRUE(
      img.GetPixel(kWidthPixels - 1, img.GetLine(kLargerHeightLines - 1)));
}

TEST(fxcodec, JBig2ImageExpandTooBig) {
  CJBig2_Image img(kWidthPixels, kHeightLines);
  pdfium::span<uint8_t> line0 = img.GetLine(0);
  pdfium::span<uint8_t> line_last = img.GetLine(kHeightLines - 1);
  img.SetPixel(0, line0, true);
  img.SetPixel(kWidthPixels - 1, line_last, false);
  img.Expand(kTooLargeHeightLines, true);
  EXPECT_EQ(kWidthPixels, img.width());
  EXPECT_EQ(kHeightLines, img.height());
  line0 = img.GetLine(0);
  line_last = img.GetLine(kHeightLines - 1);
  EXPECT_TRUE(img.GetPixel(0, line0));
  EXPECT_FALSE(img.GetPixel(kWidthPixels - 1, line_last));
}

TEST(fxcodec, JBig2ImageExpandExternal) {
  uint8_t buf[kHeightLines * kStrideBytes];
  CJBig2_Image img(kWidthPixels, kHeightLines, kStrideBytes, buf);
  pdfium::span<uint8_t> line0 = img.GetLine(0);
  pdfium::span<uint8_t> line_last = img.GetLine(kHeightLines - 1);
  img.SetPixel(0, line0, true);
  img.SetPixel(kWidthPixels - 1, line_last, false);
  img.Expand(kLargerHeightLines, true);
  EXPECT_EQ(kWidthPixels, img.width());
  EXPECT_EQ(kLargerHeightLines, img.height());
  line0 = img.GetLine(0);
  line_last = img.GetLine(kHeightLines - 1);
  EXPECT_TRUE(img.GetPixel(0, line0));
  EXPECT_FALSE(img.GetPixel(kWidthPixels - 1, line_last));
  EXPECT_TRUE(
      img.GetPixel(kWidthPixels - 1, img.GetLine(kLargerHeightLines - 1)));
}

TEST(fxcodec, JBig2ImageExpandExternalTooBig) {
  uint8_t buf[kHeightLines * kStrideBytes];
  CJBig2_Image img(kWidthPixels, kHeightLines, kStrideBytes, buf);
  pdfium::span<uint8_t> line0 = img.GetLine(0);
  pdfium::span<uint8_t> line_last = img.GetLine(kHeightLines - 1);
  img.SetPixel(0, line0, true);
  img.SetPixel(kWidthPixels - 1, line_last, false);
  img.Expand(kTooLargeHeightLines, true);
  EXPECT_EQ(kWidthPixels, img.width());
  EXPECT_EQ(kHeightLines, img.height());
  line0 = img.GetLine(0);
  line_last = img.GetLine(kHeightLines - 1);
  EXPECT_TRUE(img.GetPixel(0, line0));
  EXPECT_FALSE(img.GetPixel(kWidthPixels - 1, line_last));
}

TEST(fxcodec, JBig2EmptyImage) {
  auto empty = std::make_unique<CJBig2_Image>(0, 0);

  // Empty subimage.
  auto sub1 = empty->SubImage(0, 0, 0, 0);
  EXPECT_EQ(sub1->width(), 0);
  EXPECT_EQ(sub1->height(), 0);

  // Larger dimensions are zero-padded.
  auto sub2 = empty->SubImage(0, 0, 1, 1);
  EXPECT_EQ(1, sub2->width());
  EXPECT_EQ(1, sub2->height());
  EXPECT_EQ(0, sub2->GetPixel(0, sub2->GetLine(0)));

  // Bad dimensions give an empty image.
  sub2 = empty->SubImage(0, 0, -1, -1);
  EXPECT_EQ(sub2->width(), 0);
  EXPECT_EQ(sub2->height(), 0);

  // Bad offsets zero pad the image.
  auto sub3 = empty->SubImage(-1, -1, 2, 2);
  EXPECT_EQ(sub3->width(), 2);
  EXPECT_EQ(sub3->height(), 2);

  // Bad dimensions and bad offsets give an empty image.
  sub3 = empty->SubImage(-1, -1, -100, -100);
  EXPECT_EQ(sub3->width(), 0);
  EXPECT_EQ(sub3->height(), 0);
}

TEST(fxcodec, JBig2SubImage) {
  // clang-format off
  // 1-px wide rectangle in image.
  uint8_t pattern[40] = {
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x01, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x01, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x01, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  };

  // 1-px wide rectangle in image, offset 2 in x.
  uint8_t pattern20[40] = {
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x07, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x04, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x07, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  };

  // 1-px wide rectangle in image, offset 2 in x and y, padded.
  uint8_t pattern22[40] = {
      0x00, 0x04, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x07, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  };

  // 1-px wide rectangle in image, offset 16 in x, 1 in y, padded.
  uint8_t pattern161[40] = {
      0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  };
  // clang-format on

  // Image size a nice clean power of two.
  auto img32 = std::make_unique<CJBig2_Image>(32, 5, 8, pattern);

  // Image size not a nice clean value.
  auto img37 = std::make_unique<CJBig2_Image>(37, 5, 8, pattern);

  // Expected results to check against.
  auto expected20 = std::make_unique<CJBig2_Image>(30, 5, 8, pattern20);
  auto expected22 = std::make_unique<CJBig2_Image>(30, 5, 8, pattern22);
  auto expected161 = std::make_unique<CJBig2_Image>(25, 5, 8, pattern161);
  auto expected_zeros = std::make_unique<CJBig2_Image>(32, 5);

  // Empty subimage.
  auto sub = img32->SubImage(0, 0, 0, 0);
  EXPECT_EQ(sub->width(), 0);
  EXPECT_EQ(sub->height(), 0);

  // Full sub-image.
  sub = img32->SubImage(0, 0, 32, 5);
  EXPECT_EQ(sub->width(), 32);
  EXPECT_EQ(sub->height(), 5);
  CheckImageEq(img32.get(), sub.get(), __LINE__);

  sub = img37->SubImage(0, 0, 32, 5);
  EXPECT_EQ(sub->width(), 32);
  EXPECT_EQ(sub->height(), 5);
  CheckImageEq(img32.get(), sub.get(), __LINE__);

  // Actual bit manipulations.
  sub = img32->SubImage(2, 0, 30, 5);
  CheckImageEq(expected20.get(), sub.get(), __LINE__);

  sub = img37->SubImage(2, 2, 30, 5);
  CheckImageEq(expected22.get(), sub.get(), __LINE__);

  // Fast path.
  sub = img37->SubImage(16, 1, 25, 5);
  CheckImageEq(expected161.get(), sub.get(), __LINE__);

  // Aligned Sub-image including cruft in stride beyond width.
  sub = img37->SubImage(32, 0, 32, 5);
  CheckImageEq(expected_zeros.get(), sub.get(), __LINE__);

  // Sub-image waaaaay beyond width.
  sub = img37->SubImage(2000, 0, 32, 5);
  CheckImageEq(expected_zeros.get(), sub.get(), __LINE__);

  // Sub-image waaaaay beyond height.
  sub = img37->SubImage(0, 2000, 32, 5);
  CheckImageEq(expected_zeros.get(), sub.get(), __LINE__);

  // Sub-image with negative x offset.
  sub = img37->SubImage(-1, 0, 32, 5);
  CheckImageEq(expected_zeros.get(), sub.get(), __LINE__);

  // Sub-image with negative y offset.
  sub = img37->SubImage(0, -1, 32, 5);
  CheckImageEq(expected_zeros.get(), sub.get(), __LINE__);

  // Sub-image with negative width.
  sub = img37->SubImage(-1, 0, 32, 5);
  CheckImageEq(expected_zeros.get(), sub.get(), __LINE__);

  // Sub-image with negative height.
  sub = img37->SubImage(0, -1, 32, 5);
  CheckImageEq(expected_zeros.get(), sub.get(), __LINE__);

  // Sub-image wider than original.
  sub = img37->SubImage(0, 0, 128, 5);
  EXPECT_EQ(128, sub->width());
  EXPECT_EQ(5, sub->height());

  // Sub-image higher than original.
  sub = img37->SubImage(0, 0, 32, 40);
  EXPECT_EQ(32, sub->width());
  EXPECT_EQ(40, sub->height());
}

TEST(fxcodec, JBig2CopyLine) {
  // clang-format off
  // Horizontal line in image.
  uint8_t pattern[24] = {
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x01, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  };

  uint8_t expected_pattern[24] = {
      0x00, 0x01, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  };
  // clang-format on

  auto img = std::make_unique<CJBig2_Image>(37, 3, 8, pattern);
  auto expected = std::make_unique<CJBig2_Image>(37, 3, 8, expected_pattern);

  // Shuffle.
  img->CopyLine(img->GetLine(2), img->GetLine(1));
  img->CopyLine(img->GetLine(1), img->GetLine(0));
  img->CopyLine(img->GetLine(0), img->GetLine(2));

  // Clear top line via invalid |from| offset.
  img->CopyLine(img->GetLine(2), img->GetLine(3));

  // Copies with invalid |to|s don't mess with things.
  img->CopyLine(img->GetLine(-1), img->GetLine(0));
  img->CopyLine(img->GetLine(4), img->GetLine(0));
  img->CopyLine(img->GetLine(-1), img->GetLine(-1));
  img->CopyLine(img->GetLine(4), img->GetLine(4));

  CheckImageEq(expected.get(), img.get(), __LINE__);
}
