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

#include "remoting/host/file_transfer/rtc_log_file_operations.h"

#include <algorithm>

#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/notreached.h"
#include "base/strings/stringprintf.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "remoting/protocol/connection_to_client.h"
#include "remoting/protocol/file_transfer_helpers.h"
#include "remoting/protocol/webrtc_event_log_data.h"

namespace remoting {

namespace {

class RtcLogFileReader : public FileOperations::Reader {
 public:
  explicit RtcLogFileReader(protocol::ConnectionToClient* connection);
  ~RtcLogFileReader() override;
  RtcLogFileReader(const RtcLogFileReader&) = delete;
  RtcLogFileReader& operator=(const RtcLogFileReader&) = delete;

  // FileOperations::Reader interface.
  void Open(OpenCallback callback) override;
  void ReadChunk(std::size_t size, ReadCallback callback) override;
  const base::FilePath& filename() const override;
  std::uint64_t size() const override;
  FileOperations::State state() const override;

 private:
  using LogSection = protocol::WebrtcEventLogData::LogSection;

  void DoOpen(OpenCallback callback);
  void DoReadChunk(std::size_t size, ReadCallback callback);

  // Reads up to |maximum_to_read| bytes from the event log, and appends them
  // to |output| and returns the number of bytes appended. This only reads from
  // a single LogSection, and it takes care of advancing to the next LogSection
  // if the end is reached. Returns 0 if there is no more data to be read.
  int ReadPartially(int maximum_to_read, std::vector<std::uint8_t>& output);

  raw_ptr<protocol::ConnectionToClient> connection_;
  base::FilePath filename_;
  base::circular_deque<LogSection> data_;
  FileOperations::State state_ = FileOperations::kCreated;

  // Points to the current LogSection being read from, or data_.end() if
  // reading is finished.
  base::circular_deque<LogSection>::const_iterator current_log_section_;

  // Points to the current read position inside |current_log_section_| or is
  // undefined if current_log_section_ == data_.end(). Note that each
  // LogSection of |data_| is always non-empty. If the end of a LogSection is
  // reached, |current_log_section_| will advance to the next section, and this
  // position will be reset to the beginning of the new section.
  LogSection::const_iterator current_position_;

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

// This class simply returns a protocol error if the client attempts to upload
// a file to this FileOperations implementation. The RTC log is download-only,
// and the upload code-path is never intended to be executed. This class is
// intended to gracefully return an error instead of crashing the host process.
class RtcLogFileWriter : public FileOperations::Writer {
 public:
  RtcLogFileWriter() = default;
  ~RtcLogFileWriter() override = default;
  RtcLogFileWriter(const RtcLogFileWriter&) = delete;
  RtcLogFileWriter& operator=(const RtcLogFileWriter&) = delete;

  // FileOperations::Writer interface.
  void Open(const base::FilePath& filename, Callback callback) override;
  void WriteChunk(std::vector<std::uint8_t> data, Callback callback) override;
  void Close(Callback callback) override;
  FileOperations::State state() const override;
};

RtcLogFileReader::RtcLogFileReader(protocol::ConnectionToClient* connection)
    : connection_(connection) {}
RtcLogFileReader::~RtcLogFileReader() = default;

void RtcLogFileReader::Open(OpenCallback callback) {
  state_ = FileOperations::kBusy;
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&RtcLogFileReader::DoOpen, weak_factory_.GetWeakPtr(),
                     std::move(callback)));
}

void RtcLogFileReader::ReadChunk(std::size_t size, ReadCallback callback) {
  state_ = FileOperations::kBusy;
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&RtcLogFileReader::DoReadChunk, weak_factory_.GetWeakPtr(),
                     size, std::move(callback)));
}

const base::FilePath& RtcLogFileReader::filename() const {
  return filename_;
}

std::uint64_t RtcLogFileReader::size() const {
  std::uint64_t result = 0;
  for (const auto& section : data_) {
    result += section.size();
  }
  return result;
}

FileOperations::State RtcLogFileReader::state() const {
  return state_;
}

void RtcLogFileReader::DoOpen(OpenCallback callback) {
  protocol::WebrtcEventLogData* rtc_log = connection_->rtc_event_log();
  if (!rtc_log) {
    // This is a protocol error because RTC log is only supported for WebRTC
    // connections.
    state_ = FileOperations::kFailed;
    std::move(callback).Run(protocol::MakeFileTransferError(
        FROM_HERE, protocol::FileTransfer_Error_Type_PROTOCOL_ERROR));
    return;
  }

  base::Time::Exploded exploded;
  base::Time::NowFromSystemTime().LocalExplode(&exploded);
  std::string filename = base::StringPrintf(
      "host-rtc-log-%d-%d-%d_%d-%d-%d", exploded.year, exploded.month,
      exploded.day_of_month, exploded.hour, exploded.minute, exploded.second);
  filename_ = base::FilePath::FromUTF8Unsafe(filename);

  data_ = rtc_log->TakeLogData();
  current_log_section_ = data_.begin();
  if (!data_.empty()) {
    current_position_ = (*current_log_section_).begin();
  }

  state_ = FileOperations::kReady;
  std::move(callback).Run(kSuccessTag);
}

void RtcLogFileReader::DoReadChunk(std::size_t size, ReadCallback callback) {
  std::vector<std::uint8_t> result;
  int bytes_read;
  int bytes_remaining = static_cast<int>(size);
  while (bytes_remaining &&
         (bytes_read = ReadPartially(bytes_remaining, result)) > 0) {
    bytes_remaining -= bytes_read;
  }

  state_ = result.empty() ? FileOperations::kComplete : FileOperations::kReady;
  std::move(callback).Run(result);
}

int RtcLogFileReader::ReadPartially(int maximum_to_read,
                                    std::vector<std::uint8_t>& output) {
  if (data_.empty()) {
    return 0;
  }

  if (current_log_section_ == data_.end()) {
    return 0;
  }

  const auto& section = *current_log_section_;
  DCHECK(section.begin() <= current_position_);
  DCHECK(current_position_ < section.end());

  int remaining_in_section = section.end() - current_position_;
  int read_amount = std::min(remaining_in_section, maximum_to_read);

  output.insert(output.end(), current_position_,
                current_position_ + read_amount);
  current_position_ += read_amount;

  if (current_position_ == section.end()) {
    // Advance to beginning of next LogSection.
    current_log_section_++;
    if (current_log_section_ != data_.end()) {
      current_position_ = (*current_log_section_).begin();
    }
  }

  return read_amount;
}

void RtcLogFileWriter::Open(const base::FilePath& filename, Callback callback) {
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(
          std::move(callback),
          protocol::MakeFileTransferError(
              FROM_HERE, protocol::FileTransfer_Error_Type_PROTOCOL_ERROR)));
}

void RtcLogFileWriter::WriteChunk(std::vector<std::uint8_t> data,
                                  Callback callback) {
  NOTREACHED();
}

void RtcLogFileWriter::Close(Callback callback) {
  NOTREACHED();
}

FileOperations::State RtcLogFileWriter::state() const {
  return FileOperations::State::kFailed;
}

}  // namespace

RtcLogFileOperations::RtcLogFileOperations(
    protocol::ConnectionToClient* connection)
    : connection_(connection) {}

RtcLogFileOperations::~RtcLogFileOperations() = default;

std::unique_ptr<FileOperations::Reader> RtcLogFileOperations::CreateReader() {
  return std::make_unique<RtcLogFileReader>(connection_);
}

std::unique_ptr<FileOperations::Writer> RtcLogFileOperations::CreateWriter() {
  return std::make_unique<RtcLogFileWriter>();
}

}  // namespace remoting