// 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 "content/common/npobject_util.h"

#include "base/string_util.h"
#include "content/common/np_channel_base.h"
#include "content/common/npobject_proxy.h"
#include "content/common/plugin_messages.h"
#include "third_party/npapi/bindings/nphostapi.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebBindings.h"
#include "webkit/plugins/npapi/plugin_host.h"
#include "webkit/glue/webkit_glue.h"

using WebKit::WebBindings;

namespace content {

// true if the current process is a plugin process, false otherwise.
static bool g_plugin_process;

namespace {
// The next 7 functions are called by the plugin code when it's using the
// NPObject.  Plugins always ignore the functions in NPClass (except allocate
// and deallocate), and instead just use the function pointers that were
// passed in NPInitialize.
// When the renderer interacts with an NPObject from the plugin, it of course
// uses the function pointers in its NPClass structure.
static bool NPN_HasMethodPatch(NPP npp,
                               NPObject *npobj,
                               NPIdentifier methodName) {
  return NPObjectProxy::NPHasMethod(npobj, methodName);
}

static bool NPN_InvokePatch(NPP npp, NPObject *npobj,
                            NPIdentifier methodName,
                            const NPVariant *args,
                            uint32_t argCount,
                            NPVariant *result) {
  return NPObjectProxy::NPInvokePrivate(npp, npobj, false, methodName, args,
                                        argCount, result);
}

static bool NPN_InvokeDefaultPatch(NPP npp,
                                   NPObject *npobj,
                                   const NPVariant *args,
                                   uint32_t argCount,
                                   NPVariant *result) {
  return NPObjectProxy::NPInvokePrivate(npp, npobj, true, 0, args, argCount,
                                        result);
}

static bool NPN_HasPropertyPatch(NPP npp,
                                 NPObject *npobj,
                                 NPIdentifier propertyName) {
  return NPObjectProxy::NPHasProperty(npobj, propertyName);
}

static bool NPN_GetPropertyPatch(NPP npp,
                                 NPObject *npobj,
                                 NPIdentifier propertyName,
                                 NPVariant *result) {
  return NPObjectProxy::NPGetProperty(npobj, propertyName, result);
}

static bool NPN_SetPropertyPatch(NPP npp,
                                 NPObject *npobj,
                                 NPIdentifier propertyName,
                                 const NPVariant *value) {
  return NPObjectProxy::NPSetProperty(npobj, propertyName, value);
}

static bool NPN_RemovePropertyPatch(NPP npp,
                                    NPObject *npobj,
                                    NPIdentifier propertyName) {
  return NPObjectProxy::NPRemoveProperty(npobj, propertyName);
}

static bool NPN_EvaluatePatch(NPP npp,
                              NPObject *npobj,
                              NPString *script,
                              NPVariant *result) {
  return NPObjectProxy::NPNEvaluate(npp, npobj, script, result);
}


static void NPN_SetExceptionPatch(NPObject *obj, const NPUTF8 *message) {
  std::string message_str(message);
  if (IsPluginProcess()) {
    NPChannelBase* renderer_channel = NPChannelBase::GetCurrentChannel();
    if (renderer_channel)
      renderer_channel->Send(new PluginHostMsg_SetException(message_str));
  } else {
    WebBindings::setException(obj, message_str.c_str());
  }
}

static bool NPN_EnumeratePatch(NPP npp, NPObject *obj,
                               NPIdentifier **identifier, uint32_t *count) {
  return NPObjectProxy::NPNEnumerate(obj, identifier, count);
}

// The overrided table of functions provided to the plugin.
NPNetscapeFuncs *GetHostFunctions() {
  static bool init = false;
  static NPNetscapeFuncs host_funcs;
  if (init)
    return &host_funcs;

  memset(&host_funcs, 0, sizeof(host_funcs));
  host_funcs.invoke = NPN_InvokePatch;
  host_funcs.invokeDefault = NPN_InvokeDefaultPatch;
  host_funcs.evaluate = NPN_EvaluatePatch;
  host_funcs.getproperty = NPN_GetPropertyPatch;
  host_funcs.setproperty = NPN_SetPropertyPatch;
  host_funcs.removeproperty = NPN_RemovePropertyPatch;
  host_funcs.hasproperty = NPN_HasPropertyPatch;
  host_funcs.hasmethod = NPN_HasMethodPatch;
  host_funcs.setexception = NPN_SetExceptionPatch;
  host_funcs.enumerate = NPN_EnumeratePatch;

  init = true;
  return &host_funcs;
}

}

void PatchNPNFunctions() {
  g_plugin_process = true;
  NPNetscapeFuncs* funcs = GetHostFunctions();
  webkit::npapi::PluginHost::Singleton()->PatchNPNetscapeFuncs(funcs);
}

bool IsPluginProcess() {
  return g_plugin_process;
}

void CreateNPIdentifierParam(NPIdentifier id, NPIdentifier_Param* param) {
  param->identifier = id;
}

NPIdentifier CreateNPIdentifier(const NPIdentifier_Param& param) {
  return param.identifier;
}

void CreateNPVariantParam(const NPVariant& variant,
                          NPChannelBase* channel,
                          NPVariant_Param* param,
                          bool release,
                          int render_view_id,
                          const GURL& page_url) {
  switch (variant.type) {
    case NPVariantType_Void:
      param->type = NPVARIANT_PARAM_VOID;
      break;
    case NPVariantType_Null:
      param->type = NPVARIANT_PARAM_NULL;
      break;
    case NPVariantType_Bool:
      param->type = NPVARIANT_PARAM_BOOL;
      param->bool_value = variant.value.boolValue;
      break;
    case NPVariantType_Int32:
      param->type = NPVARIANT_PARAM_INT;
      param->int_value = variant.value.intValue;
      break;
    case NPVariantType_Double:
      param->type = NPVARIANT_PARAM_DOUBLE;
      param->double_value = variant.value.doubleValue;
      break;
    case NPVariantType_String:
      param->type = NPVARIANT_PARAM_STRING;
      if (variant.value.stringValue.UTF8Length) {
        param->string_value.assign(variant.value.stringValue.UTF8Characters,
                                   variant.value.stringValue.UTF8Length);
      }
      break;
    case NPVariantType_Object: {
      if (variant.value.objectValue->_class == NPObjectProxy::npclass()) {
        param->type = NPVARIANT_PARAM_RECEIVER_OBJECT_ROUTING_ID;
        NPObjectProxy* proxy =
            NPObjectProxy::GetProxy(variant.value.objectValue);
        DCHECK(proxy);
        param->npobject_routing_id = proxy->route_id();
        // Don't release, because our original variant is the same as our proxy.
        release = false;
      } else {
        // The channel could be NULL if there was a channel error. The caller's
        // Send call will fail anyways.
        if (channel) {
          // NPObjectStub adds its own reference to the NPObject it owns, so if
          // we were supposed to release the corresponding variant
          // (release==true), we should still do that.
          param->type = NPVARIANT_PARAM_SENDER_OBJECT_ROUTING_ID;
          int route_id = channel->GetExistingRouteForNPObjectStub(
              variant.value.objectValue);
          if (route_id != MSG_ROUTING_NONE) {
            param->npobject_routing_id = route_id;
          } else {
            route_id = channel->GenerateRouteID();
            new NPObjectStub(
                variant.value.objectValue, channel, route_id, render_view_id,
                page_url);
            param->npobject_routing_id = route_id;
          }
        } else {
          param->type = NPVARIANT_PARAM_VOID;
        }
      }
      break;
    }
    default:
      NOTREACHED();
  }

  if (release)
    WebBindings::releaseVariantValue(const_cast<NPVariant*>(&variant));
}

bool CreateNPVariant(const NPVariant_Param& param,
                     NPChannelBase* channel,
                     NPVariant* result,
                     int render_view_id,
                     const GURL& page_url) {
  switch (param.type) {
    case NPVARIANT_PARAM_VOID:
      result->type = NPVariantType_Void;
      break;
    case NPVARIANT_PARAM_NULL:
      result->type = NPVariantType_Null;
      break;
    case NPVARIANT_PARAM_BOOL:
      result->type = NPVariantType_Bool;
      result->value.boolValue = param.bool_value;
      break;
    case NPVARIANT_PARAM_INT:
      result->type = NPVariantType_Int32;
      result->value.intValue = param.int_value;
      break;
    case NPVARIANT_PARAM_DOUBLE:
      result->type = NPVariantType_Double;
      result->value.doubleValue = param.double_value;
      break;
    case NPVARIANT_PARAM_STRING: {
      result->type = NPVariantType_String;
      void* buffer = malloc(param.string_value.size());
      size_t size = param.string_value.size();
      result->value.stringValue.UTF8Characters = static_cast<NPUTF8*>(buffer);
      memcpy(buffer, param.string_value.c_str(), size);
      result->value.stringValue.UTF8Length = static_cast<int>(size);
      break;
    }
    case NPVARIANT_PARAM_SENDER_OBJECT_ROUTING_ID: {
      result->type = NPVariantType_Object;
      NPObject* object =
          channel->GetExistingNPObjectProxy(param.npobject_routing_id);
      if (object) {
        WebBindings::retainObject(object);
        result->value.objectValue = object;
      } else {
        result->value.objectValue =
            NPObjectProxy::Create(channel,
                                  param.npobject_routing_id,
                                  render_view_id,
                                  page_url);
      }
      break;
    }
    case NPVARIANT_PARAM_RECEIVER_OBJECT_ROUTING_ID: {
      NPObjectBase* npobject_base =
          channel->GetNPObjectListenerForRoute(param.npobject_routing_id);
      if (!npobject_base) {
        DLOG(WARNING) << "Invalid routing id passed in"
                      << param.npobject_routing_id;
        return false;
      }

      DCHECK(npobject_base->GetUnderlyingNPObject() != NULL);

      result->type = NPVariantType_Object;
      result->value.objectValue = npobject_base->GetUnderlyingNPObject();
      WebBindings::retainObject(result->value.objectValue);
      break;
    }
    default:
      NOTREACHED();
  }
  return true;
}

}  // namespace content
