// 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.

#ifndef COMPONENTS_LEVELDB_PROTO_INTERNAL_SHARED_PROTO_DATABASE_CLIENT_H_
#define COMPONENTS_LEVELDB_PROTO_INTERNAL_SHARED_PROTO_DATABASE_CLIENT_H_

#include <memory>
#include <string>

#include "base/bind.h"
#include "base/sequence_checker.h"
#include "components/leveldb_proto/internal/leveldb_database.h"
#include "components/leveldb_proto/internal/proto/shared_db_metadata.pb.h"
#include "components/leveldb_proto/internal/unique_proto_database.h"
#include "components/leveldb_proto/public/shared_proto_database_client_list.h"

namespace leveldb_proto {

class SharedProtoDatabase;

// TODO: Move all these as static or member functions in the class.
using ClientCorruptCallback = base::OnceCallback<void(bool)>;
using SharedClientInitCallback =
    base::OnceCallback<void(Enums::InitStatus,
                            SharedDBMetadataProto::MigrationStatus)>;

std::string StripPrefix(const std::string& key, const std::string& prefix);

std::unique_ptr<KeyVector> PrefixStrings(std::unique_ptr<KeyVector> strings,
                                         const std::string& prefix);

bool KeyFilterStripPrefix(const KeyFilter& key_filter,
                          const std::string& prefix,
                          const std::string& key);

void GetSharedDatabaseInitStatusAsync(
    const std::string& client_db_id,
    const scoped_refptr<SharedProtoDatabase>& db,
    Callbacks::InitStatusCallback callback);

void UpdateClientMetadataAsync(
    const scoped_refptr<SharedProtoDatabase>& db,
    const std::string& client_db_id,
    SharedDBMetadataProto::MigrationStatus migration_status,
    ClientCorruptCallback callback);

// Destroys all the data from obsolete clients, for the given |db_wrapper|
// instance. |callback| is called once all the obsolete clients data are
// removed, with failure status if one or more of the update fails.
void DestroyObsoleteSharedProtoDatabaseClients(
    std::unique_ptr<ProtoLevelDBWrapper> db_wrapper,
    Callbacks::UpdateCallback callback);

// Sets list of client names that are obsolete and will be cleared by next call
// to DestroyObsoleteSharedProtoDatabaseClients(). |list| is list of dbs
// with a |LAST| to mark the end of list.
void SetObsoleteClientListForTesting(const ProtoDbType* list);

// An implementation of ProtoDatabase<T> that uses a shared LevelDB and task
// runner.
// Should be created, destroyed, and used on the same sequenced task runner.
class SharedProtoDatabaseClient : public UniqueProtoDatabase {
 public:
  static std::string PrefixForDatabase(ProtoDbType db_type);

  ~SharedProtoDatabaseClient() override;

  void Init(const std::string& client_uma_name,
            Callbacks::InitStatusCallback callback) override;

  void InitWithDatabase(LevelDB* database,
                        const base::FilePath& database_dir,
                        const leveldb_env::Options& options,
                        bool destroy_on_corruption,
                        Callbacks::InitStatusCallback callback) override;

  // Overrides for prepending namespace and type prefix to all operations on the
  // shared database.
  void UpdateEntries(std::unique_ptr<KeyValueVector> entries_to_save,
                     std::unique_ptr<KeyVector> keys_to_remove,
                     Callbacks::UpdateCallback callback) override;
  void UpdateEntriesWithRemoveFilter(
      std::unique_ptr<KeyValueVector> entries_to_save,
      const KeyFilter& delete_key_filter,
      Callbacks::UpdateCallback callback) override;
  void UpdateEntriesWithRemoveFilter(
      std::unique_ptr<KeyValueVector> entries_to_save,
      const KeyFilter& delete_key_filter,
      const std::string& target_prefix,
      Callbacks::UpdateCallback callback) override;

  void LoadEntries(Callbacks::LoadCallback callback) override;
  void LoadEntriesWithFilter(const KeyFilter& filter,
                             Callbacks::LoadCallback callback) override;
  void LoadEntriesWithFilter(const KeyFilter& key_filter,
                             const leveldb::ReadOptions& options,
                             const std::string& target_prefix,
                             Callbacks::LoadCallback callback) override;

  void LoadKeys(Callbacks::LoadKeysCallback callback) override;
  void LoadKeys(const std::string& target_prefix,
                Callbacks::LoadKeysCallback callback) override;

  void LoadKeysAndEntries(
      Callbacks::LoadKeysAndEntriesCallback callback) override;
  void LoadKeysAndEntriesWithFilter(
      const KeyFilter& filter,
      Callbacks::LoadKeysAndEntriesCallback callback) override;
  void LoadKeysAndEntriesWithFilter(
      const KeyFilter& filter,
      const leveldb::ReadOptions& options,
      const std::string& target_prefix,
      Callbacks::LoadKeysAndEntriesCallback callback) override;
  void LoadKeysAndEntriesInRange(
      const std::string& start,
      const std::string& end,
      Callbacks::LoadKeysAndEntriesCallback callback) override;

  void GetEntry(const std::string& key,
                Callbacks::GetCallback callback) override;

  void Destroy(Callbacks::DestroyCallback callback) override;

  Callbacks::InitCallback GetInitCallback() const;

  const std::string& client_db_id() const { return prefix_; }

  void set_migration_status(
      SharedDBMetadataProto::MigrationStatus migration_status) {
    migration_status_ = migration_status;
  }

  virtual void UpdateClientInitMetadata(SharedDBMetadataProto::MigrationStatus);

  SharedDBMetadataProto::MigrationStatus migration_status() const {
    return migration_status_;
  }

 private:
  friend class SharedProtoDatabase;
  friend class SharedProtoDatabaseTest;
  friend class SharedProtoDatabaseClientTest;
  friend class TestSharedProtoDatabaseClient;

  // Hide this so clients can only be created by the SharedProtoDatabase.
  SharedProtoDatabaseClient(
      std::unique_ptr<ProtoLevelDBWrapper> db_wrapper,
      ProtoDbType db_type,
      const scoped_refptr<SharedProtoDatabase>& parent_db);

  static void StripPrefixLoadKeysCallback(
      Callbacks::LoadKeysCallback callback,
      const std::string& prefix,
      bool success,
      std::unique_ptr<leveldb_proto::KeyVector> keys);
  static void StripPrefixLoadKeysAndEntriesCallback(
      Callbacks::LoadKeysAndEntriesCallback callback,
      const std::string& prefix,
      bool success,
      std::unique_ptr<KeyValueMap> keys_entries);

  static std::unique_ptr<KeyValueVector> PrefixKeyEntryVector(
      std::unique_ptr<KeyValueVector> kev,
      const std::string& prefix);

  SEQUENCE_CHECKER(sequence_checker_);

  // |is_corrupt_| should be set by the SharedProtoDatabase that creates this
  // when a client is created that doesn't know about a previous shared
  // database corruption.
  bool is_corrupt_ = false;
  SharedDBMetadataProto::MigrationStatus migration_status_ =
      SharedDBMetadataProto::MIGRATION_NOT_ATTEMPTED;

  const std::string prefix_;

  scoped_refptr<SharedProtoDatabase> parent_db_;

  base::WeakPtrFactory<SharedProtoDatabaseClient> weak_ptr_factory_{this};

  DISALLOW_COPY_AND_ASSIGN(SharedProtoDatabaseClient);
};

}  // namespace leveldb_proto

#endif  // COMPONENTS_LEVELDB_PROTO_INTERNAL_SHARED_PROTO_DATABASE_CLIENT_H_
