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

#include <stddef.h>
#include <utility>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/version.h"
#include "components/update_client/action_update.h"
#include "components/update_client/configurator.h"
#include "components/update_client/update_checker.h"
#include "components/update_client/update_client.h"
#include "components/update_client/update_client_errors.h"
#include "components/update_client/utils.h"

using std::string;
using std::vector;

namespace update_client {

namespace {

// Returns true if the |proposed| version is newer than |current| version.
bool IsVersionNewer(const base::Version& current, const std::string& proposed) {
  base::Version proposed_ver(proposed);
  return proposed_ver.IsValid() && current.CompareTo(proposed_ver) < 0;
}

}  // namespace

ActionUpdateCheck::ActionUpdateCheck(
    std::unique_ptr<UpdateChecker> update_checker,
    const base::Version& browser_version,
    const std::string& extra_request_parameters)
    : update_checker_(std::move(update_checker)),
      browser_version_(browser_version),
      extra_request_parameters_(extra_request_parameters) {}

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

void ActionUpdateCheck::Run(UpdateContext* update_context, Callback callback) {
  DCHECK(thread_checker_.CalledOnValidThread());

  ActionImpl::Run(update_context, callback);

  // Calls out to get the corresponding CrxComponent data for the CRXs in this
  // update context.
  vector<CrxComponent> crx_components;
  update_context_->crx_data_callback.Run(update_context_->ids, &crx_components);

  for (size_t i = 0; i != crx_components.size(); ++i) {
    std::unique_ptr<CrxUpdateItem> item = base::MakeUnique<CrxUpdateItem>();
    const CrxComponent& crx_component = crx_components[i];

    item->id = GetCrxComponentID(crx_component);
    item->component = crx_component;
    item->last_check = base::TimeTicks::Now();
    item->crx_urls.clear();
    item->crx_diffurls.clear();
    item->previous_version = crx_component.version;
    item->next_version = base::Version();
    item->previous_fp = crx_component.fingerprint;
    item->next_fp.clear();
    item->on_demand = update_context->is_foreground;
    item->diff_update_failed = false;
    item->error_category = 0;
    item->error_code = 0;
    item->extra_code1 = 0;
    item->diff_error_category = 0;
    item->diff_error_code = 0;
    item->diff_extra_code1 = 0;
    item->download_metrics.clear();

    CrxUpdateItem* item_ptr = item.get();
    update_context_->update_items[item_ptr->id] = std::move(item);

    ChangeItemState(item_ptr, CrxUpdateItem::State::kChecking);
  }

  update_checker_->CheckForUpdates(
      update_context_->update_items, extra_request_parameters_,
      update_context_->enabled_component_updates,
      base::Bind(&ActionUpdateCheck::UpdateCheckComplete,
                 base::Unretained(this)));
}

void ActionUpdateCheck::UpdateCheckComplete(
    int error,
    const UpdateResponse::Results& results,
    int retry_after_sec) {
  DCHECK(thread_checker_.CalledOnValidThread());

  update_context_->retry_after_sec_ = retry_after_sec;

  if (!error)
    OnUpdateCheckSucceeded(results);
  else
    OnUpdateCheckFailed(error);
}

void ActionUpdateCheck::OnUpdateCheckSucceeded(
    const UpdateResponse::Results& results) {
  DCHECK(thread_checker_.CalledOnValidThread());
  VLOG(1) << "Update check succeeded.";
  std::vector<UpdateResponse::Result>::const_iterator it;
  for (it = results.list.begin(); it != results.list.end(); ++it) {
    CrxUpdateItem* crx = FindUpdateItemById(it->extension_id);
    if (!crx)
      continue;

    if (crx->state != CrxUpdateItem::State::kChecking) {
      NOTREACHED();
      continue;  // Not updating this CRX now.
    }

    if (it->manifest.version.empty()) {
      // No version means no update available.
      ChangeItemState(crx, CrxUpdateItem::State::kNoUpdate);
      VLOG(1) << "No update available for CRX: " << crx->id;
      continue;
    }

    if (!IsVersionNewer(crx->component.version, it->manifest.version)) {
      // The CRX is up to date.
      ChangeItemState(crx, CrxUpdateItem::State::kUpToDate);
      VLOG(1) << "Component already up to date: " << crx->id;
      continue;
    }

    if (!it->manifest.browser_min_version.empty()) {
      if (IsVersionNewer(browser_version_, it->manifest.browser_min_version)) {
        // The CRX is not compatible with this Chrome version.
        VLOG(1) << "Ignoring incompatible CRX: " << crx->id;
        ChangeItemState(crx, CrxUpdateItem::State::kNoUpdate);
        continue;
      }
    }

    if (it->manifest.packages.size() != 1) {
      // Assume one and only one package per CRX.
      VLOG(1) << "Ignoring multiple packages for CRX: " << crx->id;
      ChangeItemState(crx, CrxUpdateItem::State::kNoUpdate);
      continue;
    }

    // Parse the members of the result and queue an upgrade for this CRX.
    crx->next_version = base::Version(it->manifest.version);

    VLOG(1) << "Update found for CRX: " << crx->id;

    const auto& package(it->manifest.packages[0]);
    crx->next_fp = package.fingerprint;

    // Resolve the urls by combining the base urls with the package names.
    for (size_t i = 0; i != it->crx_urls.size(); ++i) {
      const GURL url(it->crx_urls[i].Resolve(package.name));
      if (url.is_valid())
        crx->crx_urls.push_back(url);
    }
    for (size_t i = 0; i != it->crx_diffurls.size(); ++i) {
      const GURL url(it->crx_diffurls[i].Resolve(package.namediff));
      if (url.is_valid())
        crx->crx_diffurls.push_back(url);
    }

    crx->hash_sha256 = package.hash_sha256;
    crx->hashdiff_sha256 = package.hashdiff_sha256;

    ChangeItemState(crx, CrxUpdateItem::State::kCanUpdate);

    update_context_->queue.push(crx->id);
  }

  // All components that are not included in the update response are
  // considered up to date.
  ChangeAllItemsState(CrxUpdateItem::State::kChecking,
                      CrxUpdateItem::State::kUpToDate);

  if (update_context_->queue.empty()) {
    VLOG(1) << "Update check completed but no update is needed.";
    UpdateComplete(Error::NONE);
    return;
  }

  // Starts the execution flow of updating the CRXs in this context.
  UpdateCrx();
}

void ActionUpdateCheck::OnUpdateCheckFailed(int error) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(error);

  VLOG(1) << "Update check failed." << error;

  ChangeAllItemsState(CrxUpdateItem::State::kChecking,
                      CrxUpdateItem::State::kNoUpdate);

  UpdateComplete(Error::UPDATE_CHECK_ERROR);
}

}  // namespace update_client
