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

#ifndef COMPONENTS_SESSIONS_CORE_COMMAND_STORAGE_MANAGER_H_
#define COMPONENTS_SESSIONS_CORE_COMMAND_STORAGE_MANAGER_H_

#include <stddef.h>

#include <list>
#include <memory>
#include <vector>

#include "base/files/file_path.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/field_trial_params.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "components/os_crypt/async/common/encryptor.h"
#include "components/sessions/core/sessions_export.h"

namespace base {
class Value;
}

namespace sessions {
class CommandStorageManagerDelegate;
class SessionCommand;
class CommandStorageBackend;

// CommandStorageManager is responsible for reading/writing SessionCommands
// to disk. SessionCommands are used to save and restore the state of the
// browser. CommandStorageManager runs on the main thread and uses
// CommandStorageBackend (which runs on a background task runner) for the actual
// reading/writing. In hopes of minimizing IO, SessionCommands are queued up
// and processed after a delay.
class SESSIONS_EXPORT CommandStorageManager {
 public:
  // The bool parameter indicates whether there was an error reading the file.
  // If there was an error, the vector contains the set of commands up to the
  // error.
  using GetCommandsCallback =
      base::OnceCallback<void(std::vector<std::unique_ptr<SessionCommand>>,
                              bool)>;

  // Identifies the type of session service this is. This is used by the
  // backend to determine the name of the files.
  enum class SessionType { kAppRestore, kSessionRestore, kTabRestore };

  // Creates a new CommandStorageManager. `delegate` is not owned by this and
  // must outlive this.
  //
  // `path` is the base directory into which session files are written.
  CommandStorageManager(
      SessionType type,
      const base::FilePath& path,
      CommandStorageManagerDelegate* delegate,
      os_crypt_async::OSCryptAsync* os_crypt_async,
      scoped_refptr<base::SequencedTaskRunner> backend_task_runner);
  CommandStorageManager(const CommandStorageManager&) = delete;
  CommandStorageManager& operator=(const CommandStorageManager&) = delete;
  virtual ~CommandStorageManager();

  static scoped_refptr<base::SequencedTaskRunner>
  CreateDefaultBackendTaskRunner();

  // Returns the set of commands which were scheduled to be written. Once
  // committed to the backend, the commands are removed from here.
  const std::vector<std::unique_ptr<SessionCommand>>& pending_commands() {
    return pending_commands_;
  }

  // Whether the next save resets the file before writing to it.
  void set_pending_reset(bool value) { pending_reset_ = value; }
  bool pending_reset() const { return pending_reset_; }

  // Returns the number of commands sent down since the last reset.
  int commands_since_reset() const { return commands_since_reset_; }

  // Schedules a command. This adds |command| to pending_commands_ and
  // invokes StartSaveTimer to start a timer that invokes Save at a later
  // time.
  void ScheduleCommand(std::unique_ptr<SessionCommand> command);

  // Appends a command as part of a general rebuild. This will neither count
  // against a rebuild, nor will it trigger a save of commands.
  void AppendRebuildCommand(std::unique_ptr<SessionCommand> command);
  void AppendRebuildCommands(
      std::vector<std::unique_ptr<SessionCommand>> commands);

  // Erase the |old_command| from the list of commands.
  // The passed command will automatically be deleted.
  void EraseCommand(SessionCommand* old_command);

  // Swap a |new_command| into the list of queued commands at the location of
  // the |old_command|. The |old_command| will be automatically deleted in the
  // process.
  void SwapCommand(SessionCommand* old_command,
                   std::unique_ptr<SessionCommand> new_command);

  // Clears all commands from the list.
  void ClearPendingCommands();

  // Starts the timer that invokes Save (if timer isn't already running).
  void StartSaveTimer();

  // Passes all pending commands to the backend for saving.
  void Save();

  // Returns true if StartSaveTimer() has been called, but a save has not yet
  // occurred.
  bool HasPendingSave() const;

  // Moves the current session to the last session.
  void MoveCurrentSessionToLastSession();

  // Deletes the last session.
  void DeleteLastSession();

  // Uses the backend to load the last session commands from disk. |callback|
  // is called once the data has arrived, and may be called after this is
  // deleted.
  void GetLastSessionCommands(GetCommandsCallback callback);

#if DCHECK_IS_ON()
  // Returns the state of this class and logs for the
  // chrome://internals/session-service debug page. The logs are in reverse
  // order for truncation ease. This value is NOT STABLE - do not rely on it's
  // contents for anything.
  base::Value ToDebugValue() const;
#endif  // DCHECK_IS_ON()

  base::FilePath GetBackendDirectoryForTesting(bool is_encrypted);

 private:
  friend class CommandStorageManagerTestHelper;

  // Called by the backend if writing to the file failed.
  void OnErrorWritingToFile();

  // Returns true if cleartext files should be written.
  bool ShouldWriteCleartextFiles() const;

  // Returns true if encrypted files should be written.
  bool ShouldWriteEncryptedFiles() const;

  // Called when an Encryptor is ready to be used.  start_time is the time when
  // os_crypt_async->GetInstance() was called.
  void OnEncryptorReady(base::TimeTicks start_time,
                        scoped_refptr<os_crypt_async::Encryptor> encryptor);

  const base::FilePath file_path_;
  const SessionType session_type_;

  // TaskRunner all backend tasks are run on. This is a SequencedTaskRunner as
  // all tasks *must* be processed in the order they are scheduled.
  scoped_refptr<base::SequencedTaskRunner> backend_task_runner_;

  // A backend which reads and saves commands in cleartext.
  // TODO(crbug.com/479420496): Remove this backend once transition to
  // `encrypted_backend_` is complete.
  scoped_refptr<CommandStorageBackend> backend_;

  // A backend that stores commands in encrypted form.
  // This backend will eventually replace the cleartext |backend_|; the launch
  // is being tracked in crbug.com/479420496.
  scoped_refptr<CommandStorageBackend> encrypted_backend_;

  // Pending operations waiting for `encrypted_backend_` initialization.
  std::vector<base::OnceClosure> pending_encrypted_ops_;

  // Commands we need to send over to the backend.
  std::vector<std::unique_ptr<SessionCommand>> pending_commands_;

  // Whether the backend file should be recreated the next time we send
  // over the commands.
  bool pending_reset_ = true;

  // The number of commands sent to the backend before doing a reset.
  int commands_since_reset_ = 0;

  raw_ptr<CommandStorageManagerDelegate> delegate_;

#if DCHECK_IS_ON()
  // Used to store debug log entries for this command manager.
  constexpr static int kMaxLogSize = 100;
  std::list<base::Value> written_commands_reverse_debug_log_;
#endif  // DCHECK_IS_ON()

  base::WeakPtrFactory<CommandStorageManager> weak_factory_{this};

  // Used solely for saving after a delay, and not to be used for any other
  // purposes.
  base::WeakPtrFactory<CommandStorageManager> weak_factory_for_timer_{this};
};

}  // namespace sessions

#endif  // COMPONENTS_SESSIONS_CORE_COMMAND_STORAGE_MANAGER_H_
