// Copyright (c) 2011 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.

#ifndef CHROME_BROWSER_EXTENSIONS_EXTENSION_TTS_API_H_
#define CHROME_BROWSER_EXTENSIONS_EXTENSION_TTS_API_H_

#include <queue>
#include <string>

#include "base/memory/singleton.h"
#include "base/task.h"
#include "chrome/browser/extensions/extension_function.h"
#include "chrome/browser/extensions/extension_tts_api_util.h"

// Abstract class that defines the native platform TTS interface.
class ExtensionTtsPlatformImpl {
 public:
  static ExtensionTtsPlatformImpl* GetInstance();

  // Speak the given utterance with the given parameters if possible,
  // and return true on success. Utterance will always be nonempty.
  // If the user does not specify the other values, then locale and gender
  // will be empty strings, and rate, pitch, and volume will be -1.0.
  //
  // The ExtensionTtsController will only try to speak one utterance at
  // a time. If it wants to interrupt speech, it will always call Stop
  // before speaking again, otherwise it will wait until IsSpeaking
  // returns false before calling Speak again.
  virtual bool Speak(
      const std::string& utterance,
      const std::string& locale,
      const std::string& gender,
      double rate,
      double pitch,
      double volume) = 0;

  // Stop speaking immediately and return true on success.
  virtual bool StopSpeaking() = 0;

  // Return true if the synthesis engine is currently speaking.
  virtual bool IsSpeaking() = 0;

  virtual std::string error();
  virtual void clear_error();
  virtual void set_error(const std::string& error);

 protected:
  ExtensionTtsPlatformImpl() {}
  virtual ~ExtensionTtsPlatformImpl() {}

  std::string error_;

  DISALLOW_COPY_AND_ASSIGN(ExtensionTtsPlatformImpl);
};

// One speech utterance.
class Utterance {
 public:
  // Construct an utterance given a profile, the text to speak,
  // the options passed to tts.speak, and a completion task to call
  // when the utterance is done speaking.
  Utterance(Profile* profile,
            const std::string& text,
            DictionaryValue* options,
            Task* completion_task);
  ~Utterance();

  // Calls the completion task and then destroys itself.
  void FinishAndDestroy();

  void set_error(const std::string& error) { error_ = error; }
  void set_extension_id(const std::string& extension_id) {
    extension_id_ = extension_id;
  }

  // Accessors
  Profile* profile() { return profile_; }
  const std::string& extension_id() { return extension_id_; }
  int id() { return id_; }
  const std::string& text() { return text_; }
  const Value* options() { return options_.get(); }
  const std::string& voice_name() { return voice_name_; }
  const std::string& locale() { return locale_; }
  const std::string& gender() { return gender_; }
  double rate() { return rate_; }
  double pitch() { return pitch_; }
  double volume() { return volume_; }
  bool can_enqueue() { return can_enqueue_; }
  const std::string& error() { return error_; }

 private:
  // The profile that initiated this utterance.
  Profile* profile_;

  // The extension ID of the extension providing TTS for this utterance, or
  // empty if native TTS is being used.
  std::string extension_id_;

  // The unique ID of this utterance, used to associate callback functions
  // with utterances.
  int id_;

  // The id of the next utterance, so we can associate requests with
  // responses.
  static int next_utterance_id_;

  // The text to speak.
  std::string text_;

  // The full options arg passed to tts.speak, which may include fields
  // other than the ones we explicitly parse, below.
  scoped_ptr<Value> options_;

  // The parsed options.
  std::string voice_name_;
  std::string locale_;
  std::string gender_;
  double rate_;
  double pitch_;
  double volume_;
  bool can_enqueue_;

  // The error string to pass to the completion task. Will be empty if
  // no error occurred.
  std::string error_;

  // The method to call when this utterance has completed speaking.
  Task* completion_task_;
};

// Singleton class that manages text-to-speech.
class ExtensionTtsController {
 public:
  // Get the single instance of this class.
  static ExtensionTtsController* GetInstance();

  // Returns true if we're currently speaking an utterance.
  bool IsSpeaking() const;

  // Speak the given utterance. If the utterance's can_enqueue flag is true
  // and another utterance is in progress, adds it to the end of the queue.
  // Otherwise, interrupts any current utterance and speaks this one
  // immediately.
  void SpeakOrEnqueue(Utterance* utterance);

  // Stop all utterances and flush the queue.
  void Stop();

  // Called when an extension finishes speaking an utterance.
  void OnSpeechFinished(int request_id, const std::string& error_message);

  // For unit testing.
  void SetPlatformImpl(ExtensionTtsPlatformImpl* platform_impl);

 private:
  ExtensionTtsController();
  virtual ~ExtensionTtsController();

  // Get the platform TTS implementation (or injected mock).
  ExtensionTtsPlatformImpl* GetPlatformImpl();

  // Start speaking the given utterance. Will either take ownership of
  // |utterance| or delete it if there's an error.
  void SpeakNow(Utterance* utterance);

  // Called periodically when speech is ongoing. Checks to see if the
  // underlying platform speech system has finished the current utterance,
  // and if so finishes it and pops the next utterance off the queue.
  void CheckSpeechStatus();

  // Clear the utterance queue.
  void ClearUtteranceQueue();

  // Finalize and delete the current utterance.
  void FinishCurrentUtterance();

  // Start speaking the next utterance in the queue.
  void SpeakNextUtterance();

  // Return the id string of the first extension with tts_voices in its
  // manifest that matches the speech parameters of this utterance,
  // or the empty string if none is found.
  std::string GetMatchingExtensionId(Utterance* utterance);

  ScopedRunnableMethodFactory<ExtensionTtsController> method_factory_;
  friend struct DefaultSingletonTraits<ExtensionTtsController>;

  // The current utterance being spoken.
  Utterance* current_utterance_;

  // A queue of utterances to speak after the current one finishes.
  std::queue<Utterance*> utterance_queue_;

  // A pointer to the platform implementation of text-to-speech, for
  // dependency injection.
  ExtensionTtsPlatformImpl* platform_impl_;

  DISALLOW_COPY_AND_ASSIGN(ExtensionTtsController);
};

//
// Extension API function definitions
//

class ExtensionTtsSpeakFunction : public AsyncExtensionFunction {
 private:
  ~ExtensionTtsSpeakFunction() {}
  virtual bool RunImpl();
  void SpeechFinished();
  Utterance* utterance_;
  DECLARE_EXTENSION_FUNCTION_NAME("experimental.tts.speak")
};

class ExtensionTtsStopSpeakingFunction : public SyncExtensionFunction {
 private:
  ~ExtensionTtsStopSpeakingFunction() {}
  virtual bool RunImpl();
  DECLARE_EXTENSION_FUNCTION_NAME("experimental.tts.stop")
};

class ExtensionTtsIsSpeakingFunction : public SyncExtensionFunction {
 private:
  ~ExtensionTtsIsSpeakingFunction() {}
  virtual bool RunImpl();
  DECLARE_EXTENSION_FUNCTION_NAME("experimental.tts.isSpeaking")
};

class ExtensionTtsSpeakCompletedFunction : public SyncExtensionFunction {
 private:
  ~ExtensionTtsSpeakCompletedFunction() {}
  virtual bool RunImpl();
  DECLARE_EXTENSION_FUNCTION_NAME("experimental.tts.speakCompleted")
};

#endif  // CHROME_BROWSER_EXTENSIONS_EXTENSION_TTS_API_H_