// 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/bluetooth/tray_bluetooth.h"

#include "ash/session/session_state_delegate.h"
#include "ash/shell.h"
#include "ash/system/tray/fixed_sized_scroll_view.h"
#include "ash/system/tray/hover_highlight_view.h"
#include "ash/system/tray/system_tray.h"
#include "ash/system/tray/system_tray_delegate.h"
#include "ash/system/tray/system_tray_notifier.h"
#include "ash/system/tray/throbber_view.h"
#include "ash/system/tray/tray_constants.h"
#include "ash/system/tray/tray_details_view.h"
#include "ash/system/tray/tray_item_more.h"
#include "ash/system/tray/tray_popup_header_button.h"
#include "grit/ash_resources.h"
#include "grit/ash_strings.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/image/image.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/box_layout.h"

namespace ash {
namespace tray {
namespace {

// Updates bluetooth device |device| in the |list|. If it is new, append to the
// end of the |list|; otherwise, keep it at the same place, but update the data
// with new device info provided by |device|.
void UpdateBluetoothDeviceListHelper(BluetoothDeviceList* list,
                                     const BluetoothDeviceInfo& device) {
  for (BluetoothDeviceList::iterator it = list->begin(); it != list->end();
       ++it) {
    if ((*it).address == device.address) {
      *it = device;
      return;
    }
  }

  list->push_back(device);
}

// Removes the obsolete BluetoothDevices from |list|, if they are not in the
// |new_list|.
void RemoveObsoleteBluetoothDevicesFromList(
    BluetoothDeviceList* list,
    const std::set<std::string>& new_list) {
  for (BluetoothDeviceList::iterator it = list->begin(); it != list->end();
       ++it) {
    if (new_list.find((*it).address) == new_list.end()) {
      it = list->erase(it);
      if (it == list->end())
        return;
    }
  }
}

}  // namespace

class BluetoothDefaultView : public TrayItemMore {
 public:
  BluetoothDefaultView(SystemTrayItem* owner, bool show_more)
      : TrayItemMore(owner, show_more) {
    ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
    SetImage(bundle.GetImageNamed(IDR_AURA_UBER_TRAY_BLUETOOTH).ToImageSkia());
    UpdateLabel();
  }

  virtual ~BluetoothDefaultView() {}

  void UpdateLabel() {
    ash::SystemTrayDelegate* delegate =
        ash::Shell::GetInstance()->system_tray_delegate();
    if (delegate->GetBluetoothAvailable()) {
      ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
      const base::string16 label =
          rb.GetLocalizedString(delegate->GetBluetoothEnabled() ?
              IDS_ASH_STATUS_TRAY_BLUETOOTH_ENABLED :
              IDS_ASH_STATUS_TRAY_BLUETOOTH_DISABLED);
      SetLabel(label);
      SetAccessibleName(label);
      SetVisible(true);
    } else {
      SetVisible(false);
    }
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(BluetoothDefaultView);
};

class BluetoothDetailedView : public TrayDetailsView,
                              public ViewClickListener,
                              public views::ButtonListener {
 public:
  BluetoothDetailedView(SystemTrayItem* owner, user::LoginStatus login)
      : TrayDetailsView(owner),
        login_(login),
        manage_devices_(NULL),
        toggle_bluetooth_(NULL),
        enable_bluetooth_(NULL) {
    CreateItems();
  }

  virtual ~BluetoothDetailedView() {
    // Stop discovering bluetooth devices when exiting BT detailed view.
    BluetoothStopDiscovering();
  }

  void Update() {
    BluetoothStartDiscovering();
    UpdateBluetoothDeviceList();

    // Update UI.
    UpdateDeviceScrollList();
    UpdateHeaderEntry();
    Layout();
  }

 private:
  void CreateItems() {
    CreateScrollableList();
    AppendSettingsEntries();
    AppendHeaderEntry();
  }

  void BluetoothStartDiscovering() {
    ash::SystemTrayDelegate* delegate =
        ash::Shell::GetInstance()->system_tray_delegate();
    bool bluetooth_enabled = delegate->GetBluetoothEnabled();
    bool bluetooth_discovering = delegate->GetBluetoothDiscovering();
    if (bluetooth_discovering) {
      throbber_->Start();
      return;
    }
    throbber_->Stop();
    if (bluetooth_enabled) {
      delegate->BluetoothStartDiscovering();
    }
  }

  void BluetoothStopDiscovering() {
    ash::SystemTrayDelegate* delegate =
        ash::Shell::GetInstance()->system_tray_delegate();
    if (delegate && delegate->GetBluetoothDiscovering()) {
      delegate->BluetoothStopDiscovering();
      throbber_->Stop();
    }
  }

  void UpdateBluetoothDeviceList() {
    std::set<std::string> new_connecting_devices;
    std::set<std::string> new_connected_devices;
    std::set<std::string> new_paired_not_connected_devices;
    std::set<std::string> new_discovered_not_paired_devices;

    BluetoothDeviceList list;
    Shell::GetInstance()->system_tray_delegate()->
        GetAvailableBluetoothDevices(&list);
    for (size_t i = 0; i < list.size(); ++i) {
      if (list[i].connecting) {
        list[i].display_name = l10n_util::GetStringFUTF16(
            IDS_ASH_STATUS_TRAY_BLUETOOTH_CONNECTING, list[i].display_name);
        new_connecting_devices.insert(list[i].address);
        UpdateBluetoothDeviceListHelper(&connecting_devices_, list[i]);
      } else if (list[i].connected && list[i].paired) {
        new_connected_devices.insert(list[i].address);
        UpdateBluetoothDeviceListHelper(&connected_devices_, list[i]);
      } else if (list[i].paired) {
        new_paired_not_connected_devices.insert(list[i].address);
        UpdateBluetoothDeviceListHelper(
            &paired_not_connected_devices_, list[i]);
      } else {
        new_discovered_not_paired_devices.insert(list[i].address);
        UpdateBluetoothDeviceListHelper(
            &discovered_not_paired_devices_, list[i]);
      }
    }
    RemoveObsoleteBluetoothDevicesFromList(&connecting_devices_,
                                           new_connecting_devices);
    RemoveObsoleteBluetoothDevicesFromList(&connected_devices_,
                                           new_connected_devices);
    RemoveObsoleteBluetoothDevicesFromList(&paired_not_connected_devices_,
                                           new_paired_not_connected_devices);
    RemoveObsoleteBluetoothDevicesFromList(&discovered_not_paired_devices_,
                                           new_discovered_not_paired_devices);
  }

  void AppendHeaderEntry() {
    CreateSpecialRow(IDS_ASH_STATUS_TRAY_BLUETOOTH, this);

    if (login_ == user::LOGGED_IN_LOCKED)
      return;

    throbber_ = new ThrobberView;
    throbber_->SetTooltipText(
        l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_BLUETOOTH_DISCOVERING));
    footer()->AddThrobber(throbber_);

    // Do not allow toggling bluetooth in the lock screen.
    ash::SystemTrayDelegate* delegate =
        ash::Shell::GetInstance()->system_tray_delegate();
    toggle_bluetooth_ = new TrayPopupHeaderButton(this,
        IDR_AURA_UBER_TRAY_BLUETOOTH_ENABLED,
        IDR_AURA_UBER_TRAY_BLUETOOTH_DISABLED,
        IDR_AURA_UBER_TRAY_BLUETOOTH_ENABLED_HOVER,
        IDR_AURA_UBER_TRAY_BLUETOOTH_DISABLED_HOVER,
        IDS_ASH_STATUS_TRAY_BLUETOOTH);
    toggle_bluetooth_->SetToggled(!delegate->GetBluetoothEnabled());
    toggle_bluetooth_->SetTooltipText(
        l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISABLE_BLUETOOTH));
    toggle_bluetooth_->SetToggledTooltipText(
        l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ENABLE_BLUETOOTH));
    footer()->AddButton(toggle_bluetooth_);
 }

  void UpdateHeaderEntry() {
    if (toggle_bluetooth_) {
      toggle_bluetooth_->SetToggled(
          !ash::Shell::GetInstance()->system_tray_delegate()->
              GetBluetoothEnabled());
    }
  }

  void UpdateDeviceScrollList() {
    device_map_.clear();
    scroll_content()->RemoveAllChildViews(true);
    enable_bluetooth_ = NULL;

    ash::SystemTrayDelegate* delegate =
        ash::Shell::GetInstance()->system_tray_delegate();
    bool bluetooth_enabled = delegate->GetBluetoothEnabled();
    bool blueooth_available = delegate->GetBluetoothAvailable();
    if (blueooth_available && !bluetooth_enabled &&
        toggle_bluetooth_) {
      enable_bluetooth_ =
          AddScrollListItem(
              l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ENABLE_BLUETOOTH),
              gfx::Font::NORMAL, false, true);
    }

    AppendSameTypeDevicesToScrollList(
        connected_devices_, true, true, bluetooth_enabled);
    AppendSameTypeDevicesToScrollList(
        connecting_devices_, true, false, bluetooth_enabled);
    AppendSameTypeDevicesToScrollList(
        paired_not_connected_devices_, false, false, bluetooth_enabled);
    if (discovered_not_paired_devices_.size() > 0)
      AddScrollSeparator();
    AppendSameTypeDevicesToScrollList(
        discovered_not_paired_devices_, false, false, bluetooth_enabled);

    // Show user Bluetooth state if there is no bluetooth devices in list.
    if (device_map_.size() == 0) {
      if (blueooth_available && bluetooth_enabled) {
        AddScrollListItem(
            l10n_util::GetStringUTF16(
                IDS_ASH_STATUS_TRAY_BLUETOOTH_DISCOVERING),
            gfx::Font::NORMAL, false, true);
      }
    }

    scroll_content()->SizeToPreferredSize();
    static_cast<views::View*>(scroller())->Layout();
  }

  void AppendSameTypeDevicesToScrollList(const BluetoothDeviceList& list,
                                         bool bold,
                                         bool checked,
                                         bool enabled) {
    for (size_t i = 0; i < list.size(); ++i) {
      HoverHighlightView* container = AddScrollListItem(
          list[i].display_name,
          bold? gfx::Font::BOLD : gfx::Font::NORMAL,
          checked, enabled);
      device_map_[container] = list[i].address;
    }
  }

  HoverHighlightView* AddScrollListItem(const base::string16& text,
                                        gfx::Font::FontStyle style,
                                        bool checked,
                                        bool enabled) {
    HoverHighlightView* container = new HoverHighlightView(this);
    views::Label* label = container->AddCheckableLabel(text, style, checked);
    label->SetEnabled(enabled);
    scroll_content()->AddChildView(container);
    return container;
  }

  // Add settings entries.
  void AppendSettingsEntries() {
    if (!ash::Shell::GetInstance()->
            system_tray_delegate()->ShouldShowSettings()) {
      return;
    }

    // Add bluetooth device requires a browser window, hide it for non logged in
    // user.
    bool userAddingRunning = ash::Shell::GetInstance()
                                 ->session_state_delegate()
                                 ->IsInSecondaryLoginScreen();

    if (login_ == user::LOGGED_IN_NONE || login_ == user::LOGGED_IN_LOCKED ||
        userAddingRunning)
      return;

    ash::SystemTrayDelegate* delegate =
        ash::Shell::GetInstance()->system_tray_delegate();
    ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    HoverHighlightView* container = new HoverHighlightView(this);
    container->AddLabel(
        rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_BLUETOOTH_MANAGE_DEVICES),
        gfx::ALIGN_LEFT,
        gfx::Font::NORMAL);
    container->SetEnabled(delegate->GetBluetoothAvailable());
    AddChildView(container);
    manage_devices_ = container;
  }

  // Returns true if the device with |device_id| is found in |device_list|,
  // and the display_name of the device will be returned in |display_name| if
  // it's not NULL.
  bool FoundDevice(const std::string& device_id,
                   const BluetoothDeviceList& device_list,
                   base::string16* display_name) {
    for (size_t i = 0; i < device_list.size(); ++i) {
      if (device_list[i].address == device_id) {
        if (display_name)
          *display_name = device_list[i].display_name;
        return true;
      }
    }
    return false;
  }

  // Updates UI of the clicked bluetooth device to show it is being connected
  // or disconnected if such an operation is going to be performed underway.
  void UpdateClickedDevice(std::string device_id, views::View* item_container) {
    base::string16 display_name;
    if (FoundDevice(device_id, paired_not_connected_devices_,
                           &display_name)) {
      display_name = l10n_util::GetStringFUTF16(
          IDS_ASH_STATUS_TRAY_BLUETOOTH_CONNECTING, display_name);

      item_container->RemoveAllChildViews(true);
      static_cast<HoverHighlightView*>(item_container)->
          AddCheckableLabel(display_name, gfx::Font::BOLD, false);
      scroll_content()->SizeToPreferredSize();
      static_cast<views::View*>(scroller())->Layout();
    }
  }

  // Overridden from ViewClickListener.
  virtual void OnViewClicked(views::View* sender) OVERRIDE {
    ash::SystemTrayDelegate* delegate =
        ash::Shell::GetInstance()->system_tray_delegate();
    if (sender == footer()->content()) {
      TransitionToDefaultView();
    } else if (sender == manage_devices_) {
      delegate->ManageBluetoothDevices();
    } else if (sender == enable_bluetooth_) {
      Shell::GetInstance()->metrics()->RecordUserMetricsAction(
          delegate->GetBluetoothEnabled() ?
          ash::UMA_STATUS_AREA_BLUETOOTH_DISABLED :
          ash::UMA_STATUS_AREA_BLUETOOTH_ENABLED);
      delegate->ToggleBluetooth();
    } else {
      if (!delegate->GetBluetoothEnabled())
        return;
      std::map<views::View*, std::string>::iterator find;
      find = device_map_.find(sender);
      if (find == device_map_.end())
        return;
      std::string device_id = find->second;
      if (FoundDevice(device_id, connecting_devices_, NULL))
        return;
      UpdateClickedDevice(device_id, sender);
      delegate->ConnectToBluetoothDevice(device_id);
    }
  }

  // Overridden from ButtonListener.
  virtual void ButtonPressed(views::Button* sender,
                             const ui::Event& event) OVERRIDE {
    ash::SystemTrayDelegate* delegate =
        ash::Shell::GetInstance()->system_tray_delegate();
    if (sender == toggle_bluetooth_)
      delegate->ToggleBluetooth();
    else
      NOTREACHED();
  }

  user::LoginStatus login_;

  std::map<views::View*, std::string> device_map_;
  views::View* manage_devices_;
  ThrobberView* throbber_;
  TrayPopupHeaderButton* toggle_bluetooth_;
  HoverHighlightView* enable_bluetooth_;
  BluetoothDeviceList connected_devices_;
  BluetoothDeviceList connecting_devices_;
  BluetoothDeviceList paired_not_connected_devices_;
  BluetoothDeviceList discovered_not_paired_devices_;

  DISALLOW_COPY_AND_ASSIGN(BluetoothDetailedView);
};

}  // namespace tray

TrayBluetooth::TrayBluetooth(SystemTray* system_tray)
    : SystemTrayItem(system_tray),
      default_(NULL),
      detailed_(NULL) {
  Shell::GetInstance()->system_tray_notifier()->AddBluetoothObserver(this);
}

TrayBluetooth::~TrayBluetooth() {
  Shell::GetInstance()->system_tray_notifier()->RemoveBluetoothObserver(this);
}

views::View* TrayBluetooth::CreateTrayView(user::LoginStatus status) {
  return NULL;
}

views::View* TrayBluetooth::CreateDefaultView(user::LoginStatus status) {
  CHECK(default_ == NULL);
  default_ = new tray::BluetoothDefaultView(
      this, status != user::LOGGED_IN_LOCKED);
  return default_;
}

views::View* TrayBluetooth::CreateDetailedView(user::LoginStatus status) {
  if (!Shell::GetInstance()->system_tray_delegate()->GetBluetoothAvailable())
    return NULL;
  Shell::GetInstance()->metrics()->RecordUserMetricsAction(
      ash::UMA_STATUS_AREA_DETAILED_BLUETOOTH_VIEW);
  CHECK(detailed_ == NULL);
  detailed_ = new tray::BluetoothDetailedView(this, status);
  detailed_->Update();
  return detailed_;
}

void TrayBluetooth::DestroyTrayView() {
}

void TrayBluetooth::DestroyDefaultView() {
  default_ = NULL;
}

void TrayBluetooth::DestroyDetailedView() {
  detailed_ = NULL;
}

void TrayBluetooth::UpdateAfterLoginStatusChange(user::LoginStatus status) {
}

void TrayBluetooth::OnBluetoothRefresh() {
  if (default_)
    default_->UpdateLabel();
  else if (detailed_)
    detailed_->Update();
}

void TrayBluetooth::OnBluetoothDiscoveringChanged() {
  if (!detailed_)
    return;
  detailed_->Update();
}

}  // namespace ash
