// 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 "chrome/browser/extensions/extension_tts_api_chromeos.h"

#include <list>

#include "base/bind.h"
#include "base/memory/singleton.h"
#include "base/memory/weak_ptr.h"
#include "base/message_loop.h"
#include "base/stl_util.h"
#include "base/string_number_conversions.h"
#include "chrome/browser/chromeos/dbus/dbus_thread_manager.h"
#include "chrome/browser/chromeos/dbus/speech_synthesizer_client.h"
#include "chrome/browser/extensions/extension_tts_api_controller.h"
#include "chrome/browser/extensions/extension_tts_api_platform.h"

using base::DoubleToString;

namespace {
const int kSpeechCheckDelayIntervalMs = 100;
};

// Very simple queue of utterances that can't be spoken because audio
// isn't initialized yet.
struct QueuedUtterance {
  int utterance_id;
  std::string utterance;
  std::string lang;
  UtteranceContinuousParameters params;
};

class ExtensionTtsPlatformImplChromeOs
    : public ExtensionTtsPlatformImpl {
 public:
  virtual bool PlatformImplAvailable() {
    return true;
  }

  virtual bool Speak(
      int utterance_id,
      const std::string& utterance,
      const std::string& lang,
      const UtteranceContinuousParameters& params);

  virtual bool StopSpeaking();

  virtual bool SendsEvent(TtsEventType event_type);

  // Get the single instance of this class.
  static ExtensionTtsPlatformImplChromeOs* GetInstance();

  // TTS won't begin until this is called.
  void Enable();

 private:
  ExtensionTtsPlatformImplChromeOs();
  virtual ~ExtensionTtsPlatformImplChromeOs() {}

  void PollUntilSpeechFinishes(int utterance_id);
  void ContinuePollingIfSpeechIsNotFinished(int utterance_id, bool result);

  void AppendSpeakOption(std::string key,
                         std::string value,
                         std::string* options);

  int utterance_id_;
  int utterance_length_;
  std::list<QueuedUtterance*> queued_utterances_;
  bool enabled_;
  base::WeakPtrFactory<ExtensionTtsPlatformImplChromeOs> weak_ptr_factory_;

  friend struct DefaultSingletonTraits<ExtensionTtsPlatformImplChromeOs>;

  DISALLOW_COPY_AND_ASSIGN(ExtensionTtsPlatformImplChromeOs);
};

// static
ExtensionTtsPlatformImpl* ExtensionTtsPlatformImpl::GetInstance() {
  return ExtensionTtsPlatformImplChromeOs::GetInstance();
}

ExtensionTtsPlatformImplChromeOs::ExtensionTtsPlatformImplChromeOs()
    : enabled_(false),
      ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) {
}

bool ExtensionTtsPlatformImplChromeOs::Speak(
    int utterance_id,
    const std::string& utterance,
    const std::string& lang,
    const UtteranceContinuousParameters& params) {
  if (!enabled_) {
    QueuedUtterance *queued = new QueuedUtterance();
    queued->utterance_id = utterance_id;
    queued->utterance = utterance;
    queued->lang = lang;
    queued->params = params;
    queued_utterances_.push_back(queued);
    return true;
  }

  utterance_id_ = utterance_id;
  utterance_length_ = utterance.size();

  std::string options;

  if (!lang.empty()) {
    AppendSpeakOption(
        chromeos::SpeechSynthesizerClient::kSpeechPropertyLocale,
        lang,
        &options);
  }

  if (params.rate >= 0.0) {
    AppendSpeakOption(
        chromeos::SpeechSynthesizerClient::kSpeechPropertyRate,
        DoubleToString(params.rate),
        &options);
  }

  if (params.pitch >= 0.0) {
    // The TTS service allows a range of 0 to 2 for speech pitch.
    AppendSpeakOption(
        chromeos::SpeechSynthesizerClient::kSpeechPropertyPitch,
        DoubleToString(params.pitch),
        &options);
  }

  if (params.volume >= 0.0) {
    // The Chrome OS TTS service allows a range of 0 to 5 for speech volume,
    // but 5 clips, so map to a range of 0...4.
    AppendSpeakOption(
        chromeos::SpeechSynthesizerClient::kSpeechPropertyVolume,
        DoubleToString(params.volume * 4),
        &options);
  }

  chromeos::SpeechSynthesizerClient* speech_synthesizer_client =
      chromeos::DBusThreadManager::Get()->GetSpeechSynthesizerClient();

  speech_synthesizer_client->Speak(utterance, options);
  if (utterance_id_ >= 0) {
    ExtensionTtsController* controller = ExtensionTtsController::GetInstance();
    controller->OnTtsEvent(utterance_id_, TTS_EVENT_START, 0, std::string());
    PollUntilSpeechFinishes(utterance_id_);
  }

  return true;
}

bool ExtensionTtsPlatformImplChromeOs::StopSpeaking() {
  // If we haven't been enabled yet, clear the internal queue.
  if (!enabled_) {
    STLDeleteElements(&queued_utterances_);
    return true;
  }

  chromeos::DBusThreadManager::Get()->GetSpeechSynthesizerClient()->
      StopSpeaking();
  return true;
}

bool ExtensionTtsPlatformImplChromeOs::SendsEvent(TtsEventType event_type) {
  return (event_type == TTS_EVENT_START ||
          event_type == TTS_EVENT_END ||
          event_type == TTS_EVENT_ERROR);
}

void ExtensionTtsPlatformImplChromeOs::Enable() {
  enabled_ = true;
  while (!queued_utterances_.empty()) {
    QueuedUtterance* queued = queued_utterances_.front();
    Speak(queued->utterance_id,
          queued->utterance,
          queued->lang,
          queued->params);
    delete queued;
    queued_utterances_.pop_front();
  }
}

void ExtensionTtsPlatformImplChromeOs::PollUntilSpeechFinishes(
    int utterance_id) {
  if (utterance_id != utterance_id_) {
    // This utterance must have been interrupted or cancelled.
    return;
  }
  chromeos::SpeechSynthesizerClient* speech_synthesizer_client =
      chromeos::DBusThreadManager::Get()->GetSpeechSynthesizerClient();
  speech_synthesizer_client->IsSpeaking(base::Bind(
      &ExtensionTtsPlatformImplChromeOs::ContinuePollingIfSpeechIsNotFinished,
      weak_ptr_factory_.GetWeakPtr(), utterance_id));
}

void ExtensionTtsPlatformImplChromeOs::ContinuePollingIfSpeechIsNotFinished(
    int utterance_id, bool is_speaking) {
  if (utterance_id != utterance_id_) {
    // This utterance must have been interrupted or cancelled.
    return;
  }
  if (!is_speaking) {
    ExtensionTtsController* controller = ExtensionTtsController::GetInstance();
    controller->OnTtsEvent(
        utterance_id_, TTS_EVENT_END, utterance_length_, std::string());
    return;
  }
  // Continue polling.
  MessageLoop::current()->PostDelayedTask(
      FROM_HERE, base::Bind(
          &ExtensionTtsPlatformImplChromeOs::PollUntilSpeechFinishes,
          weak_ptr_factory_.GetWeakPtr(),
          utterance_id),
      kSpeechCheckDelayIntervalMs);
}

void ExtensionTtsPlatformImplChromeOs::AppendSpeakOption(
    std::string key,
    std::string value,
    std::string* options) {
  *options +=
      key +
      chromeos::SpeechSynthesizerClient::kSpeechPropertyEquals +
      value +
      chromeos::SpeechSynthesizerClient::kSpeechPropertyDelimiter;
}

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

// global
void EnableChromeOsTts() {
  ExtensionTtsPlatformImplChromeOs::GetInstance()->Enable();
}