// Copyright 2018 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 "media/capabilities/in_memory_video_decode_stats_db_impl.h"

#include <memory>
#include <tuple>

#include "base/files/file_path.h"
#include "base/format_macros.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/sequence_checker.h"
#include "base/strings/stringprintf.h"
#include "base/task_scheduler/post_task.h"
#include "media/base/bind_to_current_loop.h"
#include "media/capabilities/video_decode_stats_db_provider.h"

namespace media {

InMemoryVideoDecodeStatsDBFactory::InMemoryVideoDecodeStatsDBFactory(
    VideoDecodeStatsDBProvider* seed_db_provider)
    : seed_db_provider_(seed_db_provider) {
  DVLOG(2) << __func__ << " has_seed_provider:" << !!seed_db_provider_;
}

InMemoryVideoDecodeStatsDBFactory::~InMemoryVideoDecodeStatsDBFactory() =
    default;

std::unique_ptr<VideoDecodeStatsDB>
InMemoryVideoDecodeStatsDBFactory::CreateDB() {
  return std::make_unique<InMemoryVideoDecodeStatsDBImpl>(seed_db_provider_);
}

InMemoryVideoDecodeStatsDBImpl::InMemoryVideoDecodeStatsDBImpl(
    VideoDecodeStatsDBProvider* seed_db_provider)
    : seed_db_provider_(seed_db_provider), weak_ptr_factory_(this) {
  DVLOG(2) << __func__;
}

InMemoryVideoDecodeStatsDBImpl::~InMemoryVideoDecodeStatsDBImpl() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

void InMemoryVideoDecodeStatsDBImpl::Initialize(InitializeCB init_cb) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(init_cb);
  DCHECK(!db_init_);

  // Fetch an *initialized* seed DB.
  if (seed_db_provider_) {
    seed_db_provider_->GetVideoDecodeStatsDB(
        base::BindOnce(&InMemoryVideoDecodeStatsDBImpl::OnGotSeedDB,
                       weak_ptr_factory_.GetWeakPtr(), std::move(init_cb)));
  } else {
    // No seed DB provider (e.g. guest session) means no work to do.
    DVLOG(2) << __func__ << " NO seed db";
    db_init_ = true;

    // Bind to avoid reentrancy.
    std::move(BindToCurrentLoop(std::move(init_cb))).Run(true);
  }
}

void InMemoryVideoDecodeStatsDBImpl::OnGotSeedDB(InitializeCB init_cb,
                                                 VideoDecodeStatsDB* db) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DVLOG(2) << __func__ << (db ? " has" : " null") << " seed db";

  db_init_ = true;
  seed_db_ = db;

  // Hard coding success = true. There are rare cases (e.g. disk corruption)
  // where an incognito profile may fail to acquire a reference to the base
  // profile's DB. But this just means incognito is in the same boat as guest
  // profiles (never have a seed DB) and is not a show stopper.
  std::move(init_cb).Run(true);
}

void InMemoryVideoDecodeStatsDBImpl::AppendDecodeStats(
    const VideoDescKey& key,
    const DecodeStatsEntry& entry,
    AppendDecodeStatsCB append_done_cb) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(db_init_);

  DVLOG(3) << __func__ << " Reading key " << key.ToLogString()
           << " from DB with intent to update with " << entry.ToLogString();

  auto it = in_memory_db_.find(key.Serialize());
  if (it == in_memory_db_.end()) {
    if (seed_db_) {
      // |seed_db_| exists and no in-memory entry is found for this key, means
      // we haven't checked the |seed_db_| yet. Query |seed_db_| and append new
      // stats to any seed values.
      seed_db_->GetDecodeStats(
          key, base::BindOnce(
                   &InMemoryVideoDecodeStatsDBImpl::CompleteAppendWithSeedData,
                   weak_ptr_factory_.GetWeakPtr(), key, entry,
                   std::move(append_done_cb)));
      return;
    }

    // Otherwise, these are the first stats for this key. Add a a copy of
    // |entry| to the database.
    in_memory_db_.emplace(key.Serialize(), entry);
  } else {
    // We've already asked the |seed_db_| for its data. Just add the new stats
    // to our local copy via the iterators reference.
    it->second += entry;
  }

  // Bind to avoid reentrancy.
  std::move(BindToCurrentLoop(std::move(append_done_cb))).Run(true);
}

void InMemoryVideoDecodeStatsDBImpl::GetDecodeStats(
    const VideoDescKey& key,
    GetDecodeStatsCB get_stats_cb) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(db_init_);

  DVLOG(3) << __func__ << " " << key.ToLogString();

  auto it = in_memory_db_.find(key.Serialize());
  if (it == in_memory_db_.end()) {
    if (seed_db_) {
      // |seed_db_| exists and no in-memory entry is found for this key, means
      // we haven't checked the |seed_db_| yet.
      seed_db_->GetDecodeStats(
          key, base::BindOnce(&InMemoryVideoDecodeStatsDBImpl::OnGotSeedEntry,
                              weak_ptr_factory_.GetWeakPtr(), key,
                              std::move(get_stats_cb)));
    } else {
      // No seed data. Return an empty entry. Bind to avoid reentrancy.
      std::move(BindToCurrentLoop(std::move(get_stats_cb)))
          .Run(true, std::make_unique<DecodeStatsEntry>(0, 0, 0));
    }
  } else {
    // Return whatever what we found. Bind to avoid reentrancy.
    std::move(BindToCurrentLoop(std::move(get_stats_cb)))
        .Run(true, std::make_unique<DecodeStatsEntry>(it->second));
  }
}

void InMemoryVideoDecodeStatsDBImpl::CompleteAppendWithSeedData(
    const VideoDescKey& key,
    const DecodeStatsEntry& entry,
    AppendDecodeStatsCB append_done_cb,
    bool read_success,
    std::unique_ptr<DecodeStatsEntry> seed_entry) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(db_init_);

  if (!read_success) {
    // Not a show stopper. Log it and carry on as if the seed DB were empty.
    DVLOG(2) << __func__ << " FAILED seed DB read for " << key.ToLogString();
    DCHECK(!seed_entry);
  }

  if (!seed_entry)
    seed_entry = std::make_unique<DecodeStatsEntry>(0, 0, 0);

  // Add new stats to the seed entry and store in memory.
  *seed_entry += entry;
  in_memory_db_.emplace(key.Serialize(), *seed_entry);

  DVLOG(3) << __func__ << " Updating " << key.ToLogString() << " with "
           << entry.ToLogString() << " aggregate:" << seed_entry->ToLogString();

  std::move(append_done_cb).Run(true);
}

void InMemoryVideoDecodeStatsDBImpl::OnGotSeedEntry(
    const VideoDescKey& key,
    GetDecodeStatsCB get_stats_cb,
    bool success,
    std::unique_ptr<DecodeStatsEntry> seed_entry) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // Failure is not a show stopper. Just a debug log...
  DVLOG(3) << __func__ << " read " << (success ? "succeeded" : "FAILED!")
           << " entry: " << (seed_entry ? seed_entry->ToLogString() : "null");

  if (!seed_entry)
    seed_entry = std::make_unique<DecodeStatsEntry>(0, 0, 0);

  // Always write to |in_memory_db_| to avoid querying |seed_db_| for this key
  // going forward.
  in_memory_db_.emplace(key.Serialize(), *seed_entry);

  std::move(get_stats_cb).Run(true, std::move(seed_entry));
}

void InMemoryVideoDecodeStatsDBImpl::DestroyStats(
    base::OnceClosure destroy_done_cb) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DVLOG(2) << __func__;

  // Really, this is not reachable code because user's can't clear the history
  // for a guest/incognito account. But if that ever changes, the reasonable
  // thing is to wipe only the |in_memory_db_|. |seed_db_| can be cleared by the
  // profile that owns it.
  in_memory_db_.clear();

  // Bind to avoid reentrancy.
  std::move(BindToCurrentLoop(std::move(destroy_done_cb))).Run();
}

}  // namespace media
