// Copyright 2020 The Fuchsia 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 <sys/time.h>

#include <filesystem>
#include <string>
#include <vector>

#include "src/developer/debug/shared/stream_buffer.h"
#include "src/developer/shell/mirror/common.h"

#ifndef SRC_DEVELOPER_SHELL_MIRROR_WIRE_FORMAT_H_
#define SRC_DEVELOPER_SHELL_MIRROR_WIRE_FORMAT_H_

namespace shell::mirror {

namespace remote_commands {
// Send this to a service to kill it
constexpr char kQuitCommand[] = "quitquitquit";

// Send this to a service to get the files out.
constexpr char kFilesCommand[] = "hello";
}  // namespace remote_commands

// Basic abstraction for storing an individual file.
// |Name| is the file path.
// |Data| is the contents of the file.
class File {
 public:
  File(const std::filesystem::path& name, std::unique_ptr<char[]> data, int length) {
    buf_ = std::move(data);
    data_ = std::string_view(buf_.get(), length);
    SetName(name);
  }

  File(File&& other)
      : path_(std::move(other.path_)),
        buf_(std::move(other.buf_)),
        data_(buf_.get(), other.data_.length()) {}

  File& operator=(File&& other) {
    buf_ = std::move(other.buf_);
    path_ = std::move(other.path_);
    data_ = std::string_view(buf_.get(), other.data_.length());
    return *this;
  }

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

  size_t NameLength() const { return path_.string().size(); }
  const char* Name() const { return path_.c_str(); }
  void SetName(const std::filesystem::path& path_name) { path_ = path_name; }

  const std::string_view View() const { return data_; }

 private:
  std::filesystem::path path_;   // non-NUL-terminated name.
  std::unique_ptr<char[]> buf_;  // File contents
  std::string_view data_;        // File contents as string_view
};

// Abstraction for storing multiple files.
class Files {
 public:
  // Constructor.  |root_dir| is the root of the directory containing the files,
  // and will not be serialized as part of filenames.
  explicit Files(const std::string& root_dir) : root_dir_(root_dir) {}
  Files() = default;

  // Add the file at location |path| to this list of File objects.  Reads the contents from the
  // filesystem.
  int AddFile(const std::filesystem::path&);

  // Add the file at location |path| to this list of File objects, with the contents from |contents|
  // of length |length|.
  int AddFile(const std::filesystem::path& path, std::unique_ptr<char[]>&& contents, int length);

  // Returns a reference to the list of File objects stored by this object.
  const std::vector<File>& GetFiles() const { return files_; }

  // The following two methods refer to "the data format".  The data format is:
  // uint64_t <number-of-files>
  // <repeat number-of-files times>
  //   uint64_t path-length
  //   char[path-length] path
  //   uint64_t content-length
  //   char[content-length] content

  // Writes the contents of the Files into the std::vector using the data format.
  int DumpFiles(std::vector<char>*);

  // Returns a new instantiation of Files, pulled off of fd in the data format.
  static Files FilesFromFD(int fd, Err* error, struct timeval* timeout = nullptr);

  std::vector<File>::iterator begin() { return files_.begin(); }
  std::vector<File>::const_iterator begin() const { return files_.begin(); }
  std::vector<File>::iterator end() { return files_.end(); }
  std::vector<File>::const_iterator end() const { return files_.end(); }

  size_t size() { return files_.size(); }

  Files(Files&& other) { *this = std::move(other); }

  Files& operator=(Files&& other) {
    root_dir_ = std::move(other.root_dir_);
    files_ = std::move(other.files_);
    return *this;
  }

 private:
  std::filesystem::path root_dir_;
  std::vector<File> files_;
};

}  // namespace shell::mirror

#endif  // SRC_DEVELOPER_SHELL_MIRROR_WIRE_FORMAT_H_
