// Copyright (c) 2009 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 <algorithm>

#include "app/gfx/canvas.h"
#include "app/menus/simple_menu_model.h"
#include "app/theme_provider.h"
#include "base/command_line.h"
#include "chrome/app/chrome_dll_resource.h"
#include "chrome/browser/chromeos/compact_location_bar_host.h"
#include "chrome/browser/chromeos/compact_navigation_bar.h"
#include "chrome/browser/chromeos/main_menu.h"
#include "chrome/browser/chromeos/status_area_view.h"
#include "chrome/browser/chromeos/panel_controller.h"
#include "chrome/browser/views/frame/browser_extender.h"
#include "chrome/browser/views/frame/browser_frame_gtk.h"
#include "chrome/browser/views/frame/browser_view.h"
#include "chrome/browser/views/tabs/tab_overview_types.h"
#include "chrome/browser/views/tabs/tab_strip.h"
#include "chrome/browser/views/toolbar_view.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/x11_util.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#include "views/controls/button/button.h"
#include "views/controls/button/image_button.h"
#include "views/controls/menu/menu_2.h"
#include "views/window/window.h"

namespace {

const char* kChromeOsWindowManagerName = "chromeos-wm";
const int kCompactNavbarSpaceHeight = 3;

class Spacer : public views::View {
  SkBitmap* background_;
 public:
  explicit Spacer(SkBitmap* bitmap) : background_(bitmap) {}

  void Paint(gfx::Canvas* canvas) {
    canvas->TileImageInt(*background_, 0, 0, width(), height());
  }
};

// NormalExtender adds ChromeOS specific controls and menus to a BrowserView
// created with Browser::TYPE_NORMAL. This extender adds controls to
// the title bar as follows:
//                  ____  __ __
//      [MainMenu] /    \   \  \     [StatusArea]
//
// and adds the system context menu to the remaining arae of the titlebar.
//
// For Browser::TYPE_POPUP type of BrowserView, see PopupExtender class below.
class NormalExtender : public BrowserExtender,
                       public views::ButtonListener,
                       public views::ContextMenuController {
 public:
  explicit NormalExtender(BrowserView* browser_view)
      : BrowserExtender(browser_view),
        main_menu_(NULL),
        status_area_(NULL),
        compact_navigation_bar_(NULL),
        // CompactNavigationBar is disabled by default.
        // TODO(oshima): Get this info from preference.
        compact_navigation_bar_enabled_(false),
        force_maximized_window_(false) {
  }
  virtual ~NormalExtender() {}

 protected:
  // BrowserExtender overrides.
  virtual void Init() {
    main_menu_ = new views::ImageButton(this);
    ThemeProvider* theme_provider =
        browser_view()->frame()->GetThemeProviderForFrame();
    SkBitmap* image = theme_provider->GetBitmapNamed(IDR_MAIN_MENU_BUTTON);
    main_menu_->SetImage(views::CustomButton::BS_NORMAL, image);
    main_menu_->SetImage(views::CustomButton::BS_HOT, image);
    main_menu_->SetImage(views::CustomButton::BS_PUSHED, image);
    browser_view()->AddChildView(main_menu_);

    Browser* browser = browser_view()->browser();
    compact_location_bar_host_.reset(
        new chromeos::CompactLocationBarHost(browser_view()));
    compact_navigation_bar_ = new chromeos::CompactNavigationBar(browser);
    browser_view()->AddChildView(compact_navigation_bar_);
    compact_navigation_bar_->Init();
    status_area_ = new chromeos::StatusAreaView(
        browser,
        browser_view()->GetWindow()->GetNativeWindow());
    browser_view()->AddChildView(status_area_);
    status_area_->Init();

    SkBitmap* theme_toolbar = theme_provider->GetBitmapNamed(IDR_THEME_TOOLBAR);
    spacer_ = new Spacer(theme_toolbar);
    browser_view()->AddChildView(spacer_);

    InitSystemMenu();
    chromeos::MainMenu::ScheduleCreation();

    // The ContextMenuController has to be set to a NonClientView but
    // not to a NonClientFrameView because a TabStrip is not a child of
    // a NonClientFrameView even though visually a TabStrip is over a
    // NonClientFrameView.
    BrowserFrameGtk* gtk_frame =
        static_cast<BrowserFrameGtk*>(browser_view()->frame());
    gtk_frame->GetNonClientView()->SetContextMenuController(this);

    if (browser->type() == Browser::TYPE_NORMAL) {
      std::string wm_name;
      bool wm_name_valid = x11_util::GetWindowManagerName(&wm_name);
      // NOTE: On Chrome OS the wm and Chrome are started in parallel. This
      // means it's possible for us not to be able to get the name of the window
      // manager. We assume that when this happens we're on Chrome OS.
      force_maximized_window_ = (!wm_name_valid ||
                                 wm_name == kChromeOsWindowManagerName ||
                                 CommandLine::ForCurrentProcess()->HasSwitch(
                                     switches::kChromeosFrame));
    }
  }

  virtual void Layout(const gfx::Rect& bounds,
                      gfx::Rect* tabstrip_bounds,
                      int* bottom) {
    if (browser_view()->IsTabStripVisible()) {
      *bottom = bounds.bottom();
    } else {
      *bottom = 0;
    }
    // Skip if there is no space to layout, or if the browser is in
    // fullscreen mode.
    if (bounds.IsEmpty() || browser_view()->IsFullscreen()) {
      main_menu_->SetVisible(false);
      compact_navigation_bar_->SetVisible(false);
      status_area_->SetVisible(false);
      tabstrip_bounds->SetRect(bounds.x(), bounds.y(),
                               bounds.width(), bounds.height());
      return;
    } else {
      main_menu_->SetVisible(true);
      compact_navigation_bar_->SetVisible(compact_navigation_bar_enabled_);
      status_area_->SetVisible(true);
    }
    /* TODO(oshima):
     * Disabling the ability to update location bar on re-layout bacause
     * tabstrip state may not be in sync with the browser's state when
     * new tab is added. We should decide when we know more about this
     * feature. May be we should simply hide the location?
     * Filed a bug: http://crbug.com/30612.
    if (compact_navigation_bar_->IsVisible()) {
      // Update the size and location of the compact location bar.
      int index = browser_view()->browser()->selected_index();
      compact_location_bar_host_->Update(index, false);
    }
    */

    // Layout main menu before tab strip.
    gfx::Size main_menu_size = main_menu_->GetPreferredSize();

    // TODO(oshima): Use 0 for x position for now as this is
    // sufficient for chromeos where the window is always
    // maximized. The correct value is
    // OpaqueBrowserFrameView::NonClientBorderThickness() and we will
    // consider exposing it once we settle on the UI.
    main_menu_->SetBounds(0, bounds.y(),
                          main_menu_size.width(), bounds.height());

    // Layout status area after tab strip.
    status_area_->Update();
    gfx::Size status_size = status_area_->GetPreferredSize();
    status_area_->SetBounds(bounds.x() + bounds.width() - status_size.width(),
                            bounds.y(), status_size.width(),
                            status_size.height());
    int curx = bounds.x();
    int width = bounds.width() - status_size.width();

    if (compact_navigation_bar_->IsVisible()) {
      gfx::Size cnb_bounds = compact_navigation_bar_->GetPreferredSize();
      compact_navigation_bar_->SetBounds(curx, bounds.y(),
                                         cnb_bounds.width(),
                                         bounds.height());
      curx += cnb_bounds.width();
      width -= cnb_bounds.width();

      spacer_->SetVisible(true);
      spacer_->SetBounds(0, *bottom, browser_view()->width(),
                         kCompactNavbarSpaceHeight);
      *bottom += kCompactNavbarSpaceHeight;
    } else {
      spacer_->SetVisible(false);
    }
    width = std::max(0, width);  // In case there is no space left.
    tabstrip_bounds->SetRect(curx, bounds.y(), width, bounds.height());
  }

  virtual bool NonClientHitTest(const gfx::Point& point) {
    gfx::Point point_in_main_menu_coords(point);
    views::View::ConvertPointToView(browser_view(), main_menu_,
                                    &point_in_main_menu_coords);
    if (main_menu_->HitTest(point_in_main_menu_coords))
      return true;

    gfx::Point point_in_status_area_coords(point);
    views::View::ConvertPointToView(browser_view(), status_area_,
                                    &point_in_status_area_coords);
    if (status_area_->HitTest(point_in_status_area_coords))
      return true;

    if (compact_navigation_bar_->IsVisible()) {
      gfx::Point point_in_cnb_coords(point);
      views::View::ConvertPointToView(browser_view(),
                                      compact_navigation_bar_,
                                      &point_in_cnb_coords);
      return compact_navigation_bar_->HitTest(point_in_cnb_coords);
    }
    return false;
  }

  virtual void UpdateTitleBar() {}

  virtual void Show() {
    TabOverviewTypes::instance()->SetWindowType(
        GTK_WIDGET(GetBrowserWindow()->GetNativeWindow()),
        TabOverviewTypes::WINDOW_TYPE_CHROME_TOPLEVEL,
        NULL);
  }

  virtual void Close() {}

  virtual void ActivationChanged() {}

  virtual bool ShouldForceHideToolbar() {
    return compact_navigation_bar_enabled_;
  }

  virtual bool SetFocusToCompactNavigationBar() {
    if (compact_navigation_bar_->IsFocusable()) {
      compact_navigation_bar_->FocusLocation();
      return true;
    } else {
      return false;
    }
  }

  virtual void ToggleCompactNavigationBar() {
    compact_navigation_bar_enabled_ = !compact_navigation_bar_enabled_;
    compact_navigation_bar_->SetFocusable(compact_navigation_bar_enabled_);
    compact_location_bar_host_->SetEnabled(compact_navigation_bar_enabled_);
  }

  virtual void OnMouseEnteredToTab(Tab* tab) {
    ShowCompactLocationBarUnderSelectedTab();
  }

  virtual void OnMouseMovedOnTab(Tab* tab) {
    // TODO(oshima): remove this method from extender once we settled
    // on the compact location bar behavior.
  }

  virtual void OnMouseExitedFromTab(Tab* tab) {
    // TODO(oshima): remove this method from extender once we settled
    // on the compact location bar behavior.
  }

  virtual bool ShouldForceMaximizedWindow() {
    return force_maximized_window_;
  }

  virtual int GetMainMenuWidth() const {
    return main_menu_->GetPreferredSize().width();
  }

 private:
  // Shows the compact location bar under the selected tab.
  void ShowCompactLocationBarUnderSelectedTab() {
    if (!compact_navigation_bar_enabled_)
      return;
    int index = browser_view()->browser()->selected_index();
    compact_location_bar_host_->Update(index, true);
  }

  // Creates system menu.
  void InitSystemMenu() {
    system_menu_contents_.reset(new menus::SimpleMenuModel(browser_view()));
    system_menu_contents_->AddItemWithStringId(IDC_RESTORE_TAB,
                                               IDS_RESTORE_TAB);
    system_menu_contents_->AddItemWithStringId(IDC_NEW_TAB, IDS_NEW_TAB);
    system_menu_contents_->AddSeparator();
    system_menu_contents_->AddItemWithStringId(IDC_TASK_MANAGER,
                                               IDS_TASK_MANAGER);
    system_menu_menu_.reset(new views::Menu2(system_menu_contents_.get()));
  }

  // views::ButtonListener overrides.
  virtual void ButtonPressed(views::Button* sender, const views::Event& event) {
    chromeos::MainMenu::Show(browser_view()->browser());
  }

  // views::ContextMenuController overrides.
  virtual void ShowContextMenu(views::View* source,
                               int x,
                               int y,
                               bool is_mouse_gesture) {
    system_menu_menu_->RunMenuAt(gfx::Point(x, y), views::Menu2::ALIGN_TOPLEFT);
  }

  // Main menu button.
  views::ImageButton* main_menu_;

  // Status Area view.
  chromeos::StatusAreaView* status_area_;

  // System menus.
  scoped_ptr<menus::SimpleMenuModel> system_menu_contents_;
  scoped_ptr<views::Menu2> system_menu_menu_;

  // CompactNavigationBar view.
  chromeos::CompactNavigationBar* compact_navigation_bar_;

  // A toggle flag to show/hide the compact navigation bar.
  bool compact_navigation_bar_enabled_;

  // CompactLocationBarHost.
  scoped_ptr<chromeos::CompactLocationBarHost> compact_location_bar_host_;

  // A flag to specify if the browser window should be maximized.
  bool force_maximized_window_;

  // A spacer under the tap strip used when the compact navigation bar
  // is active.
  Spacer* spacer_;

  DISALLOW_COPY_AND_ASSIGN(NormalExtender);
};

// PopupExtender class creates dedicated title window for popup window.
// The size and location of the created title window is controlled by
// by window manager.
class PopupExtender : public BrowserExtender {
 public:
  explicit PopupExtender(BrowserView* browser_view)
      : BrowserExtender(browser_view) {
  }
  virtual ~PopupExtender() {}

 private:
  // BrowserExtender overrides.
  virtual void Init() {
    // The visibility of toolbar is controlled in
    // the BrowserView::IsToolbarVisible method.

    views::Window* window = GetBrowserWindow();
    gfx::NativeWindow native_window = window->GetNativeWindow();
    // The window manager needs the min size for popups.
    gfx::Rect bounds = window->GetBounds();
    gtk_widget_set_size_request(
        GTK_WIDGET(native_window), bounds.width(), bounds.height());
    // If we don't explicitly resize here there is a race condition between
    // the X Server and the window manager. Windows will appear with a default
    // size of 200x200 if this happens.
    gtk_window_resize(native_window, bounds.width(), bounds.height());
  }

  virtual void Layout(const gfx::Rect& bounds,
                      gfx::Rect* tabstrip_bounds,
                      int* bottom) {
    *bottom = 0;
    tabstrip_bounds->SetRect(0, 0, 0, 0);
  }

  virtual bool NonClientHitTest(const gfx::Point& point) {
    return false;
  }

  virtual void Show() {
    panel_controller_.reset(new chromeos::PanelController(browser_view()));
  }

  virtual void Close() {
    if (panel_controller_.get())
      panel_controller_->Close();
  }

  virtual void UpdateTitleBar() {
    if (panel_controller_.get())
      panel_controller_->UpdateTitleBar();
  }

  virtual void ActivationChanged() {
    if (panel_controller_.get()) {
      if (GetBrowserWindow()->IsActive())
        panel_controller_->OnFocusIn();
      else
        panel_controller_->OnFocusOut();
    }
  }

  virtual bool ShouldForceHideToolbar() {
    // Always hide toolbar for popups.
    return true;
  }

  virtual bool SetFocusToCompactNavigationBar() {
    return false;
  }

  virtual void ToggleCompactNavigationBar() {}

  virtual void OnMouseEnteredToTab(Tab* tab) {}

  virtual void OnMouseMovedOnTab(Tab* tab) {}

  virtual void OnMouseExitedFromTab(Tab* tab) {}

  virtual bool ShouldForceMaximizedWindow() {
    return false;
  }

  virtual int GetMainMenuWidth() const {
    return 0;
  }

  // Controls interactions with the window manager for popup panels.
  scoped_ptr<chromeos::PanelController> panel_controller_;

  DISALLOW_COPY_AND_ASSIGN(PopupExtender);
};

}  // namespace

////////////////////////////////////////////////////////////////////////////////
// BrowserExtender, public:

// static
BrowserExtender* BrowserExtender::Create(BrowserView* browser_view) {
  if (browser_view->browser()->type() & Browser::TYPE_POPUP)
    return new PopupExtender(browser_view);
  else
    return new NormalExtender(browser_view);
}