// Copyright 2014 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 "components/cronet/android/chromium_url_request_context.h"

#include <string>

#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/metrics/statistics_recorder.h"
#include "base/values.h"
#include "components/cronet/android/chromium_url_request.h"
#include "components/cronet/android/url_request_adapter.h"
#include "components/cronet/android/url_request_context_adapter.h"
#include "components/cronet/url_request_context_config.h"
#include "jni/ChromiumUrlRequestContext_jni.h"

namespace {

// Delegate of URLRequestContextAdapter that delivers callbacks to the Java
// layer.
class JniURLRequestContextAdapterDelegate
    : public cronet::URLRequestContextAdapter::
          URLRequestContextAdapterDelegate {
 public:
  JniURLRequestContextAdapterDelegate(JNIEnv* env, jobject owner)
      : owner_(env->NewGlobalRef(owner)) {}

  virtual void OnContextInitialized(
      cronet::URLRequestContextAdapter* context) override {
    JNIEnv* env = base::android::AttachCurrentThread();
    cronet::Java_ChromiumUrlRequestContext_initNetworkThread(env, owner_);
    // TODO(dplotnikov): figure out if we need to detach from the thread.
    // The documentation says we should detach just before the thread exits.
  }

 protected:
  virtual ~JniURLRequestContextAdapterDelegate() {
    JNIEnv* env = base::android::AttachCurrentThread();
    env->DeleteGlobalRef(owner_);
  }

 private:
  jobject owner_;
};

}  // namespace

namespace cronet {

// Explicitly register static JNI functions.
bool ChromiumUrlRequestContextRegisterJni(JNIEnv* env) {
  return RegisterNativesImpl(env);
}

// Sets global user-agent to be used for all subsequent requests.
static jlong CreateRequestContextAdapter(JNIEnv* env,
                                         jobject object,
                                         jobject context,
                                         jstring user_agent,
                                         jint log_level,
                                         jstring config) {
  std::string user_agent_string =
      base::android::ConvertJavaStringToUTF8(env, user_agent);

  std::string config_string =
      base::android::ConvertJavaStringToUTF8(env, config);

  scoped_ptr<base::Value> config_value(base::JSONReader::Read(config_string));
  if (!config_value || !config_value->IsType(base::Value::TYPE_DICTIONARY)) {
    DLOG(ERROR) << "Bad JSON: " << config_string;
    return 0;
  }

  scoped_ptr<URLRequestContextConfig> context_config(
      new URLRequestContextConfig());
  base::JSONValueConverter<URLRequestContextConfig> converter;
  if (!converter.Convert(*config_value, context_config.get())) {
    DLOG(ERROR) << "Bad Config: " << config_value;
    return 0;
  }

  // Set application context.
  base::android::ScopedJavaLocalRef<jobject> scoped_context(env, context);
  base::android::InitApplicationContext(env, scoped_context);

  // TODO(mef): MinLogLevel is global, shared by all URLRequestContexts.
  // Revisit this if each URLRequestContext would need an individual log level.
  logging::SetMinLogLevel(static_cast<int>(log_level));

  // TODO(dplotnikov): set application context.
  URLRequestContextAdapter* adapter = new URLRequestContextAdapter(
      new JniURLRequestContextAdapterDelegate(env, object), user_agent_string);
  adapter->AddRef();  // Hold onto this ref-counted object.
  adapter->Initialize(context_config.Pass());
  return reinterpret_cast<jlong>(adapter);
}

// Releases native objects.
static void ReleaseRequestContextAdapter(JNIEnv* env,
                                         jobject object,
                                         jlong urlRequestContextAdapter) {
  URLRequestContextAdapter* adapter =
      reinterpret_cast<URLRequestContextAdapter*>(urlRequestContextAdapter);
  // TODO(mef): Revisit this from thread safety point of view: Can we delete a
  // thread while running on that thread?
  // URLRequestContextAdapter is a ref-counted object, and may have pending
  // tasks,
  // so we need to release it instead of deleting here.
  adapter->Release();
}

// Starts recording statistics.
static void InitializeStatistics(JNIEnv* env, jobject jcaller) {
  base::StatisticsRecorder::Initialize();
}

// Gets current statistics with |filter| as a substring as JSON text (an empty
// |filter| will include all registered histograms).
static jstring GetStatisticsJSON(JNIEnv* env, jobject jcaller, jstring filter) {
  std::string query = base::android::ConvertJavaStringToUTF8(env, filter);
  std::string json = base::StatisticsRecorder::ToJSON(query);
  return base::android::ConvertUTF8ToJavaString(env, json).Release();
}

// Starts recording NetLog into file with |fileName|.
static void StartNetLogToFile(JNIEnv* env,
                              jobject jcaller,
                              jlong urlRequestContextAdapter,
                              jstring fileName) {
  URLRequestContextAdapter* adapter =
      reinterpret_cast<URLRequestContextAdapter*>(urlRequestContextAdapter);
  std::string file_name = base::android::ConvertJavaStringToUTF8(env, fileName);
  adapter->StartNetLogToFile(file_name);
}

// Stops recording NetLog.
static void StopNetLog(JNIEnv* env,
                       jobject jcaller,
                       jlong urlRequestContextAdapter) {
  URLRequestContextAdapter* adapter =
      reinterpret_cast<URLRequestContextAdapter*>(urlRequestContextAdapter);
  adapter->StopNetLog();
}

// Called on application's main Java thread.
static void InitRequestContextOnMainThread(JNIEnv* env,
                                           jobject jcaller,
                                           jlong url_request_context_adapter) {
  URLRequestContextAdapter* adapter =
      reinterpret_cast<URLRequestContextAdapter*>(url_request_context_adapter);
  adapter->InitRequestContextOnMainThread();
}

}  // namespace cronet