// 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 "ash/system/web_notification/web_notification_tray.h"

#include <vector>

#include "ash/display/display_manager.h"
#include "ash/root_window_controller.h"
#include "ash/shelf/shelf_layout_manager.h"
#include "ash/shelf/shelf_widget.h"
#include "ash/shell.h"
#include "ash/system/status_area_widget.h"
#include "ash/system/tray/system_tray.h"
#include "ash/system/tray/system_tray_item.h"
#include "ash/test/ash_test_base.h"
#include "ash/test/status_area_widget_test_helper.h"
#include "ash/test/test_system_tray_delegate.h"
#include "ash/wm/window_state.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/test/event_generator.h"
#include "ui/aura/window.h"
#include "ui/gfx/display.h"
#include "ui/gfx/screen.h"
#include "ui/message_center/message_center_style.h"
#include "ui/message_center/message_center_tray.h"
#include "ui/message_center/notification_list.h"
#include "ui/message_center/notification_types.h"
#include "ui/message_center/views/message_center_bubble.h"
#include "ui/message_center/views/message_popup_collection.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"

namespace ash {

namespace {

WebNotificationTray* GetTray() {
  return StatusAreaWidgetTestHelper::GetStatusAreaWidget()->
      web_notification_tray();
}

WebNotificationTray* GetSecondaryTray() {
  StatusAreaWidget* status_area_widget =
      StatusAreaWidgetTestHelper::GetSecondaryStatusAreaWidget();
  if (status_area_widget)
    return status_area_widget->web_notification_tray();
  return NULL;
}

message_center::MessageCenter* GetMessageCenter() {
  return GetTray()->message_center();
}

SystemTray* GetSystemTray() {
  return StatusAreaWidgetTestHelper::GetStatusAreaWidget()->system_tray();
}

// Trivial item implementation for testing PopupAndSystemTray test case.
class TestItem : public SystemTrayItem {
 public:
  TestItem() : SystemTrayItem(GetSystemTray()) {}

  virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE {
    views::View* default_view = new views::View;
    default_view->SetLayoutManager(new views::FillLayout);
    default_view->AddChildView(new views::Label(base::UTF8ToUTF16("Default")));
    return default_view;
  }

  virtual views::View* CreateNotificationView(
      user::LoginStatus status) OVERRIDE {
    return new views::View;
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(TestItem);
};

}  // namespace

class WebNotificationTrayTest : public test::AshTestBase {
 public:
  WebNotificationTrayTest() {}
  virtual ~WebNotificationTrayTest() {}

  virtual void TearDown() OVERRIDE {
    GetMessageCenter()->RemoveAllNotifications(false);
    test::AshTestBase::TearDown();
  }

 protected:
  void AddNotification(const std::string& id) {
    scoped_ptr<message_center::Notification> notification;
    notification.reset(new message_center::Notification(
        message_center::NOTIFICATION_TYPE_SIMPLE,
        id,
        base::ASCIIToUTF16("Test Web Notification"),
        base::ASCIIToUTF16("Notification message body."),
        gfx::Image(),
        base::ASCIIToUTF16("www.test.org"),
        message_center::NotifierId(),
        message_center::RichNotificationData(),
        NULL /* delegate */));
    GetMessageCenter()->AddNotification(notification.Pass());
  }

  void UpdateNotification(const std::string& old_id,
                          const std::string& new_id) {
    scoped_ptr<message_center::Notification> notification;
    notification.reset(new message_center::Notification(
        message_center::NOTIFICATION_TYPE_SIMPLE,
        new_id,
        base::ASCIIToUTF16("Updated Web Notification"),
        base::ASCIIToUTF16("Updated message body."),
        gfx::Image(),
        base::ASCIIToUTF16("www.test.org"),
        message_center::NotifierId(),
        message_center::RichNotificationData(),
        NULL /* delegate */));
    GetMessageCenter()->UpdateNotification(old_id, notification.Pass());
  }

  void RemoveNotification(const std::string& id) {
    GetMessageCenter()->RemoveNotification(id, false);
  }

  views::Widget* GetWidget() {
    return GetTray()->GetWidget();
  }

  gfx::Rect GetPopupWorkArea() {
    return GetPopupWorkAreaForTray(GetTray());
  }

  gfx::Rect GetPopupWorkAreaForTray(WebNotificationTray* tray) {
    return tray->popup_collection_->work_area_;
  }

  bool IsPopupVisible() {
    return GetTray()->IsPopupVisible();
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(WebNotificationTrayTest);
};

TEST_F(WebNotificationTrayTest, WebNotifications) {
  // TODO(mukai): move this test case to ui/message_center.
  ASSERT_TRUE(GetWidget());

  // Add a notification.
  AddNotification("test_id1");
  EXPECT_EQ(1u, GetMessageCenter()->NotificationCount());
  EXPECT_TRUE(GetMessageCenter()->FindVisibleNotificationById("test_id1"));
  AddNotification("test_id2");
  AddNotification("test_id2");
  EXPECT_EQ(2u, GetMessageCenter()->NotificationCount());
  EXPECT_TRUE(GetMessageCenter()->FindVisibleNotificationById("test_id2"));

  // Ensure that updating a notification does not affect the count.
  UpdateNotification("test_id2", "test_id3");
  UpdateNotification("test_id3", "test_id3");
  EXPECT_EQ(2u, GetMessageCenter()->NotificationCount());
  EXPECT_FALSE(GetMessageCenter()->FindVisibleNotificationById("test_id2"));

  // Ensure that Removing the first notification removes it from the tray.
  RemoveNotification("test_id1");
  EXPECT_FALSE(GetMessageCenter()->FindVisibleNotificationById("test_id1"));
  EXPECT_EQ(1u, GetMessageCenter()->NotificationCount());

  // Remove the remianing notification.
  RemoveNotification("test_id3");
  EXPECT_EQ(0u, GetMessageCenter()->NotificationCount());
  EXPECT_FALSE(GetMessageCenter()->FindVisibleNotificationById("test_id3"));
}

TEST_F(WebNotificationTrayTest, WebNotificationPopupBubble) {
  // TODO(mukai): move this test case to ui/message_center.
  ASSERT_TRUE(GetWidget());

  // Adding a notification should show the popup bubble.
  AddNotification("test_id1");
  EXPECT_TRUE(GetTray()->IsPopupVisible());

  // Updating a notification should not hide the popup bubble.
  AddNotification("test_id2");
  UpdateNotification("test_id2", "test_id3");
  EXPECT_TRUE(GetTray()->IsPopupVisible());

  // Removing the first notification should not hide the popup bubble.
  RemoveNotification("test_id1");
  EXPECT_TRUE(GetTray()->IsPopupVisible());

  // Removing the visible notification should hide the popup bubble.
  RemoveNotification("test_id3");
  EXPECT_FALSE(GetTray()->IsPopupVisible());

  // Now test that we can show multiple popups and then show the message center.
  AddNotification("test_id4");
  AddNotification("test_id5");
  EXPECT_TRUE(GetTray()->IsPopupVisible());

  GetTray()->message_center_tray_->ShowMessageCenterBubble();
  GetTray()->message_center_tray_->HideMessageCenterBubble();

  EXPECT_FALSE(GetTray()->IsPopupVisible());
}

using message_center::NotificationList;


// Flakily fails. http://crbug.com/229791
TEST_F(WebNotificationTrayTest, DISABLED_ManyMessageCenterNotifications) {
  // Add the max visible notifications +1, ensure the correct visible number.
  size_t notifications_to_add =
      message_center::kMaxVisibleMessageCenterNotifications + 1;
  for (size_t i = 0; i < notifications_to_add; ++i) {
    std::string id = base::StringPrintf("test_id%d", static_cast<int>(i));
    AddNotification(id);
  }
  bool shown = GetTray()->message_center_tray_->ShowMessageCenterBubble();
  EXPECT_TRUE(shown);
  RunAllPendingInMessageLoop();
  EXPECT_TRUE(GetTray()->message_center_bubble() != NULL);
  EXPECT_EQ(notifications_to_add,
            GetMessageCenter()->NotificationCount());
  EXPECT_EQ(message_center::kMaxVisibleMessageCenterNotifications,
            GetTray()->GetMessageCenterBubbleForTest()->
                NumMessageViewsForTest());
}

// Flakily times out. http://crbug.com/229792
TEST_F(WebNotificationTrayTest, DISABLED_ManyPopupNotifications) {
  // Add the max visible popup notifications +1, ensure the correct num visible.
  size_t notifications_to_add =
      message_center::kMaxVisiblePopupNotifications + 1;
  for (size_t i = 0; i < notifications_to_add; ++i) {
    std::string id = base::StringPrintf("test_id%d", static_cast<int>(i));
    AddNotification(id);
  }
  GetTray()->ShowPopups();
  EXPECT_TRUE(GetTray()->IsPopupVisible());
  EXPECT_EQ(notifications_to_add,
            GetMessageCenter()->NotificationCount());
  NotificationList::PopupNotifications popups =
      GetMessageCenter()->GetPopupNotifications();
  EXPECT_EQ(message_center::kMaxVisiblePopupNotifications, popups.size());
}

#if defined(OS_CHROMEOS)
// Display notification is ChromeOS only.
#define MAYBE_PopupShownOnBothDisplays PopupShownOnBothDisplays
#define MAYBE_PopupAndSystemTrayMultiDisplay PopupAndSystemTrayMultiDisplay
#else
#define MAYBE_PopupShownOnBothDisplays DISABLED_PopupShownOnBothDisplays
#define MAYBE_PopupAndSystemTrayMultiDisplay \
  DISABLED_PopupAndSystemTrayMultiDisplay
#endif

// Verifies if the notification appears on both displays when extended mode.
TEST_F(WebNotificationTrayTest, MAYBE_PopupShownOnBothDisplays) {
  if (!SupportsMultipleDisplays())
    return;

  // Enables to appear the notification for display changes.
  test::TestSystemTrayDelegate* tray_delegate =
      static_cast<test::TestSystemTrayDelegate*>(
          Shell::GetInstance()->system_tray_delegate());
  tray_delegate->set_should_show_display_notification(true);

  UpdateDisplay("400x400,200x200");
  // UpdateDisplay() creates the display notifications, so popup is visible.
  EXPECT_TRUE(GetTray()->IsPopupVisible());
  WebNotificationTray* secondary_tray = GetSecondaryTray();
  ASSERT_TRUE(secondary_tray);
  EXPECT_TRUE(secondary_tray->IsPopupVisible());

  // Transition to mirroring and then back to extended display, which recreates
  // root window controller and shelf with having notifications. This code
  // verifies it doesn't cause crash and popups are still visible. See
  // http://crbug.com/263664
  DisplayManager* display_manager = Shell::GetInstance()->display_manager();

  display_manager->SetSecondDisplayMode(DisplayManager::MIRRORING);
  UpdateDisplay("400x400,200x200");
  EXPECT_TRUE(GetTray()->IsPopupVisible());
  EXPECT_FALSE(GetSecondaryTray());

  display_manager->SetSecondDisplayMode(DisplayManager::EXTENDED);
  UpdateDisplay("400x400,200x200");
  EXPECT_TRUE(GetTray()->IsPopupVisible());
  secondary_tray = GetSecondaryTray();
  ASSERT_TRUE(secondary_tray);
  EXPECT_TRUE(secondary_tray->IsPopupVisible());
}

#if defined(OS_CHROMEOS)
// PopupAndSystemTray may fail in platforms other than ChromeOS because the
// RootWindow's bound can be bigger than gfx::Display's work area so that
// openingsystem tray doesn't affect at all the work area of popups.
#define MAYBE_PopupAndSystemTray PopupAndSystemTray
#define MAYBE_PopupAndAutoHideShelf PopupAndAutoHideShelf
#define MAYBE_PopupAndFullscreen PopupAndFullscreen
#else
#define MAYBE_PopupAndSystemTray DISABLED_PopupAndSystemTray
#define MAYBE_PopupAndAutoHideShelf DISABLED_PopupAndAutoHideShelf
#define MAYBE_PopupAndFullscreen DISABLED_PopupAndFullscreen
#endif

TEST_F(WebNotificationTrayTest, MAYBE_PopupAndSystemTray) {
  TestItem* test_item = new TestItem;
  GetSystemTray()->AddTrayItem(test_item);

  AddNotification("test_id");
  EXPECT_TRUE(GetTray()->IsPopupVisible());
  gfx::Rect work_area = GetPopupWorkArea();

  // System tray is created, the popup's work area should be narrowed but still
  // visible.
  GetSystemTray()->ShowDefaultView(BUBBLE_CREATE_NEW);
  EXPECT_TRUE(GetTray()->IsPopupVisible());
  gfx::Rect work_area_with_tray = GetPopupWorkArea();
  EXPECT_GT(work_area.size().GetArea(), work_area_with_tray.size().GetArea());

  // System tray notification is also created, the popup's work area is narrowed
  // even more, but still visible.
  GetSystemTray()->ShowNotificationView(test_item);
  EXPECT_TRUE(GetTray()->IsPopupVisible());
  gfx::Rect work_area_with_tray_notification = GetPopupWorkArea();
  EXPECT_GT(work_area.size().GetArea(),
            work_area_with_tray_notification.size().GetArea());
  EXPECT_GT(work_area_with_tray.size().GetArea(),
            work_area_with_tray_notification.size().GetArea());

  // Close system tray, only system tray notifications.
  GetSystemTray()->ClickedOutsideBubble();
  EXPECT_TRUE(GetTray()->IsPopupVisible());
  gfx::Rect work_area_with_notification = GetPopupWorkArea();
  EXPECT_GT(work_area.size().GetArea(),
            work_area_with_notification.size().GetArea());
  EXPECT_LT(work_area_with_tray_notification.size().GetArea(),
            work_area_with_notification.size().GetArea());

  // Close the system tray notifications.
  GetSystemTray()->HideNotificationView(test_item);
  EXPECT_TRUE(GetTray()->IsPopupVisible());
  EXPECT_EQ(work_area.ToString(), GetPopupWorkArea().ToString());
}

TEST_F(WebNotificationTrayTest, MAYBE_PopupAndAutoHideShelf) {
  AddNotification("test_id");
  EXPECT_TRUE(GetTray()->IsPopupVisible());
  gfx::Rect work_area = GetPopupWorkArea();

  // Shelf's auto-hide state won't be HIDDEN unless window exists.
  scoped_ptr<aura::Window> window(
      CreateTestWindowInShellWithBounds(gfx::Rect(1, 2, 3, 4)));
  ShelfLayoutManager* shelf =
      Shell::GetPrimaryRootWindowController()->GetShelfLayoutManager();
  shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);

  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
  gfx::Rect work_area_auto_hidden = GetPopupWorkArea();
  EXPECT_LT(work_area.size().GetArea(), work_area_auto_hidden.size().GetArea());

  // Close the window, which shows the shelf.
  window.reset();
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
  gfx::Rect work_area_auto_shown = GetPopupWorkArea();
  EXPECT_EQ(work_area.ToString(), work_area_auto_shown.ToString());

  // Create the system tray during auto-hide.
  window.reset(CreateTestWindowInShellWithBounds(gfx::Rect(1, 2, 3, 4)));
  TestItem* test_item = new TestItem;
  GetSystemTray()->AddTrayItem(test_item);
  GetSystemTray()->ShowDefaultView(BUBBLE_CREATE_NEW);

  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
  EXPECT_TRUE(GetTray()->IsPopupVisible());
  gfx::Rect work_area_with_tray = GetPopupWorkArea();
  EXPECT_GT(work_area_auto_shown.size().GetArea(),
            work_area_with_tray.size().GetArea());

  // Create tray notification.
  GetSystemTray()->ShowNotificationView(test_item);
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
  gfx::Rect work_area_with_tray_notification = GetPopupWorkArea();
  EXPECT_GT(work_area_with_tray.size().GetArea(),
            work_area_with_tray_notification.size().GetArea());

  // Close the system tray.
  GetSystemTray()->ClickedOutsideBubble();
  shelf->UpdateAutoHideState();
  RunAllPendingInMessageLoop();
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
  gfx::Rect work_area_hidden_with_tray_notification = GetPopupWorkArea();
  EXPECT_LT(work_area_with_tray_notification.size().GetArea(),
            work_area_hidden_with_tray_notification.size().GetArea());
  EXPECT_GT(work_area_auto_hidden.size().GetArea(),
            work_area_hidden_with_tray_notification.size().GetArea());

  // Close the window again, which shows the shelf.
  window.reset();
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
  gfx::Rect work_area_shown_with_tray_notification = GetPopupWorkArea();
  EXPECT_GT(work_area_hidden_with_tray_notification.size().GetArea(),
            work_area_shown_with_tray_notification.size().GetArea());
  EXPECT_GT(work_area_auto_shown.size().GetArea(),
            work_area_shown_with_tray_notification.size().GetArea());
}

TEST_F(WebNotificationTrayTest, MAYBE_PopupAndFullscreen) {
  AddNotification("test_id");
  EXPECT_TRUE(IsPopupVisible());
  gfx::Rect work_area = GetPopupWorkArea();

  // Checks the work area for normal auto-hidden state.
  scoped_ptr<aura::Window> window(
      CreateTestWindowInShellWithBounds(gfx::Rect(1, 2, 3, 4)));
  ShelfLayoutManager* shelf =
      Shell::GetPrimaryRootWindowController()->GetShelfLayoutManager();
  shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
  gfx::Rect work_area_auto_hidden = GetPopupWorkArea();
  shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);

  // Put |window| into fullscreen without forcing the shelf to hide. Currently,
  // this is used by immersive fullscreen and forces the shelf to be auto
  // hidden.
  wm::GetWindowState(window.get())->set_hide_shelf_when_fullscreen(false);
  window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
  RunAllPendingInMessageLoop();

  // The work area for auto-hidden status of fullscreen is a bit larger
  // since it doesn't even have the 3-pixel width.
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
  gfx::Rect work_area_fullscreen_hidden = GetPopupWorkArea();
  EXPECT_EQ(work_area_auto_hidden.ToString(),
            work_area_fullscreen_hidden.ToString());

  // Move the mouse cursor at the bottom, which shows the shelf.
  aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
  gfx::Point bottom_right =
      Shell::GetScreen()->GetPrimaryDisplay().bounds().bottom_right();
  bottom_right.Offset(-1, -1);
  generator.MoveMouseTo(bottom_right);
  shelf->UpdateAutoHideStateNow();
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
  EXPECT_EQ(work_area.ToString(), GetPopupWorkArea().ToString());

  generator.MoveMouseTo(work_area.CenterPoint());
  shelf->UpdateAutoHideStateNow();
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
  EXPECT_EQ(work_area_auto_hidden.ToString(), GetPopupWorkArea().ToString());
}

TEST_F(WebNotificationTrayTest, MAYBE_PopupAndSystemTrayMultiDisplay) {
  UpdateDisplay("800x600,600x400");

  AddNotification("test_id");
  gfx::Rect work_area = GetPopupWorkArea();
  gfx::Rect work_area_second = GetPopupWorkAreaForTray(GetSecondaryTray());

  // System tray is created on the primary display. The popups in the secondary
  // tray aren't affected.
  GetSystemTray()->ShowDefaultView(BUBBLE_CREATE_NEW);
  EXPECT_GT(work_area.size().GetArea(), GetPopupWorkArea().size().GetArea());
  EXPECT_EQ(work_area_second.ToString(),
            GetPopupWorkAreaForTray(GetSecondaryTray()).ToString());
}

}  // namespace ash
