// Copyright (c) 2006-2008 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 "base/compiler_specific.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/WebKit/WebKit/chromium/public/WebBindings.h"
#include "webkit/glue/cpp_variant.h"

using WebKit::WebBindings;

// Creates a std::string from an NPVariant of string type.  If the NPVariant
// is not a string, empties the std::string.
void MakeStdString(const NPVariant& np, std::string* std_string) {
  if (np.type == NPVariantType_String) {
    const char* chars =
        reinterpret_cast<const char*>(np.value.stringValue.UTF8Characters);
    (*std_string).assign(chars, np.value.stringValue.UTF8Length);
  } else {
    (*std_string).clear();
  }
}

// Verifies that the actual NPVariant is a string and that its value matches
// the expected_str.
void CheckString(const std::string& expected_str, const NPVariant& actual) {
  EXPECT_EQ(NPVariantType_String, actual.type);
  std::string actual_str;
  MakeStdString(actual, &actual_str);
  EXPECT_EQ(expected_str, actual_str);
}

// Verifies that both the actual and the expected NPVariants are strings and
// that their values match.
void CheckString(const NPVariant& expected, const NPVariant& actual) {
  EXPECT_EQ(NPVariantType_String, expected.type);
  std::string expected_str;
  MakeStdString(expected, &expected_str);
  CheckString(expected_str, actual);
}

int g_allocate_call_count = 0;
int g_deallocate_call_count = 0;

void CheckObject(const NPVariant& actual) {
  EXPECT_EQ(NPVariantType_Object, actual.type);
  EXPECT_TRUE(actual.value.objectValue);
  EXPECT_EQ(1U, actual.value.objectValue->referenceCount);
  EXPECT_EQ(1, g_allocate_call_count);
  EXPECT_EQ(0, g_deallocate_call_count);
}

NPObject* MockNPAllocate(NPP npp, NPClass* aClass) {
  // This is a mock allocate method that mimics the behavior
  // of WebBindings::createObject when allocate() is NULL

  ++g_allocate_call_count;
  // Ignore npp and NPClass
  return reinterpret_cast<NPObject*>(malloc(sizeof(NPObject)));
}

void MockNPDeallocate(NPObject* npobj) {
  // This is a mock deallocate method that mimics the behavior
  // of NPN_DeallocateObject when deallocate() is NULL

  ++g_deallocate_call_count;
  free(npobj);
}

static NPClass void_class = { NP_CLASS_STRUCT_VERSION,
                              MockNPAllocate,
                              MockNPDeallocate,
                              0, 0, 0, 0, 0, 0, 0, 0, 0 };

NPObject* MakeVoidObject() {
  g_allocate_call_count = 0;
  g_deallocate_call_count = 0;
  return WebBindings::createObject(NULL, &void_class);
}

TEST(CppVariantTest, NewVariantHasNullType) {
  CppVariant value;
  EXPECT_EQ(NPVariantType_Null, value.type);
}

TEST(CppVariantTest, SetNullSetsType) {
  CppVariant value;
  value.Set(17);
  value.SetNull();
  EXPECT_EQ(NPVariantType_Null, value.type);
}

TEST(CppVariantTest, CopyConstructorDoesDeepCopy) {
  CppVariant source;
  source.Set("test string");
  CppVariant dest = source;
  EXPECT_EQ(NPVariantType_String, dest.type);
  EXPECT_EQ(NPVariantType_String, source.type);

  // Ensure that the string was copied, not just the pointer.
  EXPECT_NE(source.value.stringValue.UTF8Characters,
    dest.value.stringValue.UTF8Characters);

  CheckString(source, dest);
}

TEST(CppVariantTest, CopyConstructorIncrementsRefCount) {
  CppVariant source;
  NPObject *object = MakeVoidObject();
  source.Set(object);
  // 2 references so far.
  EXPECT_EQ(2U, source.value.objectValue->referenceCount);

  CppVariant dest = source;
  EXPECT_EQ(3U, dest.value.objectValue->referenceCount);
  EXPECT_EQ(1, g_allocate_call_count);
  WebBindings::releaseObject(object);
  source.SetNull();
  CheckObject(dest);
}

TEST(CppVariantTest, AssignmentDoesDeepCopy) {
  CppVariant source;
  source.Set("test string");
  CppVariant dest;
  dest = source;
  EXPECT_EQ(NPVariantType_String, dest.type);
  EXPECT_EQ(NPVariantType_String, source.type);

  // Ensure that the string was copied, not just the pointer.
  EXPECT_NE(source.value.stringValue.UTF8Characters,
    dest.value.stringValue.UTF8Characters);

  CheckString(source, dest);
}

TEST(CppVariantTest, AssignmentIncrementsRefCount) {
  CppVariant source;
  NPObject *object = MakeVoidObject();
  source.Set(object);
  // 2 references so far.
  EXPECT_EQ(2U, source.value.objectValue->referenceCount);

  CppVariant dest;
  dest = source;
  EXPECT_EQ(3U, dest.value.objectValue->referenceCount);
  EXPECT_EQ(1, g_allocate_call_count);

  WebBindings::releaseObject(object);
  source.SetNull();
  CheckObject(dest);
}

TEST(CppVariantTest, DestroyingCopyDoesNotCorruptSource) {
  CppVariant source;
  source.Set("test string");
  std::string before;
  MakeStdString(source, &before);
  {
    CppVariant dest = source;
  }
  CheckString(before, source);

  NPObject *object = MakeVoidObject();
  source.Set(object);
  {
    CppVariant dest2 = source;
  }
  WebBindings::releaseObject(object);
  CheckObject(source);
}

TEST(CppVariantTest, CopiesTypeAndValueToNPVariant) {
  NPVariant np;
  CppVariant cpp;

  cpp.Set(true);
  cpp.CopyToNPVariant(&np);
  EXPECT_EQ(cpp.type, np.type);
  EXPECT_EQ(cpp.value.boolValue, np.value.boolValue);
  WebBindings::releaseVariantValue(&np);

  cpp.Set(17);
  cpp.CopyToNPVariant(&np);
  EXPECT_EQ(cpp.type, np.type);
  EXPECT_EQ(cpp.value.intValue, np.value.intValue);
  WebBindings::releaseVariantValue(&np);

  cpp.Set(3.1415);
  cpp.CopyToNPVariant(&np);
  EXPECT_EQ(cpp.type, np.type);
  EXPECT_EQ(cpp.value.doubleValue, np.value.doubleValue);
  WebBindings::releaseVariantValue(&np);

  cpp.Set("test value");
  cpp.CopyToNPVariant(&np);
  CheckString("test value", np);
  WebBindings::releaseVariantValue(&np);

  cpp.SetNull();
  cpp.CopyToNPVariant(&np);
  EXPECT_EQ(cpp.type, np.type);
  WebBindings::releaseVariantValue(&np);

  NPObject *object = MakeVoidObject();
  cpp.Set(object);
  cpp.CopyToNPVariant(&np);
  WebBindings::releaseObject(object);
  cpp.SetNull();
  CheckObject(np);
  WebBindings::releaseVariantValue(&np);
}

TEST(CppVariantTest, SetsTypeAndValueFromNPVariant) {
  NPVariant np;
  CppVariant cpp;

  VOID_TO_NPVARIANT(np);
  cpp.Set(np);
  EXPECT_EQ(np.type, cpp.type);
  WebBindings::releaseVariantValue(&np);

  NULL_TO_NPVARIANT(np);
  cpp.Set(np);
  EXPECT_EQ(np.type, cpp.type);
  WebBindings::releaseVariantValue(&np);

  BOOLEAN_TO_NPVARIANT(true, np);
  cpp.Set(np);
  EXPECT_EQ(np.type, cpp.type);
  EXPECT_EQ(np.value.boolValue, cpp.value.boolValue);
  WebBindings::releaseVariantValue(&np);

  INT32_TO_NPVARIANT(15, np);
  cpp.Set(np);
  EXPECT_EQ(np.type, cpp.type);
  EXPECT_EQ(np.value.intValue, cpp.value.intValue);
  WebBindings::releaseVariantValue(&np);

  DOUBLE_TO_NPVARIANT(2.71828, np);
  cpp.Set(np);
  EXPECT_EQ(np.type, cpp.type);
  EXPECT_EQ(np.value.doubleValue, cpp.value.doubleValue);
  WebBindings::releaseVariantValue(&np);

  NPString np_ascii_str = { "1st test value",
                            static_cast<uint32_t>(strlen("1st test value")) };
  WebBindings::initializeVariantWithStringCopy(&np, &np_ascii_str);
  cpp.Set(np);
  CheckString("1st test value", cpp);
  WebBindings::releaseVariantValue(&np);

  // Test characters represented in 2/3/4 bytes in UTF-8
  // Greek alpha, Chinese number 1 (horizontal bar),
  // Deseret letter (similar to 'O')
  NPString np_intl_str = { "\xce\xb1\xe4\xb8\x80\xf0\x90\x90\x84",
                           static_cast<uint32_t>(strlen(
                               "\xce\xb1\xe4\xb8\x80\xf0\x90\x90\x84")) };
  WebBindings::initializeVariantWithStringCopy(&np, &np_intl_str);
  cpp.Set(np);
  CheckString("\xce\xb1\xe4\xb8\x80\xf0\x90\x90\x84", cpp);
  WebBindings::releaseVariantValue(&np);

  NPObject *obj = MakeVoidObject();
  OBJECT_TO_NPVARIANT(obj, np);  // Doesn't make a copy.
  cpp.Set(np);
  // Use this or WebBindings::releaseObject but NOT both.
  WebBindings::releaseVariantValue(&np);
  CheckObject(cpp);
}

TEST(CppVariantTest, SetsSimpleTypesAndValues) {
  CppVariant cpp;
  cpp.Set(true);
  EXPECT_EQ(NPVariantType_Bool, cpp.type);
  EXPECT_EQ(true, cpp.value.boolValue);

  cpp.Set(5);
  EXPECT_EQ(NPVariantType_Int32, cpp.type);
  EXPECT_EQ(5, cpp.value.intValue);

  cpp.Set(1.234);
  EXPECT_EQ(NPVariantType_Double, cpp.type);
  EXPECT_EQ(1.234, cpp.value.doubleValue);

  // C string
  cpp.Set("1st test string");
  CheckString("1st test string", cpp);

  // std::string
  std::string source("std test string");
  cpp.Set(source);
  CheckString("std test string", cpp);

  // NPString
  NPString np_ascii_str = { "test NPString",
                            static_cast<uint32_t>(strlen("test NPString")) };
  cpp.Set(np_ascii_str);
  std::string expected("test NPString");
  CheckString(expected, cpp);

  // Test characters represented in 2/3/4 bytes in UTF-8
  // Greek alpha, Chinese number 1 (horizontal bar),
  // Deseret letter (similar to 'O')
  NPString np_intl_str = { "\xce\xb1\xe4\xb8\x80\xf0\x90\x90\x84",
                           static_cast<uint32_t>(strlen(
                               "\xce\xb1\xe4\xb8\x80\xf0\x90\x90\x84")) };
  cpp.Set(np_intl_str);
  expected = std::string("\xce\xb1\xe4\xb8\x80\xf0\x90\x90\x84");
  CheckString(expected, cpp);

  NPObject* obj = MakeVoidObject();
  cpp.Set(obj);
  WebBindings::releaseObject(obj);
  CheckObject(cpp);
}

TEST(CppVariantTest, FreeDataSetsToVoid) {
  CppVariant cpp;
  EXPECT_EQ(NPVariantType_Null, cpp.type);
  cpp.Set(12);
  EXPECT_EQ(NPVariantType_Int32, cpp.type);
  cpp.FreeData();
  EXPECT_EQ(NPVariantType_Void, cpp.type);
}

TEST(CppVariantTest, FreeDataReleasesObject) {
  CppVariant cpp;
  NPObject* object = MakeVoidObject();
  cpp.Set(object);
  EXPECT_EQ(2U, object->referenceCount);
  cpp.FreeData();
  EXPECT_EQ(1U, object->referenceCount);
  EXPECT_EQ(0, g_deallocate_call_count);

  cpp.Set(object);
  WebBindings::releaseObject(object);
  EXPECT_EQ(0, g_deallocate_call_count);
  cpp.FreeData();
  EXPECT_EQ(1, g_deallocate_call_count);
}

TEST(CppVariantTest, IsTypeFunctionsWork) {
  CppVariant cpp;
  // These should not happen in practice, since voids are not supported
  // This test must be first since it just clobbers internal data without
  // releasing.
  VOID_TO_NPVARIANT(cpp);
  EXPECT_FALSE(cpp.isBool());
  EXPECT_FALSE(cpp.isInt32());
  EXPECT_FALSE(cpp.isDouble());
  EXPECT_FALSE(cpp.isNumber());
  EXPECT_FALSE(cpp.isString());
  EXPECT_TRUE(cpp.isVoid());
  EXPECT_FALSE(cpp.isNull());
  EXPECT_TRUE(cpp.isEmpty());

  cpp.Set(true);
  EXPECT_TRUE(cpp.isBool());
  EXPECT_FALSE(cpp.isInt32());
  EXPECT_FALSE(cpp.isDouble());
  EXPECT_FALSE(cpp.isNumber());
  EXPECT_FALSE(cpp.isString());
  EXPECT_FALSE(cpp.isVoid());
  EXPECT_FALSE(cpp.isNull());
  EXPECT_FALSE(cpp.isEmpty());
  EXPECT_FALSE(cpp.isObject());

  cpp.Set(12);
  EXPECT_FALSE(cpp.isBool());
  EXPECT_TRUE(cpp.isInt32());
  EXPECT_FALSE(cpp.isDouble());
  EXPECT_TRUE(cpp.isNumber());
  EXPECT_FALSE(cpp.isString());
  EXPECT_FALSE(cpp.isVoid());
  EXPECT_FALSE(cpp.isNull());
  EXPECT_FALSE(cpp.isEmpty());
  EXPECT_FALSE(cpp.isObject());

  cpp.Set(3.1415);
  EXPECT_FALSE(cpp.isBool());
  EXPECT_FALSE(cpp.isInt32());
  EXPECT_TRUE(cpp.isDouble());
  EXPECT_TRUE(cpp.isNumber());
  EXPECT_FALSE(cpp.isString());
  EXPECT_FALSE(cpp.isVoid());
  EXPECT_FALSE(cpp.isNull());
  EXPECT_FALSE(cpp.isEmpty());
  EXPECT_FALSE(cpp.isObject());

  cpp.Set("a string");
  EXPECT_FALSE(cpp.isBool());
  EXPECT_FALSE(cpp.isInt32());
  EXPECT_FALSE(cpp.isDouble());
  EXPECT_FALSE(cpp.isNumber());
  EXPECT_TRUE(cpp.isString());
  EXPECT_FALSE(cpp.isVoid());
  EXPECT_FALSE(cpp.isNull());
  EXPECT_FALSE(cpp.isEmpty());
  EXPECT_FALSE(cpp.isObject());

  cpp.SetNull();
  EXPECT_FALSE(cpp.isBool());
  EXPECT_FALSE(cpp.isInt32());
  EXPECT_FALSE(cpp.isDouble());
  EXPECT_FALSE(cpp.isNumber());
  EXPECT_FALSE(cpp.isString());
  EXPECT_FALSE(cpp.isVoid());
  EXPECT_TRUE(cpp.isNull());
  EXPECT_TRUE(cpp.isEmpty());
  EXPECT_FALSE(cpp.isObject());

  NPObject *obj = MakeVoidObject();
  cpp.Set(obj);
  EXPECT_FALSE(cpp.isBool());
  EXPECT_FALSE(cpp.isInt32());
  EXPECT_FALSE(cpp.isDouble());
  EXPECT_FALSE(cpp.isNumber());
  EXPECT_FALSE(cpp.isString());
  EXPECT_FALSE(cpp.isVoid());
  EXPECT_FALSE(cpp.isNull());
  EXPECT_FALSE(cpp.isEmpty());
  EXPECT_TRUE(cpp.isObject());
  WebBindings::releaseObject(obj);
  CheckObject(cpp);
}