// Copyright (c) 2012 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 <algorithm>

#include "remoting/base/util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkRect.h"
#include "third_party/skia/include/core/SkSize.h"

static const int kWidth = 32 ;
static const int kHeight = 24 ;
static const int kBytesPerPixel = 4;
static const int kYStride = kWidth;
static const int kUvStride = kWidth / 2;
static const int kRgbStride = kWidth * kBytesPerPixel;
static const uint32 kFillColor = 0xffffff;

namespace remoting {

class YuvToRgbTester {
 public:
  YuvToRgbTester() {
    yuv_buffer_size_ = (kYStride + kUvStride) * kHeight;
    yuv_buffer_.reset(new uint8[yuv_buffer_size_]);
    yplane_ = yuv_buffer_.get();
    uplane_ = yplane_ + (kYStride * kHeight);
    vplane_ = uplane_ + (kUvStride * kHeight / 2);

    rgb_buffer_size_ = kWidth * kHeight * kBytesPerPixel;
    rgb_buffer_.reset(new uint8[rgb_buffer_size_]);

    ResetYuvBuffer();
    ResetRgbBuffer();
  }

  ~YuvToRgbTester() {}

  void ResetYuvBuffer() {
    memset(yuv_buffer_.get(), 0, yuv_buffer_size_);
  }

  void ResetRgbBuffer() {
    memset(rgb_buffer_.get(), 0, rgb_buffer_size_);
  }

  void FillRgbBuffer(const SkIRect& rect) {
    uint32* ptr = reinterpret_cast<uint32*>(
        rgb_buffer_.get() + (rect.top() * kRgbStride) +
        (rect.left() * kBytesPerPixel));
    int width = rect.width();
    for (int height = rect.height(); height > 0; --height) {
      std::fill(ptr, ptr + width, kFillColor);
      ptr += kRgbStride / kBytesPerPixel;
    }
  }

  // Check the the desination buffer is filled within expected bounds.
  void  CheckRgbBuffer(const SkIRect& rect) {
    uint32* ptr = reinterpret_cast<uint32*>(rgb_buffer_.get());
    for (int y = 0; y < kHeight; ++y) {
      if (y < rect.top() || rect.bottom() <= y) {
        // The whole line should be intact.
        EXPECT_EQ((ptrdiff_t)kWidth,
                  std::count(ptr, ptr + kWidth, 0u));
      } else {
        // The space before the painted rectangle should be intact.
        EXPECT_EQ((ptrdiff_t)rect.left(),
                  std::count(ptr, ptr + rect.left(), 0u));

        // All pixels of the target rectangle should be touched.
        EXPECT_EQ(ptr + rect.right(),
                  std::find(ptr + rect.left(), ptr + rect.right(), 0u));

        // The space after the painted rectangle should be intact.
        EXPECT_EQ((ptrdiff_t)kWidth - rect.right(),
                  std::count(ptr + rect.right(), ptr + kWidth, 0u));
      }
      ptr += kRgbStride / kBytesPerPixel;
    }
  }

  void RunTest(const SkISize dest_size, const SkIRect& rect) {
    ASSERT_TRUE(SkIRect::MakeSize(dest_size).contains(rect));

    // Reset buffers.
    ResetYuvBuffer();
    ResetRgbBuffer();
    FillRgbBuffer(rect);

    // RGB -> YUV
    ConvertRGB32ToYUVWithRect(rgb_buffer_.get(),
                              yplane_,
                              uplane_,
                              vplane_,
                              0,
                              0,
                              kWidth,
                              kHeight,
                              kRgbStride,
                              kYStride,
                              kUvStride);

    // Reset RGB buffer and do opposite conversion.
    ResetRgbBuffer();
    ConvertAndScaleYUVToRGB32Rect(yplane_,
                                  uplane_,
                                  vplane_,
                                  kYStride,
                                  kUvStride,
                                  SkISize::Make(kWidth, kHeight),
                                  SkIRect::MakeWH(kWidth, kHeight),
                                  rgb_buffer_.get(),
                                  kRgbStride,
                                  dest_size,
                                  SkIRect::MakeSize(dest_size),
                                  rect);

    // Check if it worked out.
    CheckRgbBuffer(rect);
  }

  void TestBasicConversion() {
    // Whole buffer.
    RunTest(SkISize::Make(kWidth, kHeight), SkIRect::MakeWH(kWidth, kHeight));
  }

 private:
  size_t yuv_buffer_size_;
  scoped_array<uint8> yuv_buffer_;
  uint8* yplane_;
  uint8* uplane_;
  uint8* vplane_;

  size_t rgb_buffer_size_;
  scoped_array<uint8> rgb_buffer_;

  DISALLOW_COPY_AND_ASSIGN(YuvToRgbTester);
};

TEST(YuvToRgbTest, BasicConversion) {
  YuvToRgbTester tester;
  tester.TestBasicConversion();
}

TEST(YuvToRgbTest, Clipping) {
  YuvToRgbTester tester;

  SkISize dest_size = SkISize::Make(kWidth, kHeight);
  SkIRect rect = SkIRect::MakeLTRB(0, 0, kWidth - 1, kHeight - 1);
  for (int i = 0; i < 16; ++i) {
    SkIRect dest_rect = rect;
    if ((i & 1) != 0)
      dest_rect.fLeft += 1;
    if ((i & 2) != 0)
      dest_rect.fTop += 1;
    if ((i & 4) != 0)
      dest_rect.fRight += 1;
    if ((i & 8) != 0)
      dest_rect.fBottom += 1;

    tester.RunTest(dest_size, dest_rect);
  }
}

TEST(YuvToRgbTest, ClippingAndScaling) {
  YuvToRgbTester tester;

  SkISize dest_size = SkISize::Make(kWidth - 10, kHeight - 10);
  SkIRect rect = SkIRect::MakeLTRB(5, 5, kWidth - 11, kHeight - 11);
  for (int i = 0; i < 16; ++i) {
    SkIRect dest_rect = rect;
    if ((i & 1) != 0)
      dest_rect.fLeft += 1;
    if ((i & 2) != 0)
      dest_rect.fTop += 1;
    if ((i & 4) != 0)
      dest_rect.fRight += 1;
    if ((i & 8) != 0)
      dest_rect.fBottom += 1;

    tester.RunTest(dest_size, dest_rect);
  }
}

TEST(ReplaceLfByCrLfTest, Basic) {
  EXPECT_EQ("ab", ReplaceLfByCrLf("ab"));
  EXPECT_EQ("\r\nab", ReplaceLfByCrLf("\nab"));
  EXPECT_EQ("\r\nab\r\n", ReplaceLfByCrLf("\nab\n"));
  EXPECT_EQ("\r\nab\r\ncd", ReplaceLfByCrLf("\nab\ncd"));
  EXPECT_EQ("\r\nab\r\ncd\r\n", ReplaceLfByCrLf("\nab\ncd\n"));
  EXPECT_EQ("\r\n\r\nab\r\n\r\ncd\r\n\r\n",
      ReplaceLfByCrLf("\n\nab\n\ncd\n\n"));
}

TEST(ReplaceLfByCrLfTest, Speed) {
  int kLineSize = 128;
  std::string line(kLineSize, 'a');
  line[kLineSize - 1] = '\n';
  // Make a 10M string.
  int kLineNum = 10 * 1024 * 1024 / kLineSize;
  std::string buffer;
  buffer.resize(kLineNum * kLineSize);
  for (int i = 0; i < kLineNum; ++i) {
    memcpy(&buffer[i * kLineSize], &line[0], kLineSize);
  }
  // Convert the string.
  buffer = ReplaceLfByCrLf(buffer);
  // Check the converted string.
  EXPECT_EQ(static_cast<size_t>((kLineSize + 1) * kLineNum), buffer.size());
  const char* p = &buffer[0];
  for (int i = 0; i < kLineNum; ++i) {
    EXPECT_EQ(0, memcmp(&line[0], p, kLineSize - 1));
    p += kLineSize - 1;
    EXPECT_EQ('\r', *p++);
    EXPECT_EQ('\n', *p++);
  }
}

TEST(ReplaceCrLfByLfTest, Basic) {
  EXPECT_EQ("ab", ReplaceCrLfByLf("ab"));
  EXPECT_EQ("\nab", ReplaceCrLfByLf("\r\nab"));
  EXPECT_EQ("\nab\n", ReplaceCrLfByLf("\r\nab\r\n"));
  EXPECT_EQ("\nab\ncd", ReplaceCrLfByLf("\r\nab\r\ncd"));
  EXPECT_EQ("\nab\ncd\n", ReplaceCrLfByLf("\r\nab\r\ncd\n"));
  EXPECT_EQ("\n\nab\n\ncd\n\n",
      ReplaceCrLfByLf("\r\n\r\nab\r\n\r\ncd\r\n\r\n"));
  EXPECT_EQ("\rab\rcd\r", ReplaceCrLfByLf("\rab\rcd\r"));
}

TEST(ReplaceCrLfByLfTest, Speed) {
  int kLineSize = 128;
  std::string line(kLineSize, 'a');
  line[kLineSize - 2] = '\r';
  line[kLineSize - 1] = '\n';
  // Make a 10M string.
  int kLineNum = 10 * 1024 * 1024 / kLineSize;
  std::string buffer;
  buffer.resize(kLineNum * kLineSize);
  for (int i = 0; i < kLineNum; ++i) {
    memcpy(&buffer[i * kLineSize], &line[0], kLineSize);
  }
  // Convert the string.
  buffer = ReplaceCrLfByLf(buffer);
  // Check the converted string.
  EXPECT_EQ(static_cast<size_t>((kLineSize - 1) * kLineNum), buffer.size());
  const char* p = &buffer[0];
  for (int i = 0; i < kLineNum; ++i) {
    EXPECT_EQ(0, memcmp(&line[0], p, kLineSize - 2));
    p += kLineSize - 2;
    EXPECT_EQ('\n', *p++);
  }
}

}  // namespace remoting