/*  SPDX-License-Identifier: GPL-2.0-or-later */
/*!********************************************************************

  Audacity: A Digital Audio Editor

  RemoteProjectSnapshot.h

  Dmitry Vedenko

**********************************************************************/
#pragma once

#include <atomic>
#include <chrono>
#include <condition_variable>
#include <future>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <unordered_set>

#include "CloudSyncDTO.h"
#include "NetworkUtils.h"

namespace audacity::network_manager
{
class IResponse;
using ResponsePtr = std::shared_ptr<IResponse>;
} // namespace audacity::network_manager

namespace audacity::cloud::audiocom::sync
{

struct RemoteProjectSnapshotState final
{
   ResponseResult Result;

   int64_t BlocksDownloaded { 0 };
   int64_t BlocksTotal { 0 };

   bool ProjectDownloaded { false };

   bool IsComplete() const noexcept;
};

using RemoteProjectSnapshotStateCallback =
   std::function<void(RemoteProjectSnapshotState)>;

class RemoteProjectSnapshot final
   : public std::enable_shared_from_this<RemoteProjectSnapshot>
{
   struct Tag
   {
   };

public:
   RemoteProjectSnapshot(
      Tag, ProjectInfo projectInfo, SnapshotInfo snapshotInfo, std::string path,
      RemoteProjectSnapshotStateCallback callback, bool downloadDetached);

   ~RemoteProjectSnapshot();

   RemoteProjectSnapshot(const RemoteProjectSnapshot&)            = delete;
   RemoteProjectSnapshot& operator=(const RemoteProjectSnapshot&) = delete;
   RemoteProjectSnapshot(RemoteProjectSnapshot&&)                 = delete;
   RemoteProjectSnapshot& operator=(RemoteProjectSnapshot&&)      = delete;

   static std::shared_ptr<RemoteProjectSnapshot> Sync(
      ProjectInfo projectInfo, SnapshotInfo snapshotInfo, std::string path,
      RemoteProjectSnapshotStateCallback callback, bool downloadDetached);

   void Cancel();

   TransferStats GetTransferStats() const;

   std::string_view GetProjectId() const;

private:
   enum class State
   {
      Downloading,
      Cancelled,
      Failed,
      Succeeded
   };

   using SuccessHandler =
      std::function<void(audacity::network_manager::ResponsePtr)>;

   std::string AttachOriginalDB();

   void SetupBlocksCopy(
      const std::string& dbName, std::unordered_set<std::string> blocks);

   std::unordered_set<std::string>
   CalculateKnownBlocks(const std::string& attachedDbName) const;

   void DoCancel();

   void
   DownloadBlob(std::string url, SuccessHandler onSuccess, int retries);

   void
   OnProjectBlobDownloaded(audacity::network_manager::ResponsePtr response);
   void OnBlockDownloaded(
      std::string blockHash, audacity::network_manager::ResponsePtr response);

   void OnFailure(ResponseResult result);
   void RemoveResponse(audacity::network_manager::IResponse* response);

   void MarkProjectInDB(bool successfulDownload);

   void ReportProgress();

   void StartSync();
   bool InProgress() const;
   void RequestsThread();

   void SetState(State state);

   void CleanupOrphanBlocks();

   const std::string mSnapshotDBName;
   const ProjectInfo mProjectInfo;
   const SnapshotInfo mSnapshotInfo;
   const std::string mPath;
   RemoteProjectSnapshotStateCallback mCallback;

   std::vector<std::string> mAttachedDBNames;

   std::atomic<State> mState { State::Downloading };

   using Clock     = std::chrono::steady_clock;
   using TimePoint = Clock::time_point;

   TimePoint mStartTime { Clock::now() };
   TimePoint mEndTime;

   std::thread mRequestsThread;
   std::mutex mRequestsMutex;
   std::condition_variable mRequestsCV;

   std::vector<std::pair<std::string, SuccessHandler>> mRequests;

   int mRequestsInProgress { 0 };
   size_t mNextRequestIndex { 0 };

   std::mutex mResponsesMutex;
   std::vector<std::shared_ptr<audacity::network_manager::IResponse>>
      mResponses;

   std::atomic<int64_t> mDownloadedBlocks { 0 };
   std::atomic<int64_t> mCopiedBlocks { 0 };
   std::atomic<int64_t> mDownloadedBytes { 0 };

   int64_t mMissingBlocks { 0 };

   std::optional<std::future<bool>> mCopyBlocksFuture;

   std::atomic<bool> mProjectDownloaded { false };

   bool mNothingToDo { false };
   const bool mDownloadDetached { false };

}; // class RemoteProjectSnapshot
} // namespace audacity::cloud::audiocom::sync
