// Copyright 2015 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 "components/update_client/update_engine.h"

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/location.h"
#include "base/stl_util.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "components/prefs/pref_service.h"
#include "components/update_client/action_update_check.h"
#include "components/update_client/configurator.h"
#include "components/update_client/crx_update_item.h"
#include "components/update_client/persisted_data.h"
#include "components/update_client/update_checker.h"
#include "components/update_client/update_client_errors.h"

namespace update_client {

UpdateContext::UpdateContext(
    const scoped_refptr<Configurator>& config,
    bool is_foreground,
    const std::vector<std::string>& ids,
    const UpdateClient::CrxDataCallback& crx_data_callback,
    const UpdateEngine::NotifyObserversCallback& notify_observers_callback,
    const UpdateEngine::Callback& callback,
    UpdateChecker::Factory update_checker_factory,
    CrxDownloader::Factory crx_downloader_factory,
    PingManager* ping_manager)
    : config(config),
      is_foreground(is_foreground),
      enabled_component_updates(config->EnabledComponentUpdates()),
      ids(ids),
      crx_data_callback(crx_data_callback),
      notify_observers_callback(notify_observers_callback),
      callback(callback),
      main_task_runner(base::ThreadTaskRunnerHandle::Get()),
      blocking_task_runner(config->GetSequencedTaskRunner()),
      update_checker_factory(update_checker_factory),
      crx_downloader_factory(crx_downloader_factory),
      ping_manager(ping_manager),
      retry_after_sec_(0) {}

UpdateContext::~UpdateContext() {}

UpdateEngine::UpdateEngine(
    const scoped_refptr<Configurator>& config,
    UpdateChecker::Factory update_checker_factory,
    CrxDownloader::Factory crx_downloader_factory,
    PingManager* ping_manager,
    const NotifyObserversCallback& notify_observers_callback)
    : config_(config),
      update_checker_factory_(update_checker_factory),
      crx_downloader_factory_(crx_downloader_factory),
      ping_manager_(ping_manager),
      metadata_(new PersistedData(config->GetPrefService())),
      notify_observers_callback_(notify_observers_callback) {}

UpdateEngine::~UpdateEngine() {
  DCHECK(thread_checker_.CalledOnValidThread());
}

bool UpdateEngine::GetUpdateState(const std::string& id,
                                  CrxUpdateItem* update_item) {
  DCHECK(thread_checker_.CalledOnValidThread());
  for (const auto* context : update_contexts_) {
    const auto& update_items = context->update_items;
    const auto it = update_items.find(id);
    if (it != update_items.end()) {
      *update_item = *it->second.get();
      return true;
    }
  }
  return false;
}

void UpdateEngine::Update(
    bool is_foreground,
    const std::vector<std::string>& ids,
    const UpdateClient::CrxDataCallback& crx_data_callback,
    const Callback& callback) {
  DCHECK(thread_checker_.CalledOnValidThread());

  if (IsThrottled(is_foreground)) {
    base::ThreadTaskRunnerHandle::Get()->PostTask(
        FROM_HERE, base::Bind(callback, Error::RETRY_LATER));
    return;
  }

  std::unique_ptr<UpdateContext> update_context(new UpdateContext(
      config_, is_foreground, ids, crx_data_callback,
      notify_observers_callback_, callback, update_checker_factory_,
      crx_downloader_factory_, ping_manager_));

  CrxUpdateItem update_item;
  std::unique_ptr<ActionUpdateCheck> update_check_action(new ActionUpdateCheck(
      (*update_context->update_checker_factory)(config_, metadata_.get()),
      config_->GetBrowserVersion(), config_->ExtraRequestParams()));

  update_context->current_action.reset(update_check_action.release());
  update_contexts_.insert(update_context.get());

  update_context->current_action->Run(
      update_context.get(),
      base::Bind(&UpdateEngine::UpdateComplete, base::Unretained(this),
                 update_context.get()));

  ignore_result(update_context.release());
}

void UpdateEngine::UpdateComplete(UpdateContext* update_context, Error error) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(update_contexts_.find(update_context) != update_contexts_.end());

  const int throttle_sec(update_context->retry_after_sec_);
  DCHECK_LE(throttle_sec, 24 * 60 * 60);

  // Only positive values for throttle_sec are effective. 0 means that no
  // throttling occurs and has the effect of resetting the member.
  // Negative values are not trusted and are ignored.
  if (throttle_sec >= 0)
    throttle_updates_until_ =
        throttle_sec
            ? base::TimeTicks::Now() +
                  base::TimeDelta::FromSeconds(throttle_sec)
            : base::TimeTicks();

  auto callback = update_context->callback;

  update_contexts_.erase(update_context);
  delete update_context;

  callback.Run(error);
}

bool UpdateEngine::IsThrottled(bool is_foreground) const {
  if (is_foreground || throttle_updates_until_.is_null())
    return false;

  const auto now(base::TimeTicks::Now());

  // Throttle the calls in the interval (t - 1 day, t) to limit the effect of
  // unset clocks or clock drift.
  return throttle_updates_until_ - base::TimeDelta::FromDays(1) < now &&
         now < throttle_updates_until_;
}

}  // namespace update_client
