// Copyright 2013 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 "content/browser/speech/speech_recognizer_impl_android.h"

#include <stddef.h>

#include "base/android/context_utils.h"
#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/android/scoped_java_ref.h"
#include "base/bind.h"
#include "base/strings/utf_string_conversions.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/speech_recognition_event_listener.h"
#include "content/public/browser/speech_recognition_manager.h"
#include "content/public/browser/speech_recognition_session_config.h"
#include "content/public/common/speech_recognition_grammar.h"
#include "content/public/common/speech_recognition_result.h"
#include "jni/SpeechRecognition_jni.h"

using base::android::AppendJavaStringArrayToStringVector;
using base::android::AttachCurrentThread;
using base::android::ConvertUTF8ToJavaString;
using base::android::GetApplicationContext;
using base::android::JavaFloatArrayToFloatVector;
using base::android::JavaParamRef;

namespace content {

SpeechRecognizerImplAndroid::SpeechRecognizerImplAndroid(
    SpeechRecognitionEventListener* listener,
    int session_id)
    : SpeechRecognizer(listener, session_id),
      state_(STATE_IDLE) {
}

SpeechRecognizerImplAndroid::~SpeechRecognizerImplAndroid() { }

void SpeechRecognizerImplAndroid::StartRecognition(
    const std::string& device_id) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  // TODO(xians): Open the correct device for speech on Android.
  BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
      &SpeechRecognitionEventListener::OnRecognitionStart,
      base::Unretained(listener()),
      session_id()));
  SpeechRecognitionSessionConfig config =
      SpeechRecognitionManager::GetInstance()->GetSessionConfig(session_id());
  BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
      &content::SpeechRecognizerImplAndroid::StartRecognitionOnUIThread, this,
      config.language, config.continuous, config.interim_results));
}

void SpeechRecognizerImplAndroid::StartRecognitionOnUIThread(
    const std::string& language,
    bool continuous,
    bool interim_results) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  JNIEnv* env = AttachCurrentThread();
  j_recognition_.Reset(Java_SpeechRecognition_createSpeechRecognition(env,
      GetApplicationContext(), reinterpret_cast<intptr_t>(this)));
  Java_SpeechRecognition_startRecognition(
      env, j_recognition_, ConvertUTF8ToJavaString(env, language), continuous,
      interim_results);
}

void SpeechRecognizerImplAndroid::AbortRecognition() {
  if (BrowserThread::CurrentlyOn(BrowserThread::IO)) {
    state_ = STATE_IDLE;
    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
        &content::SpeechRecognizerImplAndroid::AbortRecognition, this));
    return;
  }
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  JNIEnv* env = AttachCurrentThread();
  if (!j_recognition_.is_null())
    Java_SpeechRecognition_abortRecognition(env, j_recognition_);
}

void SpeechRecognizerImplAndroid::StopAudioCapture() {
  if (BrowserThread::CurrentlyOn(BrowserThread::IO)) {
    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
        &content::SpeechRecognizerImplAndroid::StopAudioCapture, this));
    return;
  }
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  JNIEnv* env = AttachCurrentThread();
  if (!j_recognition_.is_null())
    Java_SpeechRecognition_stopRecognition(env, j_recognition_);
}

bool SpeechRecognizerImplAndroid::IsActive() const {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  return state_ != STATE_IDLE;
}

bool SpeechRecognizerImplAndroid::IsCapturingAudio() const {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  return state_ == STATE_CAPTURING_AUDIO;
}

void SpeechRecognizerImplAndroid::OnAudioStart(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj) {
  if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
    BrowserThread::PostTask(
        BrowserThread::IO, FROM_HERE,
        base::Bind(&SpeechRecognizerImplAndroid::OnAudioStart, this, nullptr,
                   nullptr));
    return;
  }
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  state_ = STATE_CAPTURING_AUDIO;
  listener()->OnAudioStart(session_id());
}

void SpeechRecognizerImplAndroid::OnSoundStart(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj) {
  if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
    BrowserThread::PostTask(
        BrowserThread::IO, FROM_HERE,
        base::Bind(&SpeechRecognizerImplAndroid::OnSoundStart, this, nullptr,
                   nullptr));
    return;
  }
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  listener()->OnSoundStart(session_id());
}

void SpeechRecognizerImplAndroid::OnSoundEnd(JNIEnv* env,
                                             const JavaParamRef<jobject>& obj) {
  if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
    BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
                            base::Bind(&SpeechRecognizerImplAndroid::OnSoundEnd,
                                       this, nullptr, nullptr));
    return;
  }
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  listener()->OnSoundEnd(session_id());
}

void SpeechRecognizerImplAndroid::OnAudioEnd(JNIEnv* env,
                                             const JavaParamRef<jobject>& obj) {
  if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
    BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
                            base::Bind(&SpeechRecognizerImplAndroid::OnAudioEnd,
                                       this, nullptr, nullptr));
    return;
  }
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  if (state_ == STATE_CAPTURING_AUDIO)
    state_ = STATE_AWAITING_FINAL_RESULT;
  listener()->OnAudioEnd(session_id());
}

void SpeechRecognizerImplAndroid::OnRecognitionResults(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj,
    const JavaParamRef<jobjectArray>& strings,
    const JavaParamRef<jfloatArray>& floats,
    jboolean provisional) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  std::vector<base::string16> options;
  AppendJavaStringArrayToStringVector(env, strings, &options);
  std::vector<float> scores(options.size(), 0.0);
  if (floats != NULL)
    JavaFloatArrayToFloatVector(env, floats, &scores);
  SpeechRecognitionResults results;
  results.push_back(SpeechRecognitionResult());
  SpeechRecognitionResult& result = results.back();
  CHECK_EQ(options.size(), scores.size());
  for (size_t i = 0; i < options.size(); ++i) {
    result.hypotheses.push_back(SpeechRecognitionHypothesis(
        options[i], static_cast<double>(scores[i])));
  }
  result.is_provisional = provisional;
  BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
      &SpeechRecognizerImplAndroid::OnRecognitionResultsOnIOThread,
      this, results));
}

void SpeechRecognizerImplAndroid::OnRecognitionResultsOnIOThread(
    SpeechRecognitionResults const &results) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  listener()->OnRecognitionResults(session_id(), results);
}

void SpeechRecognizerImplAndroid::OnRecognitionError(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj,
    jint error) {
  if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
    BrowserThread::PostTask(
        BrowserThread::IO, FROM_HERE,
        base::Bind(&SpeechRecognizerImplAndroid::OnRecognitionError, this,
                   nullptr, nullptr, error));
    return;
  }
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  SpeechRecognitionErrorCode code =
      static_cast<SpeechRecognitionErrorCode>(error);
  listener()->OnRecognitionError(session_id(), SpeechRecognitionError(code));
}

void SpeechRecognizerImplAndroid::OnRecognitionEnd(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj) {
  if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
    BrowserThread::PostTask(
        BrowserThread::IO, FROM_HERE,
        base::Bind(&SpeechRecognizerImplAndroid::OnRecognitionEnd, this,
                   nullptr, nullptr));
    return;
  }
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  state_ = STATE_IDLE;
  listener()->OnRecognitionEnd(session_id());
}

// static
bool SpeechRecognizerImplAndroid::RegisterSpeechRecognizer(JNIEnv* env) {
  return RegisterNativesImpl(env);
}

}  // namespace content
