// Copyright (c) 2008-2009 The Chromium Embedded Framework 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 "cefclient/binding_test.h"
#include <sstream>
#include <string>
#include <vector>
#include "include/cef_browser.h"
#include "include/cef_frame.h"
#include "include/cef_v8.h"


// Implementation of the V8 handler class for the "window.cef_test.Dump"
// function.
class ClientV8FunctionHandler : public CefV8Handler {
 public:
  ClientV8FunctionHandler() {}
  virtual ~ClientV8FunctionHandler() {}

  // Execute with the specified argument list and return value.  Return true if
  // the method was handled.
  virtual bool Execute(const CefString& name,
                       CefRefPtr<CefV8Value> object,
                       const CefV8ValueList& arguments,
                       CefRefPtr<CefV8Value>& retval,
                       CefString& exception) OVERRIDE {
    if (name == "Dump") {
      // The "Dump" function will return a human-readable dump of the input
      // arguments.
      std::stringstream stream;
      size_t i;

      for (i = 0; i < arguments.size(); ++i) {
        stream << "arg[" << i << "] = ";
        PrintValue(arguments[i], stream, 0);
        stream << "\n";
      }

      retval = CefV8Value::CreateString(stream.str());
      return true;
    } else if (name == "Call") {
      // The "Call" function will execute a function to get an object and then
      // return the result of calling a function belonging to that object.  The
      // first arument is the function that will return an object and the second
      // argument is the function that will be called on that returned object.
      int argSize = arguments.size();
      if (argSize < 2 || !arguments[0]->IsFunction()
          || !arguments[1]->IsString())
        return false;

      CefV8ValueList argList;

      // Execute the function stored in the first argument to retrieve an
      // object.
      CefRefPtr<CefV8Value> objectPtr =
          arguments[0]->ExecuteFunction(object, argList);
      if (arguments[0]->HasException())
        exception = arguments[0]->GetException()->GetMessage();

      // Verify that the returned value is an object.
      if (!objectPtr.get() || !objectPtr->IsObject())
        return false;

      // Retrieve the member function specified by name in the second argument
      // from the object.
      CefRefPtr<CefV8Value> funcPtr =
          objectPtr->GetValue(arguments[1]->GetStringValue());
      // Verify that the returned value is a function.
      if (!funcPtr.get() || !funcPtr->IsFunction())
        return false;

      // Pass any additional arguments on to the member function.
      for (int i = 2; i < argSize; ++i)
        argList.push_back(arguments[i]);

      // Execute the member function.
      funcPtr->ExecuteFunction(arguments[0], argList);
      if (funcPtr->HasException())
        exception = funcPtr->GetException()->GetMessage();
      return true;
    }
    return false;
  }

  // Simple function for formatted output of a V8 value.
  void PrintValue(CefRefPtr<CefV8Value> value, std::stringstream &stream,
                  int indent) {
    std::stringstream indent_stream;
    for (int i = 0; i < indent; ++i)
      indent_stream << "  ";
    std::string indent_str = indent_stream.str();

    if (value->IsUndefined())
      stream << "(undefined)";
    else if (value->IsNull())
      stream << "(null)";
    else if (value->IsBool())
      stream << "(bool) " << (value->GetBoolValue() ? "true" : "false");
    else if (value->IsInt())
      stream << "(int) " << value->GetIntValue();
    else if (value->IsDouble())
      stream << "(double) " << value->GetDoubleValue();
    else if (value->IsString())
      stream << "(string) " << std::string(value->GetStringValue());
    else if (value->IsFunction())
      stream << "(function) " << std::string(value->GetFunctionName());
    else if (value->IsArray()) {
      stream << "(array) [";
      int len = value->GetArrayLength();
      for (int i = 0; i < len; ++i) {
        stream << "\n  " << indent_str.c_str() << i << " = ";
        PrintValue(value->GetValue(i), stream, indent+1);
      }
      stream << "\n" << indent_str.c_str() << "]";
    } else if (value->IsObject()) {
      stream << "(object) [";
      std::vector<CefString> keys;
      if (value->GetKeys(keys)) {
        for (size_t i = 0; i < keys.size(); ++i) {
          stream << "\n  " << indent_str.c_str() << keys[i].c_str() << " = ";
          PrintValue(value->GetValue(keys[i]), stream, indent+1);
        }
      }
      stream << "\n" << indent_str.c_str() << "]";
    }
  }

  IMPLEMENT_REFCOUNTING(ClientV8FunctionHandler);
};


void InitBindingTest(CefRefPtr<CefBrowser> browser,
                     CefRefPtr<CefFrame> frame,
                     CefRefPtr<CefV8Value> object) {
  // Create the new V8 object.
  CefRefPtr<CefV8Value> testObjPtr = CefV8Value::CreateObject(NULL);
  // Add the new V8 object to the global window object with the name
  // "cef_test".
  object->SetValue("cef_test", testObjPtr, V8_PROPERTY_ATTRIBUTE_NONE);

  // Create an instance of ClientV8FunctionHandler as the V8 handler.
  CefRefPtr<CefV8Handler> handlerPtr = new ClientV8FunctionHandler();

  // Add a new V8 function to the cef_test object with the name "Dump".
  testObjPtr->SetValue("Dump",
      CefV8Value::CreateFunction("Dump", handlerPtr),
      V8_PROPERTY_ATTRIBUTE_NONE);
  // Add a new V8 function to the cef_test object with the name "Call".
  testObjPtr->SetValue("Call",
      CefV8Value::CreateFunction("Call", handlerPtr),
      V8_PROPERTY_ATTRIBUTE_NONE);
}

void RunBindingTest(CefRefPtr<CefBrowser> browser) {
  std::string html =
    "<html><body>ClientV8FunctionHandler says:<br><pre>"
    "<script language=\"JavaScript\">"
    "document.writeln(window.cef_test.Dump(false, 1, 7.6654,'bar',"
    "  [false,true],[5, 7.654, 1, 'foo', [true, 'bar'], 8]));"
    "document.writeln(window.cef_test.Dump(cef));"
    "document.writeln("
    "  window.cef_test.Call(cef.test.test_object, 'GetMessage'));"
    "function my_object() {"
    "  var obj = {};"
    "  (function() {"
    "    obj.GetMessage = function(a) {"
    "      return 'Calling a function with value '+a+' on a user object "
    "succeeded.';"
    "    };"
    "  })();"
    "  return obj;"
    "};"
    "document.writeln("
    "  window.cef_test.Call(my_object, 'GetMessage', 'foobar'));"
    "</script>"
    "</pre></body></html>";
  browser->GetMainFrame()->LoadString(html, "about:blank");
}