// 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 "chrome/browser/web_resource/promo_resource_service.h"

#include "base/command_line.h"
#include "base/message_loop.h"
#include "base/threading/thread_restrictions.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_service.h"
#include "googleurl/src/gurl.h"

namespace {

// Delay on first fetch so we don't interfere with startup.
static const int kStartResourceFetchDelay = 5000;

// Delay between calls to update the cache (12 hours), and 3 min in debug mode.
static const int kCacheUpdateDelay = 12 * 60 * 60 * 1000;
static const int kTestCacheUpdateDelay = 3 * 60 * 1000;

// The version of the service (used to expire the cache when upgrading Chrome
// to versions with different types of promos).
static const int kPromoServiceVersion = 7;

GURL GetPromoResourceURL() {
  const std::string promo_server_url = CommandLine::ForCurrentProcess()->
      GetSwitchValueASCII(switches::kPromoServerURL);
  return promo_server_url.empty() ?
      NotificationPromo::PromoServerURL() : GURL(promo_server_url);
}

bool IsTest() {
  return CommandLine::ForCurrentProcess()->HasSwitch(switches::kPromoServerURL);
}

int GetCacheUpdateDelay() {
  return IsTest() ? kTestCacheUpdateDelay : kCacheUpdateDelay;
}

}  // namespace

// static
void PromoResourceService::RegisterPrefs(PrefService* local_state) {
  local_state->RegisterIntegerPref(prefs::kNtpPromoVersion, 0);
  local_state->RegisterStringPref(prefs::kNtpPromoLocale, std::string());
}

// static
void PromoResourceService::RegisterUserPrefs(PrefService* prefs) {
  prefs->RegisterStringPref(prefs::kNtpPromoResourceCacheUpdate,
                            "0",
                            PrefService::UNSYNCABLE_PREF);
  NotificationPromo::RegisterUserPrefs(prefs);

  // TODO(achuith): Delete this in M22.
  prefs->RegisterDoublePref(prefs::kNtpCustomLogoStart,
                            0,
                            PrefService::UNSYNCABLE_PREF);
  prefs->RegisterDoublePref(prefs::kNtpCustomLogoEnd,
                            0,
                            PrefService::UNSYNCABLE_PREF);
  prefs->ClearPref(prefs::kNtpCustomLogoStart);
  prefs->ClearPref(prefs::kNtpCustomLogoEnd);
}

PromoResourceService::PromoResourceService(Profile* profile)
    : WebResourceService(profile->GetPrefs(),
                         GetPromoResourceURL(),
                         true,  // append locale to URL
                         prefs::kNtpPromoResourceCacheUpdate,
                         kStartResourceFetchDelay,
                         GetCacheUpdateDelay()),
                         profile_(profile),
                         ALLOW_THIS_IN_INITIALIZER_LIST(
                             weak_ptr_factory_(this)),
                         web_resource_update_scheduled_(false) {
  ScheduleNotificationOnInit();
}

PromoResourceService::~PromoResourceService() {
}

void PromoResourceService::ScheduleNotification(double promo_start,
                                                double promo_end) {
  if (promo_start > 0 && promo_end > 0) {
    const int64 ms_until_start =
        static_cast<int64>((base::Time::FromDoubleT(
            promo_start) - base::Time::Now()).InMilliseconds());
    const int64 ms_until_end =
        static_cast<int64>((base::Time::FromDoubleT(
            promo_end) - base::Time::Now()).InMilliseconds());
    if (ms_until_start > 0) {
      // Schedule the next notification to happen at the start of promotion.
      PostNotification(ms_until_start);
    } else if (ms_until_end > 0) {
      if (ms_until_start <= 0) {
        // Notify immediately if time is between start and end.
        PostNotification(0);
      }
      // Schedule the next notification to happen at the end of promotion.
      PostNotification(ms_until_end);
    }
  }
}

void PromoResourceService::ScheduleNotificationOnInit() {
  std::string locale = g_browser_process->GetApplicationLocale();
  if (GetPromoServiceVersion() != kPromoServiceVersion ||
      GetPromoLocale() != locale) {
    // If the promo service has been upgraded or Chrome switched locales,
    // refresh the promos.
    // TODO(achuith): Mixing local_state and prefs does not work for
    // multi-profile case. We should probably store version/locale in prefs_
    // as well.
    PrefService* local_state = g_browser_process->local_state();
    local_state->SetInteger(prefs::kNtpPromoVersion, kPromoServiceVersion);
    local_state->SetString(prefs::kNtpPromoLocale, locale);
    prefs_->ClearPref(prefs::kNtpPromoResourceCacheUpdate);
    PostNotification(0);
  } else {
    // If the promo start is in the future, set a notification task to
    // invalidate the NTP cache at the time of the promo start.
    double promo_start = prefs_->GetDouble(prefs::kNtpPromoStart);
    double promo_end = prefs_->GetDouble(prefs::kNtpPromoEnd);
    ScheduleNotification(promo_start, promo_end);
  }
}

void PromoResourceService::PostNotification(int64 delay_ms) {
  if (web_resource_update_scheduled_)
    return;
  // TODO(achuith): This crashes if we post delay_ms = 0 to the message loop.
  // during startup.
  if (delay_ms > 0) {
    web_resource_update_scheduled_ = true;
    MessageLoop::current()->PostDelayedTask(
        FROM_HERE,
        base::Bind(&PromoResourceService::PromoResourceStateChange,
                   weak_ptr_factory_.GetWeakPtr()),
        base::TimeDelta::FromMilliseconds(delay_ms));
  } else if (delay_ms == 0) {
    PromoResourceStateChange();
  }
}

void PromoResourceService::PromoResourceStateChange() {
  web_resource_update_scheduled_ = false;
  content::NotificationService* service =
      content::NotificationService::current();
  service->Notify(chrome::NOTIFICATION_PROMO_RESOURCE_STATE_CHANGED,
                  content::Source<WebResourceService>(this),
                  content::NotificationService::NoDetails());
}

int PromoResourceService::GetPromoServiceVersion() {
  PrefService* local_state = g_browser_process->local_state();
  return local_state->GetInteger(prefs::kNtpPromoVersion);
}

std::string PromoResourceService::GetPromoLocale() {
  PrefService* local_state = g_browser_process->local_state();
  return local_state->GetString(prefs::kNtpPromoLocale);
}

void PromoResourceService::Unpack(const DictionaryValue& parsed_json) {
  NotificationPromo notification_promo(profile_);
  notification_promo.InitFromJson(parsed_json);

  if (notification_promo.new_notification()) {
    ScheduleNotification(notification_promo.StartTimeForGroup(),
                         notification_promo.EndTime());
  }
}

bool PromoResourceService::CanShowNotificationPromo(Profile* profile) {
  NotificationPromo notification_promo(profile);
  notification_promo.InitFromPrefs();
  return notification_promo.CanShow();
}