// Copyright (c) 2010 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/chromeos/tab_closeable_state_watcher.h"

#include "base/command_line.h"
#include "chrome/browser/browser_shutdown.h"
#include "chrome/browser/defaults.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/tab_contents/tab_contents.h"
#include "chrome/browser/tab_contents/tab_contents_view.h"
#include "chrome/browser/tab_contents_wrapper.h"
#include "chrome/browser/tabs/tab_strip_model.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/notification_service.h"
#include "chrome/common/url_constants.h"

namespace chromeos {

////////////////////////////////////////////////////////////////////////////////
// TabCloseableStateWatcher::TabStripWatcher, public:

TabCloseableStateWatcher::TabStripWatcher::TabStripWatcher(
    TabCloseableStateWatcher* main_watcher, const Browser* browser)
    : main_watcher_(main_watcher),
      browser_(browser) {
  browser_->tabstrip_model()->AddObserver(this);
}

TabCloseableStateWatcher::TabStripWatcher::~TabStripWatcher() {
  browser_->tabstrip_model()->RemoveObserver(this);
}

////////////////////////////////////////////////////////////////////////////////
// TabCloseableStateWatcher::TabStripWatcher,
//     TabStripModelObserver implementation:

void TabCloseableStateWatcher::TabStripWatcher::TabInsertedAt(
    TabContentsWrapper* tab_contents, int index, bool foreground) {
  main_watcher_->OnTabStripChanged(browser_, false);
}

void TabCloseableStateWatcher::TabStripWatcher::TabClosingAt(
    TabStripModel* tab_strip_model,
    TabContentsWrapper* tab_contents,
    int index) {
  // Check if the last tab is closing.
  if (tab_strip_model->count() == 1)
    main_watcher_->OnTabStripChanged(browser_, true);
}

void TabCloseableStateWatcher::TabStripWatcher::TabDetachedAt(
    TabContentsWrapper* tab_contents, int index) {
  main_watcher_->OnTabStripChanged(browser_, false);
}

void TabCloseableStateWatcher::TabStripWatcher::TabChangedAt(
    TabContentsWrapper* tab_contents, int index, TabChangeType change_type) {
  main_watcher_->OnTabStripChanged(browser_, false);
}

////////////////////////////////////////////////////////////////////////////////
// TabCloseableStateWatcher, public:

TabCloseableStateWatcher::TabCloseableStateWatcher()
    : can_close_tab_(true),
      signing_off_(false),
      guest_session_(
          CommandLine::ForCurrentProcess()->HasSwitch(
              switches::kGuestSession)) {
  BrowserList::AddObserver(this);
  notification_registrar_.Add(this, NotificationType::APP_EXITING,
      NotificationService::AllSources());
}

TabCloseableStateWatcher::~TabCloseableStateWatcher() {
  BrowserList::RemoveObserver(this);
  if (!browser_shutdown::ShuttingDownWithoutClosingBrowsers())
    DCHECK(tabstrip_watchers_.empty());
}

bool TabCloseableStateWatcher::CanCloseTab(const Browser* browser) const {
  return browser->type() != Browser::TYPE_NORMAL ? true : can_close_tab_;
}

bool TabCloseableStateWatcher::CanCloseBrowser(Browser* browser) {
  BrowserActionType action_type;
  bool can_close = CanCloseBrowserImpl(browser, &action_type);
  if (action_type == OPEN_WINDOW) {
    browser->NewWindow();
  } else if (action_type == OPEN_NTP) {
    // NTP will be opened before closing last tab (via TabStripModelObserver::
    // TabClosingAt), close all tabs now.
    browser->CloseAllTabs();
  }
  return can_close;
}

void TabCloseableStateWatcher::OnWindowCloseCanceled(Browser* browser) {
  // This could be a call to cancel APP_EXITING if user doesn't proceed with
  // unloading handler.
  if (signing_off_) {
    signing_off_ = false;
    CheckAndUpdateState(browser);
  }
}

////////////////////////////////////////////////////////////////////////////////
// TabCloseableStateWatcher, BrowserList::Observer implementation:

void TabCloseableStateWatcher::OnBrowserAdded(const Browser* browser) {
  // Only normal browsers may affect closeable state.
  if (browser->type() != Browser::TYPE_NORMAL)
    return;

  // Create TabStripWatcher to observe tabstrip of new browser.
  tabstrip_watchers_.push_back(new TabStripWatcher(this, browser));

  // When a normal browser is just added, there's no tabs yet, so we wait till
  // TabInsertedAt notification to check for change in state.
}

void TabCloseableStateWatcher::OnBrowserRemoved(const Browser* browser) {
  // Only normal browsers may affect closeable state.
  if (browser->type() != Browser::TYPE_NORMAL)
    return;

  // Remove TabStripWatcher for browser that is being removed.
  for (std::vector<TabStripWatcher*>::iterator it = tabstrip_watchers_.begin();
       it != tabstrip_watchers_.end(); ++it) {
    if ((*it)->browser() == browser) {
      delete (*it);
      tabstrip_watchers_.erase(it);
      break;
    }
  }

  CheckAndUpdateState(NULL);
}

////////////////////////////////////////////////////////////////////////////////
// TabCloseableStateWatcher, NotificationObserver implementation:

void TabCloseableStateWatcher::Observe(NotificationType type,
    const NotificationSource& source, const NotificationDetails& details) {
  if (type.value != NotificationType::APP_EXITING)
    NOTREACHED();
  if (!signing_off_) {
    signing_off_ = true;
    SetCloseableState(true);
  }
}

////////////////////////////////////////////////////////////////////////////////
// TabCloseableStateWatcher, private

void TabCloseableStateWatcher::OnTabStripChanged(const Browser* browser,
    bool closing_last_tab) {
  if (!closing_last_tab) {
    CheckAndUpdateState(browser);
    return;
  }

  // Before closing last tab, open new window or NTP if necessary.
  BrowserActionType action_type;
  CanCloseBrowserImpl(browser, &action_type);
  if (action_type != NONE) {
    Browser* mutable_browser = const_cast<Browser*>(browser);
    if (action_type == OPEN_WINDOW)
      mutable_browser->NewWindow();
    else if (action_type == OPEN_NTP)
      mutable_browser->NewTab();
  }
}

void TabCloseableStateWatcher::CheckAndUpdateState(
    const Browser* browser_to_check) {
  // We shouldn't update state if we're signing off, or there's no normal
  // browser, or browser is always closeable.
  if (signing_off_ || tabstrip_watchers_.empty() ||
      (browser_to_check && browser_to_check->type() != Browser::TYPE_NORMAL))
    return;

  bool new_can_close;

  if (tabstrip_watchers_.size() > 1) {
    new_can_close = true;
  } else {  // There's only 1 normal browser.
    if (!browser_to_check)
      browser_to_check = tabstrip_watchers_[0]->browser();
    if (browser_to_check->profile()->IsOffTheRecord() && !guest_session_) {
      new_can_close = true;
    } else {
      TabStripModel* tabstrip_model = browser_to_check->tabstrip_model();
      if (tabstrip_model->count() == 1) {
        new_can_close =
            tabstrip_model->GetTabContentsAt(0)->tab_contents()->GetURL() !=
                GURL(chrome::kChromeUINewTabURL);  // Tab is not NewTabPage.
      } else {
        new_can_close = true;
      }
    }
  }

  SetCloseableState(new_can_close);
}

void TabCloseableStateWatcher::SetCloseableState(bool closeable) {
  if (can_close_tab_ == closeable)  // No change in state.
    return;

  can_close_tab_ = closeable;

  // Notify of change in tab closeable state.
  NotificationService::current()->Notify(
      NotificationType::TAB_CLOSEABLE_STATE_CHANGED,
      NotificationService::AllSources(),
      Details<bool>(&can_close_tab_));
}

bool TabCloseableStateWatcher::CanCloseBrowserImpl(const Browser* browser,
    BrowserActionType* action_type) const {
  *action_type = NONE;

  // Browser is always closeable when signing off.
  if (signing_off_)
    return true;

  // Non-normal browsers are always closeable.
  if (browser->type() != Browser::TYPE_NORMAL)
    return true;

  // If this is not the last normal browser, it's always closeable.
  if (tabstrip_watchers_.size() > 1)
    return true;

  // If last normal browser is incognito, open a non-incognito window,
  // and allow closing of incognito one (if not guest).
  if (browser->profile()->IsOffTheRecord() && !guest_session_) {
    *action_type = OPEN_WINDOW;
    return true;
  }

  // If tab is not closeable, browser is not closeable.
  if (!can_close_tab_)
    return false;

  // Otherwise, close existing tabs, and deny closing of browser.
  // TabClosingAt will open NTP when the last tab is being closed.
  *action_type = OPEN_NTP;
  return false;
}

}  // namespace chromeos