/*
 * Copyright (C) 2010 Apple 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:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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 "NPRemoteObjectMap.h"

#if ENABLE(PLUGIN_PROCESS)

#include "NPObjectMessageReceiver.h"
#include "NPObjectProxy.h"
#include "NPRuntimeUtilities.h"
#include "NPVariantData.h"
#include <WebCore/NotImplemented.h>
#include <wtf/OwnPtr.h>

namespace WebKit {

static uint64_t generateNPObjectID()
{
    static uint64_t generateNPObjectID;
    return ++generateNPObjectID;
}

PassRefPtr<NPRemoteObjectMap> NPRemoteObjectMap::create(CoreIPC::Connection* connection)
{
    return adoptRef(new NPRemoteObjectMap(connection));
}

NPRemoteObjectMap::NPRemoteObjectMap(CoreIPC::Connection* connection)
    : m_connection(connection)
{
}

NPRemoteObjectMap::~NPRemoteObjectMap()
{
    ASSERT(m_npObjectProxies.isEmpty());
    ASSERT(m_registeredNPObjects.isEmpty());
}

NPObject* NPRemoteObjectMap::createNPObjectProxy(uint64_t remoteObjectID, Plugin* plugin)
{
    NPObjectProxy* npObjectProxy = NPObjectProxy::create(this, plugin, remoteObjectID);

    m_npObjectProxies.add(npObjectProxy);

    return npObjectProxy;
}

void NPRemoteObjectMap::npObjectProxyDestroyed(NPObject* npObject)
{
    NPObjectProxy* npObjectProxy = NPObjectProxy::toNPObjectProxy(npObject);
    ASSERT(m_npObjectProxies.contains(npObjectProxy));

    m_npObjectProxies.remove(npObjectProxy);
}

uint64_t NPRemoteObjectMap::registerNPObject(NPObject* npObject, Plugin* plugin)
{
    uint64_t npObjectID = generateNPObjectID();
    m_registeredNPObjects.set(npObjectID, NPObjectMessageReceiver::create(this, plugin, npObjectID, npObject).leakPtr());

    return npObjectID;
}

void NPRemoteObjectMap::unregisterNPObject(uint64_t npObjectID)
{
    m_registeredNPObjects.remove(npObjectID);
}

NPVariantData NPRemoteObjectMap::npVariantToNPVariantData(const NPVariant& variant, Plugin* plugin)
{
    switch (variant.type) {
    case NPVariantType_Void:
        return NPVariantData::makeVoid();

    case NPVariantType_Null:
        return NPVariantData::makeNull();

    case NPVariantType_Bool:
        return NPVariantData::makeBool(variant.value.boolValue);

    case NPVariantType_Int32:
        return NPVariantData::makeInt32(variant.value.intValue);

    case NPVariantType_Double:
        return NPVariantData::makeDouble(variant.value.doubleValue);

    case NPVariantType_String:
        return NPVariantData::makeString(variant.value.stringValue.UTF8Characters, variant.value.stringValue.UTF8Length);

    case NPVariantType_Object: {
        NPObject* npObject = variant.value.objectValue;
        if (NPObjectProxy::isNPObjectProxy(npObject)) {
            NPObjectProxy* npObjectProxy = NPObjectProxy::toNPObjectProxy(npObject);

            uint64_t npObjectID = npObjectProxy->npObjectID();

            // FIXME: Under some circumstances, this might leak the NPObjectProxy object. 
            // Figure out how to avoid that.
            retainNPObject(npObjectProxy);
            return NPVariantData::makeRemoteNPObjectID(npObjectID);
        }

        uint64_t npObjectID = registerNPObject(npObject, plugin);
        return NPVariantData::makeLocalNPObjectID(npObjectID);
    }

    }

    ASSERT_NOT_REACHED();
    return NPVariantData::makeVoid();
}

NPVariant NPRemoteObjectMap::npVariantDataToNPVariant(const NPVariantData& npVariantData, Plugin* plugin)
{
    NPVariant npVariant;

    switch (npVariantData.type()) {
    case NPVariantData::Void:
        VOID_TO_NPVARIANT(npVariant);
        break;
    case NPVariantData::Null:
        NULL_TO_NPVARIANT(npVariant);
        break;
    case NPVariantData::Bool:
        BOOLEAN_TO_NPVARIANT(npVariantData.boolValue(), npVariant);
        break;
    case NPVariantData::Int32:
        INT32_TO_NPVARIANT(npVariantData.int32Value(), npVariant);
        break;
    case NPVariantData::Double:
        DOUBLE_TO_NPVARIANT(npVariantData.doubleValue(), npVariant);
        break;
    case NPVariantData::String: {
        NPString npString = createNPString(npVariantData.stringValue());
        STRINGN_TO_NPVARIANT(npString.UTF8Characters, npString.UTF8Length, npVariant);
        break;
    }
    case NPVariantData::LocalNPObjectID: {
        uint64_t npObjectID = npVariantData.localNPObjectIDValue();
        ASSERT(npObjectID);

        NPObjectMessageReceiver* npObjectMessageReceiver = m_registeredNPObjects.get(npObjectID);
        if (!npObjectMessageReceiver) {
            ASSERT_NOT_REACHED();
            VOID_TO_NPVARIANT(npVariant);
            break;
        }

        NPObject* npObject = npObjectMessageReceiver->npObject();
        ASSERT(npObject);

        retainNPObject(npObject);
        OBJECT_TO_NPVARIANT(npObject, npVariant);
        break;
    }
    case NPVariantData::RemoteNPObjectID: {
        NPObject* npObjectProxy = createNPObjectProxy(npVariantData.remoteNPObjectIDValue(), plugin);
        OBJECT_TO_NPVARIANT(npObjectProxy, npVariant);
        break;
    }
    }

    return npVariant;
}

void NPRemoteObjectMap::pluginDestroyed(Plugin* plugin)
{
    Vector<NPObjectMessageReceiver*> messageReceivers;

    // Gather the receivers associated with this plug-in.
    for (HashMap<uint64_t, NPObjectMessageReceiver*>::const_iterator it = m_registeredNPObjects.begin(), end = m_registeredNPObjects.end(); it != end; ++it) {
        NPObjectMessageReceiver* npObjectMessageReceiver = it->second;
        if (npObjectMessageReceiver->plugin() == plugin)
            messageReceivers.append(npObjectMessageReceiver);
    }

    // Now delete all the receivers.
    deleteAllValues(messageReceivers);

    Vector<NPObjectProxy*> objectProxies;
    for (HashSet<NPObjectProxy*>::const_iterator it = m_npObjectProxies.begin(), end = m_npObjectProxies.end(); it != end; ++it) {
        NPObjectProxy* npObjectProxy = *it;

        if (npObjectProxy->plugin() == plugin)
            objectProxies.append(npObjectProxy);
    }

    // Invalidate and remove all proxies associated with this plug-in.
    for (size_t i = 0; i < objectProxies.size(); ++i) {
        NPObjectProxy* npObjectProxy = objectProxies[i];

        npObjectProxy->invalidate();

        ASSERT(m_npObjectProxies.contains(npObjectProxy));
        m_npObjectProxies.remove(npObjectProxy);
    }
}

CoreIPC::SyncReplyMode NPRemoteObjectMap::didReceiveSyncMessage(CoreIPC::Connection* connection, CoreIPC::MessageID messageID, CoreIPC::ArgumentDecoder* arguments, CoreIPC::ArgumentEncoder* reply)
{
    NPObjectMessageReceiver* messageReceiver = m_registeredNPObjects.get(arguments->destinationID());
    if (!messageReceiver)
        return CoreIPC::AutomaticReply;

    return messageReceiver->didReceiveSyncNPObjectMessageReceiverMessage(connection, messageID, arguments, reply);
}

} // namespace WebKit

#endif // ENABLE(PLUGIN_PROCESS)
