// Copyright (c) 2012 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 "device/geolocation/location_api_adapter_android.h"

#include "base/android/context_utils.h"
#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/bind.h"
#include "base/location.h"
#include "base/threading/thread_task_runner_handle.h"
#include "device/geolocation/location_provider_android.h"
#include "jni/LocationProviderAdapter_jni.h"

using base::android::AttachCurrentThread;
using base::android::CheckException;
using base::android::ClearException;
using base::android::JavaParamRef;
using device::AndroidLocationApiAdapter;

static void NewLocationAvailable(JNIEnv* env,
                                 const JavaParamRef<jclass>&,
                                 jdouble latitude,
                                 jdouble longitude,
                                 jdouble time_stamp,
                                 jboolean has_altitude,
                                 jdouble altitude,
                                 jboolean has_accuracy,
                                 jdouble accuracy,
                                 jboolean has_heading,
                                 jdouble heading,
                                 jboolean has_speed,
                                 jdouble speed) {
  AndroidLocationApiAdapter::OnNewLocationAvailable(
      latitude, longitude, time_stamp, has_altitude, altitude, has_accuracy,
      accuracy, has_heading, heading, has_speed, speed);
}

static void NewErrorAvailable(JNIEnv* env,
                              const JavaParamRef<jclass>&,
                              const JavaParamRef<jstring>& message) {
  AndroidLocationApiAdapter::OnNewErrorAvailable(env, message);
}

namespace device {

AndroidLocationApiAdapter::AndroidLocationApiAdapter()
    : location_provider_(NULL) {}

AndroidLocationApiAdapter::~AndroidLocationApiAdapter() {
  CHECK(!location_provider_);
  CHECK(!task_runner_.get());
  CHECK(java_location_provider_android_object_.is_null());
}

bool AndroidLocationApiAdapter::Start(
    LocationProviderAndroid* location_provider,
    bool high_accuracy) {
  JNIEnv* env = AttachCurrentThread();
  if (!location_provider_) {
    location_provider_ = location_provider;
    CHECK(java_location_provider_android_object_.is_null());
    CreateJavaObject(env);
    {
      base::AutoLock lock(lock_);
      CHECK(!task_runner_.get());
      task_runner_ = base::ThreadTaskRunnerHandle::Get();
    }
  }
  // At this point we should have all our pre-conditions ready, and they'd only
  // change in Stop() which must be called on the same thread as here.
  CHECK(location_provider_);
  CHECK(task_runner_.get());
  CHECK(!java_location_provider_android_object_.is_null());
  // We'll start receiving notifications from java in the main thread looper
  // until Stop() is called.
  return Java_LocationProviderAdapter_start(
      env, java_location_provider_android_object_, high_accuracy);
}

void AndroidLocationApiAdapter::Stop() {
  if (!location_provider_) {
    CHECK(!task_runner_.get());
    CHECK(java_location_provider_android_object_.is_null());
    return;
  }

  {
    base::AutoLock lock(lock_);
    task_runner_ = NULL;
  }

  location_provider_ = NULL;

  JNIEnv* env = AttachCurrentThread();
  Java_LocationProviderAdapter_stop(env,
                                    java_location_provider_android_object_);
  java_location_provider_android_object_.Reset();
}

// static
void AndroidLocationApiAdapter::NotifyProviderNewGeoposition(
    const Geoposition& geoposition) {
  // Called on the geolocation thread, safe to access location_provider_ here.
  if (GetInstance()->location_provider_) {
    CHECK(GetInstance()->task_runner_->BelongsToCurrentThread());
    GetInstance()->location_provider_->NotifyNewGeoposition(geoposition);
  }
}

// static
void AndroidLocationApiAdapter::OnNewLocationAvailable(double latitude,
                                                       double longitude,
                                                       double time_stamp,
                                                       bool has_altitude,
                                                       double altitude,
                                                       bool has_accuracy,
                                                       double accuracy,
                                                       bool has_heading,
                                                       double heading,
                                                       bool has_speed,
                                                       double speed) {
  Geoposition position;
  position.latitude = latitude;
  position.longitude = longitude;
  position.timestamp = base::Time::FromDoubleT(time_stamp);
  if (has_altitude)
    position.altitude = altitude;
  if (has_accuracy)
    position.accuracy = accuracy;
  if (has_heading)
    position.heading = heading;
  if (has_speed)
    position.speed = speed;
  GetInstance()->OnNewGeopositionInternal(position);
}

// static
void AndroidLocationApiAdapter::OnNewErrorAvailable(JNIEnv* env,
                                                    jstring message) {
  Geoposition position_error;
  position_error.error_code = Geoposition::ERROR_CODE_POSITION_UNAVAILABLE;
  position_error.error_message =
      base::android::ConvertJavaStringToUTF8(env, message);
  GetInstance()->OnNewGeopositionInternal(position_error);
}

// static
AndroidLocationApiAdapter* AndroidLocationApiAdapter::GetInstance() {
  return base::Singleton<AndroidLocationApiAdapter>::get();
}

// static
bool AndroidLocationApiAdapter::RegisterGeolocationService(JNIEnv* env) {
  return RegisterNativesImpl(env);
}

void AndroidLocationApiAdapter::CreateJavaObject(JNIEnv* env) {
  // Create the Java LocationProviderAdapter object.
  java_location_provider_android_object_.Reset(
      Java_LocationProviderAdapter_create(
          env, base::android::GetApplicationContext()));
  CHECK(!java_location_provider_android_object_.is_null());
}

void AndroidLocationApiAdapter::OnNewGeopositionInternal(
    const Geoposition& geoposition) {
  base::AutoLock lock(lock_);
  if (!task_runner_.get())
    return;
  task_runner_->PostTask(
      FROM_HERE,
      base::Bind(&AndroidLocationApiAdapter::NotifyProviderNewGeoposition,
                 geoposition));
}

}  // namespace device
