// Copyright (c) 2011 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 <vector>

#include "base/compiler_specific.h"
#include "base/string_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/WebKit/Source/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_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);
}

bool MockNPHasPropertyFunction(NPObject *npobj, NPIdentifier name) {
  return true;
}

bool MockNPGetPropertyFunction(NPObject *npobj, NPIdentifier name,
                               NPVariant *result) {
  if (WebBindings::getStringIdentifier("length") == name) {
    DOUBLE_TO_NPVARIANT(4, *result);
  } else if (WebBindings::getIntIdentifier(0) == name) {
    DOUBLE_TO_NPVARIANT(0, *result);
  } else if (WebBindings::getIntIdentifier(1) == name) {
    BOOLEAN_TO_NPVARIANT(true, *result);
  } else if (WebBindings::getIntIdentifier(2) == name) {
    NULL_TO_NPVARIANT(*result);
  } else if (WebBindings::getIntIdentifier(3) == name) {
    const char* s = "string";
    size_t length = strlen(s);
    char* mem = static_cast<char*>(malloc(length + 1));
    base::strlcpy(mem, s, length + 1);
    STRINGZ_TO_NPVARIANT(mem, *result);
  }

  return true;
}

TEST(CppVariantTest, ToVector) {
  NPClass array_like_class = {
      NP_CLASS_STRUCT_VERSION,
      0, // NPAllocateFunctionPtr allocate;
      0, // NPDeallocateFunctionPtr deallocate;
      0, // NPInvalidateFunctionPtr invalidate;
      0, // NPHasMethodFunctionPtr hasMethod;
      0, // NPInvokeFunctionPtr invoke;
      0, // NPInvokeDefaultFunctionPtr invokeDefault;
      MockNPHasPropertyFunction, // NPHasPropertyFunctionPtr hasProperty;
      MockNPGetPropertyFunction, // NPGetPropertyFunctionPtr getProperty;
      0, // NPSetPropertyFunctionPtr setProperty;
      0, // NPRemovePropertyFunctionPtr removeProperty;
      0, // NPEnumerationFunctionPtr enumerate;
      0 // NPConstructFunctionPtr construct;
      };

  NPObject* obj = WebBindings::createObject(NULL, &array_like_class);

  CppVariant cpp;
  cpp.Set(obj);

  std::vector<CppVariant> cpp_vector = cpp.ToVector();
  EXPECT_EQ(4u, cpp_vector.size());

  EXPECT_TRUE(cpp_vector[0].isDouble());
  EXPECT_EQ(0, cpp_vector[0].ToDouble());

  EXPECT_TRUE(cpp_vector[1].isBool());
  EXPECT_EQ(true, cpp_vector[1].ToBoolean());

  EXPECT_TRUE(cpp_vector[2].isNull());

  EXPECT_TRUE(cpp_vector[3].isString());
  CheckString("string", cpp_vector[3]);

  WebBindings::releaseObject(obj);
}