// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/gcm_driver/gcm_driver_android.h"

#include <stddef.h>
#include <stdint.h>

#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/notimplemented.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "third_party/jni_zero/default_conversions.h"
// Must come after all headers that specialize FromJniType() / ToJniType().
#include "components/gcm_driver/android/jni_headers/GCMDriver_jni.h"

using base::android::AttachCurrentThread;
using base::android::JavaRef;

namespace gcm {

GCMDriverAndroid::GCMDriverAndroid(
    const base::FilePath& store_path,
    const scoped_refptr<base::SequencedTaskRunner>& blocking_task_runner)
    : GCMDriver(store_path, blocking_task_runner), recorder_(this) {
  JNIEnv* env = AttachCurrentThread();
  java_ref_.Reset(Java_GCMDriver_create(env, reinterpret_cast<intptr_t>(this)));
}

GCMDriverAndroid::~GCMDriverAndroid() {
  JNIEnv* env = AttachCurrentThread();
  Java_GCMDriver_destroy(env, java_ref_);
}

void GCMDriverAndroid::OnRegisterFinished(JNIEnv* env,
                                          const std::string& app_id,
                                          const std::string& registration_id,
                                          bool success) {
  GCMClient::Result result =
      success ? GCMClient::SUCCESS : GCMClient::UNKNOWN_ERROR;

  recorder_.RecordRegistrationResponse(app_id, success);

  RegisterFinished(app_id, registration_id, result);
}

void GCMDriverAndroid::OnUnregisterFinished(JNIEnv* env,
                                            const std::string& app_id,
                                            bool success) {
  GCMClient::Result result =
      success ? GCMClient::SUCCESS : GCMClient::UNKNOWN_ERROR;

  recorder_.RecordUnregistrationResponse(app_id, success);

  RemoveEncryptionInfoAfterUnregister(app_id, result);
}

void GCMDriverAndroid::OnMessageReceived(
    JNIEnv* env,
    const std::string& app_id,
    const std::string& sender_id,
    const std::optional<std::string>& message_id,
    const std::optional<std::string>& collapse_key,
    const std::optional<std::vector<uint8_t>>& raw_data,
    const std::optional<std::vector<std::string>>& data_keys_and_values) {
  int message_byte_size = 0;

  IncomingMessage message;
  message.sender_id = sender_id;

  if (message_id) {
    message.message_id = *message_id;
  }
  if (collapse_key) {
    message.collapse_key = *collapse_key;
  }

  // Expand data_keys_and_values from array to map.
  if (data_keys_and_values) {
    for (size_t i = 0; i + 1 < data_keys_and_values->size(); i += 2) {
      message.data[(*data_keys_and_values)[i]] = (*data_keys_and_values)[i + 1];
      message_byte_size += (*data_keys_and_values)[i + 1].size();
    }
  }

  // Convert raw_data from std::vector<uint8_t> to binary std::string.
  if (raw_data && !raw_data->empty()) {
    message.raw_data.assign(raw_data->begin(), raw_data->end());
    message_byte_size += message.raw_data.size();
  }

  recorder_.RecordDataMessageReceived(app_id, message.sender_id,
                                      message_byte_size);

  DispatchMessage(app_id, message);
}

void GCMDriverAndroid::ValidateRegistration(
    const std::string& app_id,
    const std::vector<std::string>& sender_ids,
    const std::string& registration_id,
    ValidateRegistrationCallback callback) {
  // gcm_driver doesn't store registration IDs on Android, so assume it's valid.
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), true /* is_valid */));
}

void GCMDriverAndroid::AddAppHandler(const std::string& app_id,
                                     GCMAppHandler* handler) {
  GCMDriver::AddAppHandler(app_id, handler);
  JNIEnv* env = AttachCurrentThread();
  // TODO(melandory, mamir): check if messages were persisted
  // and only then go to java.
  Java_GCMDriver_replayPersistedMessages(env, java_ref_, app_id);
}

void GCMDriverAndroid::AddConnectionObserver(GCMConnectionObserver* observer) {}

void GCMDriverAndroid::RemoveConnectionObserver(
    GCMConnectionObserver* observer) {}

GCMClient* GCMDriverAndroid::GetGCMClientForTesting() const {
  NOTIMPLEMENTED();
  return NULL;
}

bool GCMDriverAndroid::IsStarted() const {
  return true;
}

bool GCMDriverAndroid::IsConnected() const {
  // TODO(gcm): hook up to GCM connected status
  return true;
}

void GCMDriverAndroid::GetGCMStatistics(GetGCMStatisticsCallback callback,
                                        ClearActivityLogs clear_logs) {
  DCHECK(!callback.is_null());

  if (clear_logs == CLEAR_LOGS)
    recorder_.Clear();

  GCMClient::GCMStatistics stats;
  stats.is_recording = recorder_.is_recording();

  recorder_.CollectActivities(&stats.recorded_activities);

  std::move(callback).Run(stats);
}

void GCMDriverAndroid::SetGCMRecording(
    const GCMStatisticsRecordingCallback& callback,
    bool recording) {
  DCHECK(!callback.is_null());

  gcm_statistics_recording_callback_ = callback;
  recorder_.set_is_recording(recording);

  GCMClient::GCMStatistics stats;
  stats.is_recording = recording;

  recorder_.CollectActivities(&stats.recorded_activities);

  callback.Run(stats);
}

void GCMDriverAndroid::SetAccountTokens(
    const std::vector<GCMClient::AccountTokenInfo>& account_tokens) {
  NOTIMPLEMENTED();
}

void GCMDriverAndroid::UpdateAccountMapping(
    const AccountMapping& account_mapping) {
  NOTIMPLEMENTED();
}

void GCMDriverAndroid::RemoveAccountMapping(const CoreAccountId& account_id) {
  NOTIMPLEMENTED();
}

base::Time GCMDriverAndroid::GetLastTokenFetchTime() {
  NOTIMPLEMENTED();
  return base::Time();
}

void GCMDriverAndroid::SetLastTokenFetchTime(const base::Time& time) {
  NOTIMPLEMENTED();
}

InstanceIDHandler* GCMDriverAndroid::GetInstanceIDHandlerInternal() {
  // Not supported for Android.
  return NULL;
}

void GCMDriverAndroid::AddHeartbeatInterval(const std::string& scope,
                                            int interval_ms) {}

void GCMDriverAndroid::RemoveHeartbeatInterval(const std::string& scope) {}

void GCMDriverAndroid::OnActivityRecorded() {
  DCHECK(gcm_statistics_recording_callback_);

  GCMClient::GCMStatistics stats;
  stats.is_recording = recorder_.is_recording();

  recorder_.CollectActivities(&stats.recorded_activities);

  gcm_statistics_recording_callback_.Run(stats);
}

GCMClient::Result GCMDriverAndroid::EnsureStarted(
    GCMClient::StartMode start_mode) {
  // TODO(johnme): Maybe we should check if GMS is available?
  return GCMClient::SUCCESS;
}

void GCMDriverAndroid::RegisterImpl(
    const std::string& app_id,
    const std::vector<std::string>& sender_ids) {
  DCHECK_EQ(1u, sender_ids.size());
  JNIEnv* env = AttachCurrentThread();

  recorder_.RecordRegistrationSent(app_id);

  Java_GCMDriver_register(env, java_ref_, app_id, sender_ids[0]);
}

void GCMDriverAndroid::UnregisterImpl(const std::string& app_id) {
  NOTREACHED();
}

void GCMDriverAndroid::UnregisterWithSenderIdImpl(
    const std::string& app_id,
    const std::string& sender_id) {
  JNIEnv* env = AttachCurrentThread();

  recorder_.RecordUnregistrationSent(app_id);

  Java_GCMDriver_unregister(env, java_ref_, app_id, sender_id);
}

void GCMDriverAndroid::SendImpl(const std::string& app_id,
                                const std::string& receiver_id,
                                const OutgoingMessage& message) {
  NOTIMPLEMENTED();
}

void GCMDriverAndroid::RecordDecryptionFailure(const std::string& app_id,
                                               GCMDecryptionResult result) {
  recorder_.RecordDecryptionFailure(app_id, result);
}

}  // namespace gcm

DEFINE_JNI(GCMDriver)
