// Copyright (c) 2011 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/status/network_menu_button.h"

#include <algorithm>
#include <limits>

#include "base/logging.h"
#include "base/message_loop.h"
#include "base/string_util.h"
#include "base/stringprintf.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/cros/cros_library.h"
#include "chrome/browser/chromeos/login/helper.h"
#include "chrome/browser/chromeos/login/user_manager.h"
#include "chrome/browser/chromeos/options/network_config_view.h"
#include "chrome/browser/chromeos/sim_dialog_delegate.h"
#include "chrome/browser/chromeos/status/status_area_host.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/common/pref_names.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/canvas_skia.h"
#include "views/window/window.h"

namespace {

// Time in milliseconds to delay showing of promo
// notification when Chrome window is not on screen.
const int kPromoShowDelayMs = 10000;

const int kNotificationCountPrefDefault = -1;

bool GetBooleanPref(const char* pref_name) {
  Browser* browser = BrowserList::GetLastActive();
  // Default to safe value which is false (not to show bubble).
  if (!browser || !browser->profile())
    return false;

  PrefService* prefs = browser->profile()->GetPrefs();
  return prefs->GetBoolean(pref_name);
}

int GetIntegerPref(const char* pref_name) {
  Browser* browser = BrowserList::GetLastActive();
  // Default to "safe" value.
  if (!browser || !browser->profile())
    return kNotificationCountPrefDefault;

  PrefService* prefs = browser->profile()->GetPrefs();
  return prefs->GetInteger(pref_name);
}

void SetBooleanPref(const char* pref_name, bool value) {
  Browser* browser = BrowserList::GetLastActive();
  if (!browser || !browser->profile())
    return;

  PrefService* prefs = browser->profile()->GetPrefs();
  prefs->SetBoolean(pref_name, value);
}

void SetIntegerPref(const char* pref_name, int value) {
  Browser* browser = BrowserList::GetLastActive();
  if (!browser || !browser->profile())
    return;

  PrefService* prefs = browser->profile()->GetPrefs();
  prefs->SetInteger(pref_name, value);
}

// Returns prefs::kShow3gPromoNotification or false
// if there's no active browser.
bool ShouldShow3gPromoNotification() {
  return GetBooleanPref(prefs::kShow3gPromoNotification);
}

void SetShow3gPromoNotification(bool value) {
  SetBooleanPref(prefs::kShow3gPromoNotification, value);
}
// Returns prefs::kCarrierDealPromoShown which is number of times
// carrier deal notification has been shown to user or -1
// if there's no active browser.
int GetCarrierDealPromoShown() {
  return GetIntegerPref(prefs::kCarrierDealPromoShown);
}

void SetCarrierDealPromoShown(int value) {
  SetIntegerPref(prefs::kCarrierDealPromoShown, value);
}

}  // namespace

namespace chromeos {

////////////////////////////////////////////////////////////////////////////////
// NetworkMenuButton

// static
const int NetworkMenuButton::kThrobDuration = 750;

NetworkMenuButton::NetworkMenuButton(StatusAreaHost* host)
    : StatusAreaButton(host, this),
      NetworkMenu(),
      icon_(NULL),
      right_badge_(NULL),
      left_badge_(NULL),
      mobile_data_bubble_(NULL),
      check_for_promo_(true),
      was_sim_locked_(false),
      ALLOW_THIS_IN_INITIALIZER_LIST(animation_connecting_(this)),
      ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) {
  animation_connecting_.SetThrobDuration(kThrobDuration);
  animation_connecting_.SetTweenType(ui::Tween::LINEAR);
  NetworkLibrary* network_library = CrosLibrary::Get()->GetNetworkLibrary();
  OnNetworkManagerChanged(network_library);
  network_library->AddNetworkManagerObserver(this);
  network_library->AddCellularDataPlanObserver(this);
  const NetworkDevice* cellular = network_library->FindCellularDevice();
  if (cellular) {
    cellular_device_path_ = cellular->device_path();
    was_sim_locked_ = cellular->is_sim_locked();
    network_library->AddNetworkDeviceObserver(cellular_device_path_, this);
  }
}

NetworkMenuButton::~NetworkMenuButton() {
  NetworkLibrary* netlib = CrosLibrary::Get()->GetNetworkLibrary();
  netlib->RemoveNetworkManagerObserver(this);
  netlib->RemoveObserverForAllNetworks(this);
  netlib->RemoveCellularDataPlanObserver(this);
  if (!cellular_device_path_.empty())
    netlib->RemoveNetworkDeviceObserver(cellular_device_path_, this);
  if (mobile_data_bubble_)
    mobile_data_bubble_->Close();
}

////////////////////////////////////////////////////////////////////////////////
// NetworkMenuButton, ui::AnimationDelegate implementation:

void NetworkMenuButton::AnimationProgressed(const ui::Animation* animation) {
  if (animation == &animation_connecting_) {
    SetIconOnly(IconForNetworkConnecting(
        animation_connecting_.GetCurrentValue(), false));
    // No need to set the badge here, because it should already be set.
    SchedulePaint();
  } else {
    MenuButton::AnimationProgressed(animation);
  }
}

////////////////////////////////////////////////////////////////////////////////
// NetworkLibrary::NetworkDeviceObserver implementation:

void NetworkMenuButton::OnNetworkDeviceChanged(NetworkLibrary* cros,
                                               const NetworkDevice* device) {
  // Device status, such as SIMLock may have changed.
  OnNetworkChanged(cros, cros->active_network());
  const NetworkDevice* cellular = cros->FindCellularDevice();
  if (cellular) {
    // We make an assumption (which is valid for now) that the SIM
    // unlock dialog is put up only when the user is trying to enable
    // mobile data. So if the SIM is now unlocked, initiate the
    // enable operation that the user originally requested.
    if (was_sim_locked_ && !cellular->is_sim_locked() &&
        !cros->cellular_enabled()) {
      cros->EnableCellularNetworkDevice(true);
    }
    was_sim_locked_ = cellular->is_sim_locked();
  }
}

////////////////////////////////////////////////////////////////////////////////
// NetworkMenuButton, NetworkLibrary::NetworkManagerObserver implementation:

void NetworkMenuButton::OnNetworkManagerChanged(NetworkLibrary* cros) {
  OnNetworkChanged(cros, cros->active_network());
  ShowOptionalMobileDataPromoNotification(cros);
}

////////////////////////////////////////////////////////////////////////////////
// NetworkMenuButton, NetworkLibrary::NetworkObserver implementation:
void NetworkMenuButton::OnNetworkChanged(NetworkLibrary* cros,
                                         const Network* network) {
  // This gets called on initialization, so any changes should be reflected
  // in CrosMock::SetNetworkLibraryStatusAreaExpectations().
  SetNetworkIcon(cros, network);
  RefreshNetworkObserver(cros);
  RefreshNetworkDeviceObserver(cros);
  SchedulePaint();
  UpdateMenu();
}

void NetworkMenuButton::OnCellularDataPlanChanged(NetworkLibrary* cros) {
  // Call OnNetworkManagerChanged which will update the icon.
  OnNetworkManagerChanged(cros);
}

////////////////////////////////////////////////////////////////////////////////
// NetworkMenuButton, NetworkMenu implementation:

bool NetworkMenuButton::IsBrowserMode() const {
  return host_->GetScreenMode() == StatusAreaHost::kBrowserMode;
}

gfx::NativeWindow NetworkMenuButton::GetNativeWindow() const {
  return host_->GetNativeWindow();
}

void NetworkMenuButton::OpenButtonOptions() {
  host_->OpenButtonOptions(this);
}

bool NetworkMenuButton::ShouldOpenButtonOptions() const {
  return host_->ShouldOpenButtonOptions(this);
}

////////////////////////////////////////////////////////////////////////////////
// NetworkMenuButton, views::View implementation:

void NetworkMenuButton::OnLocaleChanged() {
  NetworkLibrary* lib = CrosLibrary::Get()->GetNetworkLibrary();
  SetNetworkIcon(lib, lib->active_network());
}

////////////////////////////////////////////////////////////////////////////////
// MessageBubbleDelegate implementation:

void NetworkMenuButton::OnHelpLinkActivated() {
  // mobile_data_bubble_ will be set to NULL in callback.
  if (mobile_data_bubble_)
    mobile_data_bubble_->Close();
  if (!deal_url_.empty()) {
    Browser* browser = BrowserList::GetLastActive();
    if (!browser)
      return;
    browser->ShowSingletonTab(GURL(deal_url_));
    deal_url_.clear();
  } else {
    const Network* cellular =
        CrosLibrary::Get()->GetNetworkLibrary()->cellular_network();
    if (!cellular)
      return;
    ShowTabbedNetworkSettings(cellular);
  }
}

////////////////////////////////////////////////////////////////////////////////
// NetworkMenuButton, private methods

const ServicesCustomizationDocument::CarrierDeal*
NetworkMenuButton::GetCarrierDeal(
    NetworkLibrary* cros) {
  std::string carrier_id = cros->GetCellularHomeCarrierId();
  if (carrier_id.empty()) {
    LOG(ERROR) << "Empty carrier ID with a cellular connected.";
    return NULL;
  }

  ServicesCustomizationDocument* customization =
      ServicesCustomizationDocument::GetInstance();
  if (!customization->IsReady())
    return NULL;

  const ServicesCustomizationDocument::CarrierDeal* deal =
      customization->GetCarrierDeal(carrier_id, true);
  if (deal) {
    // Check deal for validity.
    int carrier_deal_promo_pref = GetCarrierDealPromoShown();
    if (carrier_deal_promo_pref >= deal->notification_count)
      return NULL;
    const std::string locale = g_browser_process->GetApplicationLocale();
    std::string deal_text = deal->GetLocalizedString(locale,
                                                     "notification_text");
    if (deal_text.empty())
      return NULL;
  }
  return deal;
}

void NetworkMenuButton::SetIconAndBadges(const SkBitmap* icon,
                                         const SkBitmap* right_badge,
                                         const SkBitmap* left_badge) {
  icon_ = icon;
  right_badge_ = right_badge;
  left_badge_ = left_badge;
  SetIcon(IconForDisplay(icon_, right_badge_, NULL /*no top_left_icon*/,
                         left_badge_));
}

void NetworkMenuButton::SetIconOnly(const SkBitmap* icon) {
  icon_ = icon;
  SetIcon(IconForDisplay(icon_, right_badge_, NULL /*no top_left_icon*/,
                         left_badge_));
}

void NetworkMenuButton::SetBadgesOnly(const SkBitmap* right_badge,
                                      const SkBitmap* left_badge) {
  right_badge_ = right_badge;
  left_badge_ = left_badge;
  SetIcon(IconForDisplay(icon_, right_badge_, NULL /*no top_left_icon*/,
                         left_badge_));
}

void NetworkMenuButton::SetNetworkIcon(NetworkLibrary* cros,
                                       const Network* network) {
  ResourceBundle& rb = ResourceBundle::GetSharedInstance();

  if (!cros || !CrosLibrary::Get()->EnsureLoaded()) {
    SetIconAndBadges(rb.GetBitmapNamed(IDR_STATUSBAR_NETWORK_BARS0),
                     rb.GetBitmapNamed(IDR_STATUSBAR_NETWORK_WARNING),
                     NULL);
    SetTooltipText(UTF16ToWide(l10n_util::GetStringUTF16(
        IDS_STATUSBAR_NETWORK_NO_NETWORK_TOOLTIP)));
    return;
  }

  if (!cros->Connected() && !cros->Connecting()) {
    animation_connecting_.Stop();
    SetIconAndBadges(rb.GetBitmapNamed(IDR_STATUSBAR_NETWORK_BARS0),
                     rb.GetBitmapNamed(IDR_STATUSBAR_NETWORK_DISCONNECTED),
                     NULL);
    SetTooltipText(UTF16ToWide(l10n_util::GetStringUTF16(
        IDS_STATUSBAR_NETWORK_NO_NETWORK_TOOLTIP)));
    return;
  }

  if (cros->wifi_connecting() || cros->cellular_connecting()) {
    // Start the connecting animation if not running.
    if (!animation_connecting_.is_animating()) {
      animation_connecting_.Reset();
      animation_connecting_.StartThrobbing(-1);
      SetIconOnly(IconForNetworkConnecting(0, false));
    }
    const WirelessNetwork* wireless = NULL;
    if (cros->wifi_connecting()) {
      wireless = cros->wifi_network();
      SetBadgesOnly(NULL, NULL);
    } else {  // cellular_connecting
      wireless = cros->cellular_network();
      SetBadgesOnly(BadgeForNetworkTechnology(cros->cellular_network()), NULL);
    }
    SetTooltipText(UTF16ToWide(l10n_util::GetStringFUTF16(
        wireless->configuring() ? IDS_STATUSBAR_NETWORK_CONFIGURING_TOOLTIP
                                : IDS_STATUSBAR_NETWORK_CONNECTING_TOOLTIP,
        UTF8ToUTF16(wireless->name()))));
  } else {
    // Stop connecting animation since we are not connecting.
    animation_connecting_.Stop();
    // Only set the icon, if it is an active network that changed.
    if (network && network->is_active()) {
      const SkBitmap* right_badge(NULL);
      const SkBitmap* left_badge(NULL);
      if (cros->virtual_network())
        left_badge = rb.GetBitmapNamed(IDR_STATUSBAR_NETWORK_SECURE);
      if (network->type() == TYPE_ETHERNET) {
        SetIconAndBadges(rb.GetBitmapNamed(IDR_STATUSBAR_WIRED),
                         right_badge, left_badge);
        SetTooltipText(
            UTF16ToWide(l10n_util::GetStringFUTF16(
                IDS_STATUSBAR_NETWORK_CONNECTED_TOOLTIP,
                l10n_util::GetStringUTF16(
                    IDS_STATUSBAR_NETWORK_DEVICE_ETHERNET))));
      } else if (network->type() == TYPE_WIFI) {
        const WifiNetwork* wifi = static_cast<const WifiNetwork*>(network);
        SetIconAndBadges(IconForNetworkStrength(wifi, false),
                         right_badge, left_badge);
        SetTooltipText(UTF16ToWide(l10n_util::GetStringFUTF16(
            IDS_STATUSBAR_NETWORK_CONNECTED_TOOLTIP,
            UTF8ToUTF16(wifi->name()))));
      } else if (network->type() == TYPE_CELLULAR) {
        const CellularNetwork* cellular =
            static_cast<const CellularNetwork*>(network);
        right_badge = BadgeForNetworkTechnology(cellular);
        SetIconAndBadges(IconForNetworkStrength(cellular, false),
                         right_badge, left_badge);
        SetTooltipText(UTF16ToWide(l10n_util::GetStringFUTF16(
            IDS_STATUSBAR_NETWORK_CONNECTED_TOOLTIP,
            UTF8ToUTF16(cellular->name()))));
      }
    }
  }
}

void NetworkMenuButton::RefreshNetworkObserver(NetworkLibrary* cros) {
  const Network* network = cros->active_network();
  std::string new_network = network ? network->service_path() : std::string();
  if (active_network_ != new_network) {
    if (!active_network_.empty()) {
      cros->RemoveNetworkObserver(active_network_, this);
    }
    if (!new_network.empty()) {
      cros->AddNetworkObserver(new_network, this);
    }
    active_network_ = new_network;
  }
}

void NetworkMenuButton::RefreshNetworkDeviceObserver(NetworkLibrary* cros) {
  const NetworkDevice* cellular = cros->FindCellularDevice();
  std::string new_cellular_device_path = cellular ?
      cellular->device_path() : std::string();
  if (cellular_device_path_ != new_cellular_device_path) {
    if (!cellular_device_path_.empty()) {
      cros->RemoveNetworkDeviceObserver(cellular_device_path_, this);
    }
    if (!new_cellular_device_path.empty()) {
      was_sim_locked_ = cellular->is_sim_locked();
      cros->AddNetworkDeviceObserver(new_cellular_device_path, this);
    }
    cellular_device_path_ = new_cellular_device_path;
  }
}

void NetworkMenuButton::ShowOptionalMobileDataPromoNotification(
    NetworkLibrary* cros) {
  // Display one-time notification for non-Guest users on first use
  // of Mobile Data connection or if there's a carrier deal defined
  // show that even if user has already seen generic promo.
  if (IsBrowserMode() && !UserManager::Get()->IsLoggedInAsGuest() &&
      check_for_promo_ && BrowserList::GetLastActive() &&
      cros->cellular_connected() && !cros->ethernet_connected() &&
      !cros->wifi_connected()) {
    const ServicesCustomizationDocument::CarrierDeal* deal =
        GetCarrierDeal(cros);
    std::string deal_text;
    int carrier_deal_promo_pref = -1;
    if (deal) {
      carrier_deal_promo_pref = GetCarrierDealPromoShown();
      const std::string locale = g_browser_process->GetApplicationLocale();
      deal_text = deal->GetLocalizedString(locale, "notification_text");
      deal_url_ = deal->top_up_url;
    } else if (!ShouldShow3gPromoNotification()) {
      check_for_promo_ = false;
      return;
    }

    gfx::Rect button_bounds = GetScreenBounds();
    // StatusArea button Y position is usually -1, fix it so that
    // Contains() method for screen bounds works correctly.
    button_bounds.set_y(button_bounds.y() + 1);
    gfx::Rect screen_bounds(chromeos::CalculateScreenBounds(gfx::Size()));

    // Chrome window is initialized in visible state off screen and then is
    // moved into visible screen area. Make sure that we're on screen
    // so that bubble is shown correctly.
    if (!screen_bounds.Contains(button_bounds)) {
      // If we're not on screen yet, delay notification display.
      // It may be shown earlier, on next NetworkLibrary callback processing.
      if (method_factory_.empty()) {
        MessageLoop::current()->PostDelayedTask(FROM_HERE,
            method_factory_.NewRunnableMethod(
                &NetworkMenuButton::ShowOptionalMobileDataPromoNotification,
                cros),
            kPromoShowDelayMs);
      }
      return;
    }

    // Add deal text if it's defined.
    std::wstring notification_text;
    std::wstring default_text =
        UTF16ToWide(l10n_util::GetStringUTF16(IDS_3G_NOTIFICATION_MESSAGE));
    if (!deal_text.empty()) {
      notification_text = StringPrintf(L"%ls\n\n%ls",
                                       UTF8ToWide(deal_text).c_str(),
                                       default_text.c_str());
    } else {
      notification_text = default_text;
    }

    // Use deal URL if it's defined or general "Network Settings" URL.
    int link_message_id;
    if (deal_url_.empty())
      link_message_id = IDS_OFFLINE_NETWORK_SETTINGS;
    else
      link_message_id = IDS_STATUSBAR_NETWORK_VIEW_ACCOUNT;

    mobile_data_bubble_ = MessageBubble::Show(
        GetWidget(),
        button_bounds,
        BubbleBorder::TOP_RIGHT ,
        ResourceBundle::GetSharedInstance().GetBitmapNamed(IDR_NOTIFICATION_3G),
        notification_text,
        UTF16ToWide(l10n_util::GetStringUTF16(link_message_id)),
        this);

    check_for_promo_ = false;
    SetShow3gPromoNotification(false);
    if (carrier_deal_promo_pref != kNotificationCountPrefDefault)
      SetCarrierDealPromoShown(carrier_deal_promo_pref + 1);
  }
}

}  // namespace chromeos