// Copyright (c) 2010 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.

#ifndef CHROME_TEST_AUTOMATION_JAVASCRIPT_EXECUTION_CONTROLLER_H_
#define CHROME_TEST_AUTOMATION_JAVASCRIPT_EXECUTION_CONTROLLER_H_

#include <map>
#include <string>
#include <vector>

#include "base/ref_counted.h"
#include "base/scoped_ptr.h"
#include "base/values.h"
#include "base/weak_ptr.h"
#include "testing/gtest/include/gtest/gtest_prod.h"

class JavaScriptExecutionController;

// This class is a proxy to an object in JavaScript. It holds a handle which
// can be used to retrieve the actual object in JavaScript scripts.
class JavaScriptObjectProxy
    : public base::RefCountedThreadSafe<JavaScriptObjectProxy> {
 public:
  JavaScriptObjectProxy(JavaScriptExecutionController* executor, int handle);
  virtual ~JavaScriptObjectProxy();

  // Returns JavaScript which can be used for retrieving the actual object
  // associated with this proxy.
  std::string GetReferenceJavaScript();

  int handle() const { return handle_; }
  bool is_valid() const { return executor_; }

 protected:
  base::WeakPtr<JavaScriptExecutionController> executor_;
  int handle_;

 private:
  DISALLOW_COPY_AND_ASSIGN(JavaScriptObjectProxy);
};

// This class handles the execution of arbitrary JavaScript, preparing it for
// execution, and parsing its result (in JSON). It keeps track of all returned
// JavaScript objects.
class JavaScriptExecutionController
    : public base::SupportsWeakPtr<JavaScriptExecutionController> {
 public:
  JavaScriptExecutionController() {}
  virtual ~JavaScriptExecutionController() {}

  // Executes |script| and parse return value.
  // A corresponding ConvertResponse(Value* value, T* result) must exist
  // for type T.
  template <typename T>
  bool ExecuteJavaScriptAndParse(const std::string& script, T* result) {
    std::string json;
    if (!ExecuteJavaScript(script, &json))
      return false;
    scoped_ptr<Value> value;
    if (!ParseJSON(json, &value))
      return false;
    return ConvertResponse(value.get(), result);
  }

  // Executes |script| with no return.
  bool ExecuteJavaScript(const std::string& script);

  // Returns JavaScript which can be used for retrieving the actual object
  // associated with the proxy |object|.
  static std::string GetReferenceJavaScript(JavaScriptObjectProxy* object);

  // Returns the equivalent JSON for |vector|.
  static std::string Serialize(const std::vector<std::string>& vector);

 protected:
  // Executes |script| and sets the JSON response |json|. Returns true
  // on success.
  virtual bool ExecuteJavaScriptAndGetJSON(const std::string& script,
                                           std::string* json) = 0;

  // Called when this controller is tracking its first object. Used by
  // reference counted subclasses.
  virtual void FirstObjectAdded() {}

  // Called when this controller is no longer tracking any objects. Used by
  // reference counted subclasses.
  virtual void LastObjectRemoved() {}

 private:
  typedef std::map<int, JavaScriptObjectProxy*> HandleToObjectMap;

  friend class JavaScriptObjectProxy;
  // Called by JavaScriptObjectProxy on destruct.
  void Remove(int handle);

  bool ParseJSON(const std::string& json, scoped_ptr<Value>* result);

  bool ExecuteJavaScript(const std::string& script, std::string* json);

  bool ConvertResponse(Value* value, bool* result);
  bool ConvertResponse(Value* value, int* result);
  bool ConvertResponse(Value* value, std::string* result);

  template<class JavaScriptObject>
  bool ConvertResponse(Value* value, JavaScriptObject** result) {
    int handle;
    if (!value->GetAsInteger(&handle))
      return false;

    HandleToObjectMap::const_iterator iter = handle_to_object_.find(handle);
    if (iter == handle_to_object_.end()) {
      *result = new JavaScriptObject(this, handle);
      if (handle_to_object_.empty())
        FirstObjectAdded();
      handle_to_object_.insert(std::make_pair(handle, *result));
    } else {
      *result = static_cast<JavaScriptObject*>(iter->second);
    }
    return true;
  }

  template<typename T>
  bool ConvertResponse(Value* value, std::vector<T>* result) {
    if (!value->IsType(Value::TYPE_LIST))
      return false;

    ListValue* list = static_cast<ListValue*>(value);
    for (size_t i = 0; i < list->GetSize(); i++) {
      Value* inner_value;
      if (!list->Get(i, &inner_value))
        return false;
      T item;
      ConvertResponse(inner_value, &item);
      result->push_back(item);
    }
    return true;
  }

  // Weak pointer to all the object proxies that we create.
  HandleToObjectMap handle_to_object_;

  DISALLOW_COPY_AND_ASSIGN(JavaScriptExecutionController);
};

#endif  // CHROME_TEST_AUTOMATION_JAVASCRIPT_EXECUTION_CONTROLLER_H_