/*
 * Copyright (C) 2010 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "CppVariant.h"

#include "WebBindings.h"
#include <limits>
#include <wtf/Assertions.h>
#include <wtf/StringExtras.h>

using namespace WebKit;
using namespace std;

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* thisValue = value.objectValue;
        NPObject* otherValue = other.value.objectValue;
        return thisValue->_class == otherValue->_class
            && thisValue->referenceCount == otherValue->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& newValue)
{
    freeData();
    switch (newValue.type) {
    case NPVariantType_Bool:
        set(newValue.value.boolValue);
        break;
    case NPVariantType_Int32:
        set(newValue.value.intValue);
        break;
    case NPVariantType_Double:
        set(newValue.value.doubleValue);
        break;
    case NPVariantType_String:
        set(newValue.value.stringValue);
        break;
    case NPVariantType_Null:
    case NPVariantType_Void:
        type = newValue.type;
        break;
    case NPVariantType_Object:
        set(newValue.value.objectValue);
        break;
    }
}

void CppVariant::setNull()
{
    freeData();
    type = NPVariantType_Null;
}

void CppVariant::set(bool newValue)
{
    freeData();
    type = NPVariantType_Bool;
    value.boolValue = newValue;
}

void CppVariant::set(int32_t newValue)
{
    freeData();
    type = NPVariantType_Int32;
    value.intValue = newValue;
}

void CppVariant::set(double newValue)
{
    freeData();
    type = NPVariantType_Double;
    value.doubleValue = newValue;
}

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

void CppVariant::set(const string& newValue)
{
    freeData();
    type = NPVariantType_String;
    NPString newString = {newValue.data(),
                          static_cast<uint32_t>(newValue.size())};
    WebBindings::initializeVariantWithStringCopy(this, &newString);
}

void CppVariant::set(const NPString& newValue)
{
    freeData();
    type = NPVariantType_String;
    WebBindings::initializeVariantWithStringCopy(this, &newValue);
}

void CppVariant::set(NPObject* newValue)
{
    freeData();
    type = NPVariantType_Object;
    value.objectValue = WebBindings::retainObject(newValue);
}

string CppVariant::toString() const
{
    ASSERT(isString());
    return string(value.stringValue.UTF8Characters,
                  value.stringValue.UTF8Length);
}

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

double CppVariant::toDouble() const
{
    if (isInt32())
        return static_cast<double>(value.intValue);
    if (isDouble())
        return value.doubleValue;
    ASSERT_NOT_REACHED();
    return 0;
}

bool CppVariant::toBoolean() const
{
    ASSERT(isBool());
    return value.boolValue;
}

Vector<string> CppVariant::toStringVector() const
{

    ASSERT(isObject());
    Vector<string> stringVector;
    NPObject* npValue = value.objectValue;
    NPIdentifier lengthId = WebBindings::getStringIdentifier("length");

    if (!WebBindings::hasProperty(0, npValue, lengthId))
        return stringVector;

    NPVariant lengthValue;
    if (!WebBindings::getProperty(0, npValue, lengthId, &lengthValue))
        return stringVector;

    int length = 0;
    // The length is a double in some cases.
    if (NPVARIANT_IS_DOUBLE(lengthValue))
        length = static_cast<int>(NPVARIANT_TO_DOUBLE(lengthValue));
    else if (NPVARIANT_IS_INT32(lengthValue))
        length = NPVARIANT_TO_INT32(lengthValue);
    WebBindings::releaseVariantValue(&lengthValue);

    // For sanity, only allow 100 items.
    length = min(100, length);
    for (int i = 0; i < length; ++i) {
        // Get each of the items.
        char indexInChar[20]; // Enough size to store 32-bit integer
        snprintf(indexInChar, 20, "%d", i);
        string index(indexInChar);
        NPIdentifier indexId = WebBindings::getStringIdentifier(index.c_str());
        if (!WebBindings::hasProperty(0, npValue, indexId))
            continue;
        NPVariant indexValue;
        if (!WebBindings::getProperty(0, npValue, indexId, &indexValue))
            continue;
        if (NPVARIANT_IS_STRING(indexValue)) {
            string item(NPVARIANT_TO_STRING(indexValue).UTF8Characters,
                        NPVARIANT_TO_STRING(indexValue).UTF8Length);
            stringVector.append(item);
        }
        WebBindings::releaseVariantValue(&indexValue);
    }
    return stringVector;
}

bool CppVariant::invoke(const string& method, const CppVariant* arguments,
                        uint32_t argumentCount, CppVariant& result) const
{
    ASSERT(isObject());
    NPIdentifier methodName = WebBindings::getStringIdentifier(method.c_str());
    NPObject* npObject = value.objectValue;
    if (!WebBindings::hasMethod(0, npObject, methodName))
        return false;
    NPVariant r;
    bool status = WebBindings::invoke(0, npObject, methodName, arguments, argumentCount, &r);
    result.set(r);
    return status;
}

bool CppVariant::invokeDefault(const CppVariant* arguments, uint32_t argumentCount,
                               CppVariant& result) const
{
    ASSERT(isObject());
    NPObject* npObject = value.objectValue;
    NPVariant r;
    bool status = WebBindings::invokeDefault(0, npObject, arguments, argumentCount, &r);
    result.set(r);
    return status;
}
