// 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/webrtc/modules/desktop_capture/desktop_geometry.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 webrtc::DesktopRect& 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 webrtc::DesktopRect& 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 webrtc::DesktopSize dest_size,
               const webrtc::DesktopRect& rect) {
    ASSERT_TRUE(
        DoesRectContain(webrtc::DesktopRect::MakeSize(dest_size), 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,
                                  webrtc::DesktopSize(kWidth, kHeight),
                                  webrtc::DesktopRect::MakeWH(kWidth, kHeight),
                                  rgb_buffer_.get(),
                                  kRgbStride,
                                  dest_size,
                                  webrtc::DesktopRect::MakeSize(dest_size),
                                  rect);

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

  void TestBasicConversion() {
    // Whole buffer.
    RunTest(webrtc::DesktopSize(kWidth, kHeight),
            webrtc::DesktopRect::MakeWH(kWidth, kHeight));
  }

 private:
  size_t yuv_buffer_size_;
  scoped_ptr<uint8[]> yuv_buffer_;
  uint8* yplane_;
  uint8* uplane_;
  uint8* vplane_;

  size_t rgb_buffer_size_;
  scoped_ptr<uint8[]> rgb_buffer_;

  DISALLOW_COPY_AND_ASSIGN(YuvToRgbTester);
};

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

TEST(YuvToRgbTest, Clipping) {
  YuvToRgbTester tester;

  webrtc::DesktopSize dest_size = webrtc::DesktopSize(kWidth, kHeight);
  webrtc::DesktopRect rect =
      webrtc::DesktopRect::MakeLTRB(0, 0, kWidth - 1, kHeight - 1);
  // TODO(fbarchard): Allow top/left clipping to odd boundary.
  for (int i = 0; i < 16; ++i) {
    webrtc::DesktopRect dest_rect = webrtc::DesktopRect::MakeLTRB(
        rect.left() + ((i & 1) ? 2 : 0),
        rect.top() + ((i & 2) ? 2 : 0),
        rect.right() - ((i & 4) ? 1 : 0),
        rect.bottom() - ((i & 8) ? 1 : 0));

    tester.RunTest(dest_size, dest_rect);
  }
}

TEST(YuvToRgbTest, ClippingAndScaling) {
  YuvToRgbTester tester;

  webrtc::DesktopSize dest_size =
      webrtc::DesktopSize(kWidth - 10, kHeight - 10);
  webrtc::DesktopRect rect =
      webrtc::DesktopRect::MakeLTRB(6, 6, kWidth - 11, kHeight - 11);
  for (int i = 0; i < 16; ++i) {
    webrtc::DesktopRect dest_rect = webrtc::DesktopRect::MakeLTRB(
        rect.left() + ((i & 1) ? 2 : 0),
        rect.top() + ((i & 2) ? 2 : 0),
        rect.right() - ((i & 4) ? 1 : 0),
        rect.bottom() - ((i & 8) ? 1 : 0));

    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++);
  }
}

TEST(StringIsUtf8Test, Basic) {
  EXPECT_TRUE(StringIsUtf8("", 0));
  EXPECT_TRUE(StringIsUtf8("\0", 1));
  EXPECT_TRUE(StringIsUtf8("abc", 3));
  EXPECT_TRUE(StringIsUtf8("\xc0\x80", 2));
  EXPECT_TRUE(StringIsUtf8("\xe0\x80\x80", 3));
  EXPECT_TRUE(StringIsUtf8("\xf0\x80\x80\x80", 4));
  EXPECT_TRUE(StringIsUtf8("\xf8\x80\x80\x80\x80", 5));
  EXPECT_TRUE(StringIsUtf8("\xfc\x80\x80\x80\x80\x80", 6));

  // Not enough continuation characters
  EXPECT_FALSE(StringIsUtf8("\xc0", 1));
  EXPECT_FALSE(StringIsUtf8("\xe0\x80", 2));
  EXPECT_FALSE(StringIsUtf8("\xf0\x80\x80", 3));
  EXPECT_FALSE(StringIsUtf8("\xf8\x80\x80\x80", 4));
  EXPECT_FALSE(StringIsUtf8("\xfc\x80\x80\x80\x80", 5));

  // One more continuation character than needed
  EXPECT_FALSE(StringIsUtf8("\xc0\x80\x80", 3));
  EXPECT_FALSE(StringIsUtf8("\xe0\x80\x80\x80", 4));
  EXPECT_FALSE(StringIsUtf8("\xf0\x80\x80\x80\x80", 5));
  EXPECT_FALSE(StringIsUtf8("\xf8\x80\x80\x80\x80\x80", 6));
  EXPECT_FALSE(StringIsUtf8("\xfc\x80\x80\x80\x80\x80\x80", 7));

  // Invalid first byte
  EXPECT_FALSE(StringIsUtf8("\xfe\x80\x80\x80\x80\x80\x80", 7));
  EXPECT_FALSE(StringIsUtf8("\xff\x80\x80\x80\x80\x80\x80", 7));

  // Invalid continuation byte
  EXPECT_FALSE(StringIsUtf8("\xc0\x00", 2));
  EXPECT_FALSE(StringIsUtf8("\xc0\x40", 2));
  EXPECT_FALSE(StringIsUtf8("\xc0\xc0", 2));
}

}  // namespace remoting