// 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.

// This file contains definitions for CppVariant.

#include <limits>
#include "third_party/WebKit/Source/WebKit/chromium/public/WebBindings.h"
#include "webkit/glue/cpp_variant.h"
#include "base/logging.h"
#include "base/string_util.h"
#include "base/stringprintf.h"
#include "base/utf_string_conversions.h"

using WebKit::WebBindings;

CppVariant::CppVariant() {
  type = NPVariantType_Null;
}

// Note that Set() performs a deep copy, which is necessary to safely
// call FreeData() on the value in the destructor.
CppVariant::CppVariant(const CppVariant& original) {
  type = NPVariantType_Null;
  Set(original);
}

// See comment for copy constructor, above.
CppVariant& CppVariant::operator=(const CppVariant& original) {
  if (&original != this)
    Set(original);
  return *this;
}

CppVariant::~CppVariant() {
  FreeData();
}

void CppVariant::FreeData() {
  WebBindings::releaseVariantValue(this);
}

bool CppVariant::isEqual(const CppVariant& other) const {
  if (type != other.type)
    return false;

  switch (type) {
    case NPVariantType_Bool: {
      return (value.boolValue == other.value.boolValue);
    }
    case NPVariantType_Int32: {
      return (value.intValue == other.value.intValue);
    }
    case NPVariantType_Double: {
      return (value.doubleValue == other.value.doubleValue);
    }
    case NPVariantType_String: {
      const NPString *this_value = &value.stringValue;
      const NPString *other_value = &other.value.stringValue;
      uint32_t len = this_value->UTF8Length;
      return (len == other_value->UTF8Length &&
              !strncmp(this_value->UTF8Characters, other_value->UTF8Characters,
                       len));
    }
    case NPVariantType_Null:
    case NPVariantType_Void: {
      return true;
    }
    case NPVariantType_Object: {
      NPObject *this_value = value.objectValue;
      NPObject *other_value = other.value.objectValue;
      return (this_value->_class == other_value->_class &&
              this_value->referenceCount == other_value->referenceCount);
    }
  }
  return false;
}

void CppVariant::CopyToNPVariant(NPVariant* result) const {
  result->type = type;
  switch (type) {
    case NPVariantType_Bool:
      result->value.boolValue = value.boolValue;
      break;
    case NPVariantType_Int32:
      result->value.intValue = value.intValue;
      break;
    case NPVariantType_Double:
      result->value.doubleValue = value.doubleValue;
      break;
    case NPVariantType_String:
      WebBindings::initializeVariantWithStringCopy(result, &value.stringValue);
      break;
    case NPVariantType_Null:
    case NPVariantType_Void:
      // Nothing to set.
      break;
    case NPVariantType_Object:
      result->type = NPVariantType_Object;
      result->value.objectValue = WebBindings::retainObject(value.objectValue);
      break;
  }
}

void CppVariant::Set(const NPVariant& new_value) {
  FreeData();
  switch (new_value.type) {
    case NPVariantType_Bool:
      Set(new_value.value.boolValue);
      break;
    case NPVariantType_Int32:
      Set(new_value.value.intValue);
      break;
    case NPVariantType_Double:
      Set(new_value.value.doubleValue);
      break;
    case NPVariantType_String:
      Set(new_value.value.stringValue);
      break;
    case NPVariantType_Null:
    case NPVariantType_Void:
      type = new_value.type;
      break;
    case NPVariantType_Object:
      Set(new_value.value.objectValue);
      break;
  }
}

void CppVariant::SetNull() {
  FreeData();
  type = NPVariantType_Null;
}

void CppVariant::Set(bool new_value) {
  FreeData();
  type = NPVariantType_Bool;
  value.boolValue = new_value;
}

void CppVariant::Set(int32_t new_value) {
  FreeData();
  type = NPVariantType_Int32;
  value.intValue = new_value;
}

void CppVariant::Set(double new_value) {
  FreeData();
  type = NPVariantType_Double;
  value.doubleValue = new_value;
}

// The new_value must be a null-terminated string.
void CppVariant::Set(const char* new_value) {
  FreeData();
  type = NPVariantType_String;
  NPString new_string = {new_value,
                         static_cast<uint32_t>(strlen(new_value))};
  WebBindings::initializeVariantWithStringCopy(this, &new_string);
}

void CppVariant::Set(const std::string& new_value) {
  FreeData();
  type = NPVariantType_String;
  NPString new_string = {new_value.data(),
                         static_cast<uint32_t>(new_value.size())};
  WebBindings::initializeVariantWithStringCopy(this, &new_string);
}
void CppVariant::Set(const NPString& new_value) {
  FreeData();
  type = NPVariantType_String;
  WebBindings::initializeVariantWithStringCopy(this, &new_value);
}

void CppVariant::Set(NPObject* new_value) {
  FreeData();
  type = NPVariantType_Object;
  value.objectValue = WebBindings::retainObject(new_value);
}

std::string CppVariant::ToString() const {
  DCHECK(isString());
  return std::string(value.stringValue.UTF8Characters,
                     value.stringValue.UTF8Length);
}

int32_t CppVariant::ToInt32() const {
  if (isInt32()) {
    return value.intValue;
  } else if (isDouble()) {
    return static_cast<int32_t>(value.doubleValue);
  } else {
    NOTREACHED();
    return 0;
  }
}

double CppVariant::ToDouble() const {
  if (isInt32()) {
    return static_cast<double>(value.intValue);
  } else if (isDouble()) {
    return value.doubleValue;
  } else {
    NOTREACHED();
    return 0.0;
  }
}

bool CppVariant::ToBoolean() const {
  DCHECK(isBool());
  return value.boolValue;
}

std::vector<CppVariant> CppVariant::ToVector() const {
  DCHECK(isObject());
  std::vector<CppVariant> vector;
  NPObject* np_value = value.objectValue;
  NPIdentifier length_id = WebBindings::getStringIdentifier("length");

  if (WebBindings::hasProperty(NULL, np_value, length_id)) {
    CppVariant length_value;
    if (WebBindings::getProperty(NULL, np_value, length_id, &length_value)) {
      int length = 0;
      // The length is a double in some cases.
      if (NPVARIANT_IS_DOUBLE(length_value))
        length = static_cast<int>(NPVARIANT_TO_DOUBLE(length_value));
      else if (NPVARIANT_IS_INT32(length_value))
        length = NPVARIANT_TO_INT32(length_value);
      else
        NOTREACHED();

      // For sanity, only allow 60000 items.
      length = std::min(60000, length);
      for (int i = 0; i < length; ++i) {
        // Get each of the items.
        NPIdentifier index = WebBindings::getIntIdentifier(i);
        if (WebBindings::hasProperty(NULL, np_value, index)) {
          CppVariant index_value;
          if (WebBindings::getProperty(NULL, np_value, index, &index_value))
            vector.push_back(index_value);
        }
      }
    }
  } else {
    NOTREACHED();
  }
  return vector;
}

bool CppVariant::Invoke(const std::string& method, const CppVariant* args,
                        uint32 arg_count, CppVariant& result) const {
  DCHECK(isObject());
  NPIdentifier method_name = WebBindings::getStringIdentifier(method.c_str());
  NPObject* np_object = value.objectValue;
  if (WebBindings::hasMethod(NULL, np_object, method_name)) {
    NPVariant r;
    bool status = WebBindings::invoke(NULL, np_object, method_name, args, arg_count, &r);
    result.Set(r);
    return status;
  } else {
    return false;
  }
}