// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/webapps/browser/installable/installable_manager.h"

#include <algorithm>
#include <utility>

#include "base/check_is_test.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/strings/string_util.h"
#include "base/task/sequenced_task_runner.h"
#include "build/build_config.h"
#include "components/webapps/browser/installable/installable_metrics.h"
#include "components/webapps/browser/webapps_client.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/page.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/storage_partition.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/mojom/favicon/favicon_url.mojom.h"
#include "third_party/blink/public/mojom/manifest/display_mode.mojom.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "url/origin.h"

namespace webapps {

namespace {

void OnDidCompleteGetAllErrors(
    base::OnceCallback<void(std::vector<content::InstallabilityError>
                                installability_errors)> callback,
    const InstallableData& data) {
  std::vector<content::InstallabilityError> installability_errors;
  for (auto error : data.errors) {
    content::InstallabilityError installability_error =
        GetInstallabilityError(error);
    if (!installability_error.error_id.empty())
      installability_errors.push_back(installability_error);
  }

  std::move(callback).Run(std::move(installability_errors));
}

void OnDidCompleteGetPrimaryIcon(
    base::OnceCallback<void(const SkBitmap*)> callback,
    const InstallableData& data) {
  std::move(callback).Run(data.primary_icon.get());
}

}  // namespace

InstallableManager::InstallableManager(content::WebContents* web_contents)
    : content::WebContentsObserver(web_contents),
      content::WebContentsUserData<InstallableManager>(*web_contents),
      page_data_(std::make_unique<InstallablePageData>()),
      sequenced_task_runner_(base::SequencedTaskRunner::GetCurrentDefault()) {
  // This is null in unit tests.
  if (web_contents) {
    content::StoragePartition* storage_partition =
        web_contents->GetBrowserContext()->GetStoragePartition(
            web_contents->GetSiteInstance());
    DCHECK(storage_partition);
  }
}

InstallableManager::~InstallableManager() {
}

void InstallableManager::GetData(const InstallableParams& params,
                                 InstallableCallback callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  DCHECK(callback);
  // Return immediately if we're already working on a task. The new task will be
  // looked at once the current task is finished.
  bool was_active = task_queue_.HasCurrent();
  task_queue_.Add(std::make_unique<InstallableTask>(
      GetWebContents(), weak_factory_.GetWeakPtr(), params, std::move(callback),
      *page_data_));
  if (was_active)
    return;

  task_queue_.Current().Start();
}

void InstallableManager::GetAllErrors(
    base::OnceCallback<void(std::vector<content::InstallabilityError>
                                installability_errors)> callback) {
  DCHECK(callback);
  InstallableParams params;
  params.check_eligibility = true;
  params.installable_criteria = InstallableCriteria::kValidManifestWithIcons;
  params.fetch_screenshots = true;
  params.valid_primary_icon = true;
  params.is_debug_mode = true;
  GetData(params,
          base::BindOnce(OnDidCompleteGetAllErrors, std::move(callback)));
}

void InstallableManager::GetPrimaryIcon(
    base::OnceCallback<void(const SkBitmap*)> callback) {
  DCHECK(callback);
  InstallableParams params;
  params.valid_primary_icon = true;
  GetData(params,
          base::BindOnce(OnDidCompleteGetPrimaryIcon, std::move(callback)));
}

void InstallableManager::SetSequencedTaskRunnerForTesting(
    scoped_refptr<base::SequencedTaskRunner> task_runner) {
  sequenced_task_runner_ = task_runner;
}

InstallableStatusCode InstallableManager::manifest_error() const {
  return page_data_->manifest_error();
}

InstallableStatusCode InstallableManager::icon_error() const {
  return page_data_->icon_error();
}

GURL InstallableManager::icon_url() const {
  return page_data_->primary_icon_url();
}

const SkBitmap* InstallableManager::icon() const {
  return page_data_->primary_icon();
}

content::WebContents* InstallableManager::GetWebContents() {
  content::WebContents* contents = web_contents();
  if (!contents || contents->IsBeingDestroyed())
    return nullptr;
  return contents;
}

void InstallableManager::Reset(InstallableStatusCode error) {
  DCHECK(error != InstallableStatusCode::NO_ERROR_DETECTED);
  // Prevent any outstanding callbacks to or from this object from being called.
  weak_factory_.InvalidateWeakPtrs();

  task_queue_.ResetWithError(error);

  page_data_->Reset();

  OnResetData();
}

void InstallableManager::OnTaskFinished() {
  sequenced_task_runner_->PostTask(
      FROM_HERE, base::BindOnce(&InstallableManager::FinishAndStartNextTask,
                                weak_factory_.GetWeakPtr()));
}

void InstallableManager::FinishAndStartNextTask() {
  task_queue_.Next();

  if (task_queue_.HasCurrent()) {
    task_queue_.Current().Start();
  }
}

void InstallableManager::PrimaryPageChanged(content::Page& page) {
  Reset(InstallableStatusCode::USER_NAVIGATED);
}

void InstallableManager::DidUpdateWebManifestURL(content::RenderFrameHost* rfh,
                                                 const GURL& manifest_url) {
  // A change in the manifest URL invalidates our entire internal state.
  Reset(InstallableStatusCode::MANIFEST_URL_CHANGED);
}

void InstallableManager::WebContentsDestroyed() {
  // This ensures that we do not just hang callbacks on web_contents being
  // destroyed.
  Reset(InstallableStatusCode::RENDERER_EXITING);
  Observe(nullptr);
}

const GURL& InstallableManager::manifest_url() const {
  return page_data_->manifest_url();
}

const blink::mojom::Manifest& InstallableManager::manifest() const {
  return page_data_->GetManifest();
}

WEB_CONTENTS_USER_DATA_KEY_IMPL(InstallableManager);

}  // namespace webapps
