// 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 "ppapi/tests/test_flash_clipboard.h"

#include <algorithm>
#include <vector>

#include "ppapi/cpp/instance.h"
#include "ppapi/cpp/module.h"
#include "ppapi/cpp/point.h"
#include "ppapi/cpp/private/flash_clipboard.h"
#include "ppapi/cpp/var.h"
#include "ppapi/cpp/var_array_buffer.h"
#include "ppapi/tests/testing_instance.h"

// http://crbug.com/176822
#if !defined(OS_WIN)
REGISTER_TEST_CASE(FlashClipboard);
#endif

// WriteData() sends an async request to the browser process. As a result, the
// string written may not be reflected by IsFormatAvailable() or ReadPlainText()
// immediately. We need to wait and retry.
const int kIntervalMs = 250;
const int kMaxIntervals = kActionTimeoutMs / kIntervalMs;

TestFlashClipboard::TestFlashClipboard(TestingInstance* instance)
    : TestCase(instance) {
}

void TestFlashClipboard::RunTests(const std::string& filter) {
  RUN_TEST(ReadWritePlainText, filter);
  RUN_TEST(ReadWriteHTML, filter);
  RUN_TEST(ReadWriteRTF, filter);
  RUN_TEST(ReadWriteCustomData, filter);
  RUN_TEST(ReadWriteMultipleFormats, filter);
  RUN_TEST(Clear, filter);
  RUN_TEST(InvalidFormat, filter);
  RUN_TEST(RegisterCustomFormat, filter);
}

bool TestFlashClipboard::ReadStringVar(uint32_t format, std::string* result) {
  pp::Var text;
  bool success = pp::flash::Clipboard::ReadData(
      instance_,
      PP_FLASH_CLIPBOARD_TYPE_STANDARD,
      format,
      &text);
  if (success && text.is_string()) {
    *result = text.AsString();
    return true;
  }
  return false;
}

bool TestFlashClipboard::WriteStringVar(uint32_t format,
                                        const std::string& text) {
  std::vector<uint32_t> formats_vector(1, format);
  std::vector<pp::Var> data_vector(1, pp::Var(text));
  bool success = pp::flash::Clipboard::WriteData(
      instance_,
      PP_FLASH_CLIPBOARD_TYPE_STANDARD,
      formats_vector,
      data_vector);
  return success;
}

bool TestFlashClipboard::IsFormatAvailableMatches(uint32_t format,
                                                  bool expected) {
  for (int i = 0; i < kMaxIntervals; ++i) {
    bool is_available = pp::flash::Clipboard::IsFormatAvailable(
        instance_,
        PP_FLASH_CLIPBOARD_TYPE_STANDARD,
        format);
    if (is_available == expected)
      return true;

    PlatformSleep(kIntervalMs);
  }
  return false;
}

bool TestFlashClipboard::ReadPlainTextMatches(const std::string& expected) {
  for (int i = 0; i < kMaxIntervals; ++i) {
    std::string result;
    bool success = ReadStringVar(PP_FLASH_CLIPBOARD_FORMAT_PLAINTEXT, &result);
    if (success && result == expected)
      return true;

    PlatformSleep(kIntervalMs);
  }
  return false;
}

bool TestFlashClipboard::ReadHTMLMatches(const std::string& expected) {
  for (int i = 0; i < kMaxIntervals; ++i) {
    std::string result;
    bool success = ReadStringVar(PP_FLASH_CLIPBOARD_FORMAT_HTML, &result);
    // Harmless markup may be inserted around the copied html on some
    // platforms, so just check that the pasted string contains the
    // copied string. Also check that we only paste the copied fragment, see
    // http://code.google.com/p/chromium/issues/detail?id=130827.
    if (success && result.find(expected) != std::string::npos &&
        result.find("<!--StartFragment-->") == std::string::npos &&
        result.find("<!--EndFragment-->") == std::string::npos) {
      return true;
    }

    PlatformSleep(kIntervalMs);
  }
  return false;
}

std::string TestFlashClipboard::TestReadWritePlainText() {
  std::string input = "Hello world plain text!";
  ASSERT_TRUE(WriteStringVar(PP_FLASH_CLIPBOARD_FORMAT_PLAINTEXT, input));
  ASSERT_TRUE(IsFormatAvailableMatches(PP_FLASH_CLIPBOARD_FORMAT_PLAINTEXT,
                                       true));
  ASSERT_TRUE(ReadPlainTextMatches(input));

  PASS();
}

std::string TestFlashClipboard::TestReadWriteHTML() {
  std::string input = "Hello world html!";
  ASSERT_TRUE(WriteStringVar(PP_FLASH_CLIPBOARD_FORMAT_HTML, input));
  ASSERT_TRUE(IsFormatAvailableMatches(PP_FLASH_CLIPBOARD_FORMAT_HTML, true));
  ASSERT_TRUE(ReadHTMLMatches(input));

  PASS();
}

std::string TestFlashClipboard::TestReadWriteRTF() {
  std::string rtf_string =
        "{\\rtf1\\ansi{\\fonttbl\\f0\\fswiss Helvetica;}\\f0\\pard\n"
        "This is some {\\b bold} text.\\par\n"
        "}";
  pp::VarArrayBuffer array_buffer(rtf_string.size());
  char* bytes = static_cast<char*>(array_buffer.Map());
  std::copy(rtf_string.data(), rtf_string.data() + rtf_string.size(), bytes);
  std::vector<uint32_t> formats_vector(1, PP_FLASH_CLIPBOARD_FORMAT_RTF);
  std::vector<pp::Var> data_vector(1, array_buffer);
  ASSERT_TRUE(pp::flash::Clipboard::WriteData(
      instance_,
      PP_FLASH_CLIPBOARD_TYPE_STANDARD,
      formats_vector,
      data_vector));

  ASSERT_TRUE(IsFormatAvailableMatches(PP_FLASH_CLIPBOARD_FORMAT_RTF, true));

  pp::Var rtf_result;
  ASSERT_TRUE(pp::flash::Clipboard::ReadData(
        instance_,
        PP_FLASH_CLIPBOARD_TYPE_STANDARD,
        PP_FLASH_CLIPBOARD_FORMAT_RTF,
        &rtf_result));
  ASSERT_TRUE(rtf_result.is_array_buffer());
  pp::VarArrayBuffer array_buffer_result(rtf_result);
  ASSERT_TRUE(array_buffer_result.ByteLength() == array_buffer.ByteLength());
  char* bytes_result = static_cast<char*>(array_buffer_result.Map());
  ASSERT_TRUE(std::equal(bytes, bytes + array_buffer.ByteLength(),
      bytes_result));

  PASS();
}

std::string TestFlashClipboard::TestReadWriteCustomData() {
  std::string custom_data = "custom_data";
  pp::VarArrayBuffer array_buffer(custom_data.size());
  char* bytes = static_cast<char*>(array_buffer.Map());
  std::copy(custom_data.begin(), custom_data.end(), bytes);
  uint32_t format_id =
      pp::flash::Clipboard::RegisterCustomFormat(instance_, "my-format");
  ASSERT_NE(format_id, PP_FLASH_CLIPBOARD_FORMAT_INVALID);

  std::vector<uint32_t> formats_vector(1, format_id);
  std::vector<pp::Var> data_vector(1, array_buffer);
  ASSERT_TRUE(pp::flash::Clipboard::WriteData(
      instance_,
      PP_FLASH_CLIPBOARD_TYPE_STANDARD,
      formats_vector,
      data_vector));

  ASSERT_TRUE(IsFormatAvailableMatches(format_id, true));

  pp::Var custom_data_result;
  ASSERT_TRUE(pp::flash::Clipboard::ReadData(
      instance_,
      PP_FLASH_CLIPBOARD_TYPE_STANDARD,
      format_id,
      &custom_data_result));
  ASSERT_TRUE(custom_data_result.is_array_buffer());
  pp::VarArrayBuffer array_buffer_result(custom_data_result);
  ASSERT_EQ(array_buffer_result.ByteLength(), array_buffer.ByteLength());
  char* bytes_result = static_cast<char*>(array_buffer_result.Map());
  ASSERT_TRUE(std::equal(bytes, bytes + array_buffer.ByteLength(),
      bytes_result));

  PASS();
}

std::string TestFlashClipboard::TestReadWriteMultipleFormats() {
  std::vector<uint32_t> formats;
  std::vector<pp::Var> data;
  formats.push_back(PP_FLASH_CLIPBOARD_FORMAT_PLAINTEXT);
  data.push_back(pp::Var("plain text"));
  formats.push_back(PP_FLASH_CLIPBOARD_FORMAT_HTML);
  data.push_back(pp::Var("html"));
  bool success = pp::flash::Clipboard::WriteData(
      instance_,
      PP_FLASH_CLIPBOARD_TYPE_STANDARD,
      formats,
      data);
  ASSERT_TRUE(success);
  ASSERT_TRUE(IsFormatAvailableMatches(PP_FLASH_CLIPBOARD_FORMAT_PLAINTEXT,
                                       true));
  ASSERT_TRUE(IsFormatAvailableMatches(PP_FLASH_CLIPBOARD_FORMAT_HTML, true));
  ASSERT_TRUE(ReadPlainTextMatches(data[0].AsString()));
  ASSERT_TRUE(ReadHTMLMatches(data[1].AsString()));

  PASS();
}

std::string TestFlashClipboard::TestClear() {
  std::string input = "Hello world plain text!";
  ASSERT_TRUE(WriteStringVar(PP_FLASH_CLIPBOARD_FORMAT_PLAINTEXT, input));
  ASSERT_TRUE(IsFormatAvailableMatches(PP_FLASH_CLIPBOARD_FORMAT_PLAINTEXT,
                                       true));
  bool success = pp::flash::Clipboard::WriteData(
      instance_,
      PP_FLASH_CLIPBOARD_TYPE_STANDARD,
      std::vector<uint32_t>(),
      std::vector<pp::Var>());
  ASSERT_TRUE(success);
  ASSERT_TRUE(IsFormatAvailableMatches(PP_FLASH_CLIPBOARD_FORMAT_PLAINTEXT,
                                       false));

  PASS();
}

std::string TestFlashClipboard::TestInvalidFormat() {
  uint32_t invalid_format = 999;
  ASSERT_FALSE(WriteStringVar(invalid_format, "text"));
  ASSERT_TRUE(IsFormatAvailableMatches(invalid_format, false));
  std::string unused;
  ASSERT_FALSE(ReadStringVar(invalid_format, &unused));

  PASS();
}

std::string TestFlashClipboard::TestRegisterCustomFormat() {
  // Test an empty name is rejected.
  uint32_t format_id =
      pp::flash::Clipboard::RegisterCustomFormat(instance_, std::string());
  ASSERT_EQ(format_id, PP_FLASH_CLIPBOARD_FORMAT_INVALID);

  // Test a valid format name.
  format_id = pp::flash::Clipboard::RegisterCustomFormat(instance_, "a-b");
  ASSERT_NE(format_id, PP_FLASH_CLIPBOARD_FORMAT_INVALID);
  // Make sure the format doesn't collide with predefined formats.
  ASSERT_NE(format_id, PP_FLASH_CLIPBOARD_FORMAT_PLAINTEXT);
  ASSERT_NE(format_id, PP_FLASH_CLIPBOARD_FORMAT_HTML);
  ASSERT_NE(format_id, PP_FLASH_CLIPBOARD_FORMAT_RTF);

  // Check that if the same name is registered, the same id comes out.
  uint32_t format_id2 =
      pp::flash::Clipboard::RegisterCustomFormat(instance_, "a-b");
  ASSERT_EQ(format_id, format_id2);

  // Check that the second format registered has a different id.
  uint32_t format_id3 =
      pp::flash::Clipboard::RegisterCustomFormat(instance_, "a-b-c");
  ASSERT_NE(format_id, format_id3);

  PASS();
}