// 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 "base/bind.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/speech/speech_recognition_bubble_controller.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
#include "chrome/test/base/browser_with_test_window_test.h"
#include "chrome/test/base/testing_profile.h"
#include "content/test/test_browser_thread.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/rect.h"

using content::BrowserThread;
using content::WebContents;

class SkBitmap;

namespace speech {

// A mock bubble class which fakes a focus change or recognition cancel by the
// user and closing of the info bubble.
class MockSpeechRecognitionBubble : public SpeechRecognitionBubbleBase {
 public:
  enum BubbleType {
    BUBBLE_TEST_FOCUS_CHANGED,
    BUBBLE_TEST_CLICK_CANCEL,
    BUBBLE_TEST_CLICK_TRY_AGAIN,
  };

  MockSpeechRecognitionBubble(WebContents* web_contents,
                        Delegate* delegate,
                        const gfx::Rect&)
      : SpeechRecognitionBubbleBase(web_contents) {
    VLOG(1) << "MockSpeechRecognitionBubble created";
    MessageLoop::current()->PostTask(
        FROM_HERE, base::Bind(&InvokeDelegate, delegate));
  }

  static void InvokeDelegate(Delegate* delegate) {
    VLOG(1) << "MockSpeechRecognitionBubble invoking delegate for type "
            << type_;
    switch (type_) {
      case BUBBLE_TEST_FOCUS_CHANGED:
        delegate->InfoBubbleFocusChanged();
        break;
      case BUBBLE_TEST_CLICK_CANCEL:
        delegate->InfoBubbleButtonClicked(
            SpeechRecognitionBubble::BUTTON_CANCEL);
        break;
      case BUBBLE_TEST_CLICK_TRY_AGAIN:
        delegate->InfoBubbleButtonClicked(
            SpeechRecognitionBubble::BUTTON_TRY_AGAIN);
        break;
    }
  }

  static void set_type(BubbleType type) {
    type_ = type;
  }
  static BubbleType type() {
    return type_;
  }

  virtual void Show() {}
  virtual void Hide() {}
  virtual void UpdateLayout() {}
  virtual void UpdateImage() {}

 private:
  static BubbleType type_;
};

// The test fixture.
class SpeechRecognitionBubbleControllerTest
    : public SpeechRecognitionBubbleControllerDelegate,
      public BrowserWithTestWindowTest {
 public:
  SpeechRecognitionBubbleControllerTest()
      : BrowserWithTestWindowTest(),
        io_thread_(BrowserThread::IO),  // constructs a new thread and loop
        cancel_clicked_(false),
        try_again_clicked_(false),
        focus_changed_(false),
        controller_(ALLOW_THIS_IN_INITIALIZER_LIST(
            new SpeechRecognitionBubbleController(this))) {
    EXPECT_EQ(NULL, test_fixture_);
    test_fixture_ = this;
  }

  ~SpeechRecognitionBubbleControllerTest() {
    test_fixture_ = NULL;
  }

  // SpeechRecognitionBubbleControllerDelegate methods.
  virtual void InfoBubbleButtonClicked(int caller_id,
                                       SpeechRecognitionBubble::Button button) {
    VLOG(1) << "Received InfoBubbleButtonClicked for button " << button;
    EXPECT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO));
    if (button == SpeechRecognitionBubble::BUTTON_CANCEL) {
      cancel_clicked_ = true;
    } else if (button == SpeechRecognitionBubble::BUTTON_TRY_AGAIN) {
      try_again_clicked_ = true;
    }
    message_loop()->PostTask(FROM_HERE, MessageLoop::QuitClosure());
  }

  virtual void InfoBubbleFocusChanged(int caller_id) {
    VLOG(1) << "Received InfoBubbleFocusChanged";
    EXPECT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO));
    focus_changed_ = true;
    message_loop()->PostTask(FROM_HERE, MessageLoop::QuitClosure());
  }

  // testing::Test methods.
  virtual void SetUp() {
    BrowserWithTestWindowTest::SetUp();
    SpeechRecognitionBubble::set_factory(
        &SpeechRecognitionBubbleControllerTest::CreateBubble);
    io_thread_.Start();
  }

  virtual void TearDown() {
    SpeechRecognitionBubble::set_factory(NULL);
    io_thread_.Stop();
    BrowserWithTestWindowTest::TearDown();
  }

  static void ActivateBubble() {
    if (MockSpeechRecognitionBubble::type() ==
        MockSpeechRecognitionBubble::BUBBLE_TEST_FOCUS_CHANGED) {
      test_fixture_->controller_->SetBubbleWarmUpMode(kBubbleCallerId);
    } else {
      test_fixture_->controller_->SetBubbleMessage(kBubbleCallerId,
                                                   ASCIIToUTF16("Test"));
    }
  }

  static SpeechRecognitionBubble* CreateBubble(
      WebContents* web_contents,
      SpeechRecognitionBubble::Delegate* delegate,
      const gfx::Rect& element_rect) {
    EXPECT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::UI));
    // Set up to activate the bubble soon after it gets created, since we test
    // events sent by the bubble and those are handled only when the bubble is
    // active.
    MessageLoop::current()->PostTask(FROM_HERE,
                                     base::Bind(&ActivateBubble));

    // The |tab_contents| parameter would be NULL since the dummy caller id
    // passed to CreateBubble would not have matched any active tab. So get a
    // real WebContents pointer from the test fixture and pass that, because
    // the bubble controller registers for tab close notifications which need
    // a valid WebContents.
    TabContentsWrapper* wrapper =
        test_fixture_->browser()->GetSelectedTabContentsWrapper();
    if (wrapper)
      web_contents = wrapper->web_contents();
    return new MockSpeechRecognitionBubble(web_contents, delegate,
                                           element_rect);
  }

 protected:
  // The main thread of the test is marked as the IO thread and we create a new
  // one for the UI thread.
  content::TestBrowserThread io_thread_;
  bool cancel_clicked_;
  bool try_again_clicked_;
  bool focus_changed_;
  scoped_refptr<SpeechRecognitionBubbleController> controller_;

  static const int kBubbleCallerId;
  static SpeechRecognitionBubbleControllerTest* test_fixture_;
};

SpeechRecognitionBubbleControllerTest*
SpeechRecognitionBubbleControllerTest::test_fixture_ = NULL;

const int SpeechRecognitionBubbleControllerTest::kBubbleCallerId = 1;

MockSpeechRecognitionBubble::BubbleType MockSpeechRecognitionBubble::type_ =
    MockSpeechRecognitionBubble::BUBBLE_TEST_FOCUS_CHANGED;

// Test that the speech bubble UI gets created in the UI thread and that the
// focus changed callback comes back in the IO thread.
TEST_F(SpeechRecognitionBubbleControllerTest, TestFocusChanged) {
  MockSpeechRecognitionBubble::set_type(
      MockSpeechRecognitionBubble::BUBBLE_TEST_FOCUS_CHANGED);

  controller_->CreateBubble(kBubbleCallerId, 1, 1, gfx::Rect(1, 1));
  MessageLoop::current()->Run();
  EXPECT_TRUE(focus_changed_);
  EXPECT_FALSE(cancel_clicked_);
  EXPECT_FALSE(try_again_clicked_);
  controller_->CloseBubble(kBubbleCallerId);
}

// Test that the speech bubble UI gets created in the UI thread and that the
// recognition cancelled callback comes back in the IO thread.
TEST_F(SpeechRecognitionBubbleControllerTest, TestRecognitionCancelled) {
  MockSpeechRecognitionBubble::set_type(
      MockSpeechRecognitionBubble::BUBBLE_TEST_CLICK_CANCEL);

  controller_->CreateBubble(kBubbleCallerId, 1, 1, gfx::Rect(1, 1));
  MessageLoop::current()->Run();
  EXPECT_TRUE(cancel_clicked_);
  EXPECT_FALSE(try_again_clicked_);
  EXPECT_FALSE(focus_changed_);
  controller_->CloseBubble(kBubbleCallerId);
}

// Test that the speech bubble UI gets created in the UI thread and that the
// try-again button click event comes back in the IO thread.
TEST_F(SpeechRecognitionBubbleControllerTest, TestTryAgainClicked) {
  MockSpeechRecognitionBubble::set_type(
      MockSpeechRecognitionBubble::BUBBLE_TEST_CLICK_TRY_AGAIN);

  controller_->CreateBubble(kBubbleCallerId, 1, 1, gfx::Rect(1, 1));
  MessageLoop::current()->Run();
  EXPECT_FALSE(cancel_clicked_);
  EXPECT_TRUE(try_again_clicked_);
  EXPECT_FALSE(focus_changed_);
  controller_->CloseBubble(kBubbleCallerId);
}

}  // namespace speech