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

#include "base/callback.h"
#include "base/stl_util-inl.h"
#include "base/string_number_conversions.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/chromeos/cros/cros_library.h"
#include "chrome/browser/chromeos/cros/network_library.h"
#include "chrome/browser/chromeos/notifications/balloon_view_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 "chrome/common/time_format.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#include "ui/base/l10n/l10n_util.h"

namespace {

// Returns prefs::kShowPlanNotifications in the profile of the last active
// browser. If there is no active browser, returns true.
bool ShouldShowMobilePlanNotifications() {
  Browser* browser = BrowserList::GetLastActive();
  if (!browser || !browser->profile())
    return true;

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

}  // namespace

namespace chromeos {

NetworkMessageObserver::NetworkMessageObserver(Profile* profile)
    : notification_connection_error_(profile, "network_connection.chromeos",
          IDR_NOTIFICATION_NETWORK_FAILED,
          l10n_util::GetStringUTF16(IDS_NETWORK_CONNECTION_ERROR_TITLE)),
      notification_low_data_(profile, "network_low_data.chromeos",
          IDR_NOTIFICATION_BARS_CRITICAL,
          l10n_util::GetStringUTF16(IDS_NETWORK_LOW_DATA_TITLE)),
      notification_no_data_(profile, "network_no_data.chromeos",
          IDR_NOTIFICATION_BARS_EMPTY,
          l10n_util::GetStringUTF16(IDS_NETWORK_OUT_OF_DATA_TITLE)) {
  NetworkLibrary* netlib = CrosLibrary::Get()->GetNetworkLibrary();
  OnNetworkManagerChanged(netlib);
  // Note that this gets added as a NetworkManagerObserver,
  // CellularDataPlanObserver, and UserActionObserver in browser_init.cc
}

NetworkMessageObserver::~NetworkMessageObserver() {
  NetworkLibrary* netlib = CrosLibrary::Get()->GetNetworkLibrary();
  netlib->RemoveNetworkManagerObserver(this);
  netlib->RemoveCellularDataPlanObserver(this);
  netlib->RemoveUserActionObserver(this);
  notification_connection_error_.Hide();
  notification_low_data_.Hide();
  notification_no_data_.Hide();
}

// static
bool NetworkMessageObserver::IsApplicableBackupPlan(
    const CellularDataPlan* plan, const CellularDataPlan* other_plan) {
  // By applicable plan, we mean that the other plan has data AND the timeframe
  // will apply: (unlimited OR used bytes < max bytes) AND
  //   ((start time - 1 sec) <= end time of currently active plan).
  // In other words, there is data available and there is no gap of more than a
  // second in time between the old plan and the new plan.
  bool has_data = other_plan->plan_type == CELLULAR_DATA_PLAN_UNLIMITED ||
      other_plan->remaining_data() > 0;
  bool will_apply =
      (other_plan->plan_start_time - plan->plan_end_time).InSeconds() <= 1;
  return has_data && will_apply;
}

void NetworkMessageObserver::OpenMobileSetupPage(const ListValue* args) {
  Browser* browser = BrowserList::GetLastActive();
  if (browser)
    browser->OpenMobilePlanTabAndActivate();
}

void NetworkMessageObserver::OpenMoreInfoPage(const ListValue* args) {
  Browser* browser = BrowserList::GetLastActive();
  if (!browser)
    return;
  chromeos::NetworkLibrary* lib =
      chromeos::CrosLibrary::Get()->GetNetworkLibrary();
  const chromeos::CellularNetwork* cellular = lib->cellular_network();
  if (!cellular)
    return;
  browser->ShowSingletonTab(GURL(cellular->payment_url()));
}

void NetworkMessageObserver::InitNewPlan(const CellularDataPlan* plan) {
  notification_low_data_.Hide();
  notification_no_data_.Hide();
  if (plan->plan_type == CELLULAR_DATA_PLAN_UNLIMITED) {
    notification_no_data_.set_title(
        l10n_util::GetStringFUTF16(IDS_NETWORK_DATA_EXPIRED_TITLE,
                                   ASCIIToUTF16(plan->plan_name)));
    notification_low_data_.set_title(
        l10n_util::GetStringFUTF16(IDS_NETWORK_NEARING_EXPIRATION_TITLE,
                                   ASCIIToUTF16(plan->plan_name)));
  } else {
    notification_no_data_.set_title(
        l10n_util::GetStringFUTF16(IDS_NETWORK_OUT_OF_DATA_TITLE,
                                   ASCIIToUTF16(plan->plan_name)));
    notification_low_data_.set_title(
        l10n_util::GetStringFUTF16(IDS_NETWORK_LOW_DATA_TITLE,
                                   ASCIIToUTF16(plan->plan_name)));
  }
}

void NetworkMessageObserver::ShowNeedsPlanNotification(
    const CellularNetwork* cellular) {
  notification_no_data_.set_title(
      l10n_util::GetStringFUTF16(IDS_NETWORK_NO_DATA_PLAN_TITLE,
                                 UTF8ToUTF16(cellular->name())));
  notification_no_data_.Show(
      l10n_util::GetStringFUTF16(
          IDS_NETWORK_NO_DATA_PLAN_MESSAGE,
          UTF8ToUTF16(cellular->name())),
      l10n_util::GetStringUTF16(IDS_NETWORK_PURCHASE_MORE_MESSAGE),
      NewCallback(this, &NetworkMessageObserver::OpenMobileSetupPage),
      false, false);
}

void NetworkMessageObserver::ShowNoDataNotification(
    CellularDataPlanType plan_type) {
  notification_low_data_.Hide();  // Hide previous low data notification.
  string16 message = plan_type == CELLULAR_DATA_PLAN_UNLIMITED ?
      TimeFormat::TimeRemaining(base::TimeDelta()) :
      l10n_util::GetStringFUTF16(IDS_NETWORK_DATA_REMAINING_MESSAGE,
                                 ASCIIToUTF16("0"));
  notification_no_data_.Show(message,
      l10n_util::GetStringUTF16(IDS_NETWORK_PURCHASE_MORE_MESSAGE),
      NewCallback(this, &NetworkMessageObserver::OpenMobileSetupPage),
      false, false);
}

void NetworkMessageObserver::ShowLowDataNotification(
    const CellularDataPlan* plan) {
  string16 message;
  if (plan->plan_type == CELLULAR_DATA_PLAN_UNLIMITED) {
    message = plan->GetPlanExpiration();
  } else {
    int64 remaining_mbytes = plan->remaining_data() / (1024 * 1024);
    message = l10n_util::GetStringFUTF16(IDS_NETWORK_DATA_REMAINING_MESSAGE,
        UTF8ToUTF16(base::Int64ToString(remaining_mbytes)));
  }
  notification_low_data_.Show(message,
      l10n_util::GetStringUTF16(IDS_NETWORK_MORE_INFO_MESSAGE),
      NewCallback(this, &NetworkMessageObserver::OpenMoreInfoPage),
      false, false);
}

bool NetworkMessageObserver::CheckNetworkFailed(const Network* network) {
  if (network->failed()) {
    NetworkStateMap::iterator iter =
        network_states_.find(network->service_path());
    // If the network did not previously exist, then don't do anything.
    // For example, if the user travels to a location and finds a service
    // that has previously failed, we don't want to show a notification.
    if (iter == network_states_.end())
      return false;
    // If network connection failed, display a notification.
    // We only do this if we were trying to make a new connection.
    // So if a previously connected network got disconnected for any reason,
    // we don't display notification.
    ConnectionState prev_state = iter->second;
    if (Network::IsConnectingState(prev_state))
      return true;
  }
  return false;
}

void NetworkMessageObserver::OnNetworkManagerChanged(NetworkLibrary* cros) {
  const Network* new_failed_network = NULL;
  // Check to see if we have any newly failed networks.
  for (WifiNetworkVector::const_iterator it = cros->wifi_networks().begin();
       it != cros->wifi_networks().end(); it++) {
    const WifiNetwork* net = *it;
    if (CheckNetworkFailed(net)) {
      new_failed_network = net;
      break;  // There should only be one failed network.
    }
  }

  if (!new_failed_network) {
    for (CellularNetworkVector::const_iterator it =
             cros->cellular_networks().begin();
         it != cros->cellular_networks().end(); it++) {
      const CellularNetwork* net = *it;
      if (CheckNetworkFailed(net)) {
        new_failed_network = net;
        break;  // There should only be one failed network.
      }
    }
  }

  if (!new_failed_network) {
    for (VirtualNetworkVector::const_iterator it =
             cros->virtual_networks().begin();
         it != cros->virtual_networks().end(); it++) {
      const VirtualNetwork* net = *it;
      if (CheckNetworkFailed(net)) {
        new_failed_network = net;
        break;  // There should only be one failed network.
      }
    }
  }

  network_states_.clear();
  for (WifiNetworkVector::const_iterator it = cros->wifi_networks().begin();
       it != cros->wifi_networks().end(); it++)
    network_states_[(*it)->service_path()] = (*it)->state();
  for (CellularNetworkVector::const_iterator it =
           cros->cellular_networks().begin();
       it != cros->cellular_networks().end(); it++)
    network_states_[(*it)->service_path()] = (*it)->state();
  for (VirtualNetworkVector::const_iterator it =
           cros->virtual_networks().begin();
       it != cros->virtual_networks().end(); it++)
    network_states_[(*it)->service_path()] = (*it)->state();

  // Show connection error notification if necessary.
  if (new_failed_network) {
    // Hide if already shown to force show it in case user has closed it.
    if (notification_connection_error_.visible())
      notification_connection_error_.Hide();
    notification_connection_error_.Show(l10n_util::GetStringFUTF16(
        IDS_NETWORK_CONNECTION_ERROR_MESSAGE,
        UTF8ToUTF16(new_failed_network->name())), false, false);
  }
}

void NetworkMessageObserver::OnCellularDataPlanChanged(NetworkLibrary* cros) {
  if (!ShouldShowMobilePlanNotifications())
    return;
  const CellularNetwork* cellular = cros->cellular_network();
  if (!cellular || !cellular->SupportsDataPlan())
    return;

  const CellularDataPlanVector* plans =
      cros->GetDataPlans(cellular->service_path());
  // If no plans available, check to see if we need a new plan.
  if (!plans || plans->empty()) {
    // If previously, we had low data, we know that a plan was near expiring.
    // In that case, because the plan disappeared, we assume that it expired.
    if (cellular_data_left_ == CellularNetwork::DATA_LOW) {
      ShowNoDataNotification(cellular_data_plan_type_);
    } else if (cellular->needs_new_plan()) {
      ShowNeedsPlanNotification(cellular);
    }
    SaveLastCellularInfo(cellular, NULL);
    return;
  }

  CellularDataPlanVector::const_iterator iter = plans->begin();
  const CellularDataPlan* current_plan = *iter;

  // If current plan is not the last plan (there is another backup plan),
  // then we do not show notifications for this plan.
  // For example, if there is another data plan available when this runs out.
  for (++iter; iter != plans->end(); ++iter) {
    if (IsApplicableBackupPlan(current_plan, *iter)) {
      SaveLastCellularInfo(cellular, current_plan);
      return;
    }
  }

  // If connected cellular network changed, or data plan is different, then
  // it's a new network. Then hide all previous notifications.
  bool new_plan = cellular->service_path() != cellular_service_path_ ||
      current_plan->GetUniqueIdentifier() != cellular_data_plan_unique_id_;

  if (new_plan) {
    InitNewPlan(current_plan);
  }

  if (cellular->data_left() == CellularNetwork::DATA_NONE) {
    ShowNoDataNotification(current_plan->plan_type);
  } else if (cellular->data_left() == CellularNetwork::DATA_VERY_LOW) {
    // Only show low data notification if we transition to very low data
    // and we are on the same plan. This is so that users don't get a
    // notification whenever they connect to a low data 3g network.
    if (!new_plan && (cellular_data_left_ != CellularNetwork::DATA_VERY_LOW))
      ShowLowDataNotification(current_plan);
  }

  SaveLastCellularInfo(cellular, current_plan);
}

void NetworkMessageObserver::OnConnectionInitiated(NetworkLibrary* cros,
                                                   const Network* network) {
  // If user initiated any network connection, we hide the error notification.
  notification_connection_error_.Hide();
}

void NetworkMessageObserver::SaveLastCellularInfo(
    const CellularNetwork* cellular, const CellularDataPlan* plan) {
  DCHECK(cellular);
  cellular_service_path_ = cellular->service_path();
  cellular_data_left_ = cellular->data_left();
  if (plan) {
    cellular_data_plan_unique_id_ = plan->GetUniqueIdentifier();
    cellular_data_plan_type_ = plan->plan_type;
  } else {
    cellular_data_plan_unique_id_ = std::string();
    cellular_data_plan_type_ = CELLULAR_DATA_PLAN_UNKNOWN;
  }
}

}  // namespace chromeos