/*
 * Copyright 2016 WebAssembly Community Group participants
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef WABT_STREAM_H_
#define WABT_STREAM_H_

#include <cassert>
#include <memory>
#include <vector>

#include "wabt/common.h"

namespace wabt {

/* whether to display the ASCII characters in the debug output for
 * write_memory */
enum class PrintChars {
  No = 0,
  Yes = 1,
};

class Stream {
 public:
  explicit Stream(Stream* log_stream = nullptr);
  virtual ~Stream() = default;

  size_t offset() { return offset_; }
  Result result() { return result_; }

  void set_log_stream(Stream* stream) {
    assert(stream);
    log_stream_ = stream;
  }

  Stream& log_stream() {
    assert(log_stream_);
    return *log_stream_;
  }

  bool has_log_stream() const { return log_stream_ != nullptr; }

  void ClearOffset() { offset_ = 0; }
  void AddOffset(ssize_t delta);

  void WriteData(const void* src,
                 size_t size,
                 const char* desc = nullptr,
                 PrintChars = PrintChars::No);

  template <typename T>
  void WriteData(const std::vector<T> src,
                 const char* desc,
                 PrintChars print_chars = PrintChars::No) {
    if (!src.empty()) {
      WriteData(src.data(), src.size() * sizeof(T), desc, print_chars);
    }
  }

  void WriteDataAt(size_t offset,
                   const void* src,
                   size_t size,
                   const char* desc = nullptr,
                   PrintChars = PrintChars::No);

  void MoveData(size_t dst_offset, size_t src_offset, size_t size);

  void Truncate(size_t size);

  void WABT_PRINTF_FORMAT(2, 3) Writef(const char* format, ...);

  // Specified as uint32_t instead of uint8_t so we can check if the value
  // given is in range before wrapping.
  void WriteU8(uint32_t value,
               const char* desc = nullptr,
               PrintChars print_chars = PrintChars::No) {
    assert(value <= UINT8_MAX);
    Write(static_cast<uint8_t>(value), desc, print_chars);
  }
  void WriteU32(uint32_t value,
                const char* desc = nullptr,
                PrintChars print_chars = PrintChars::No) {
    Write(value, desc, print_chars);
  }
  void WriteU64(uint64_t value,
                const char* desc = nullptr,
                PrintChars print_chars = PrintChars::No) {
    Write(value, desc, print_chars);
  }
  void WriteU128(v128 value,
                 const char* desc = nullptr,
                 PrintChars print_chars = PrintChars::No) {
    Write(value, desc, print_chars);
  }

  void WriteChar(char c,
                 const char* desc = nullptr,
                 PrintChars print_chars = PrintChars::No) {
    WriteU8(static_cast<unsigned char>(c), desc, print_chars);
  }

  // Dump memory as text, similar to the xxd format.
  void WriteMemoryDump(const void* start,
                       size_t size,
                       size_t offset = 0,
                       PrintChars print_chars = PrintChars::No,
                       const char* prefix = nullptr,
                       const char* desc = nullptr);

  // Convenience functions for writing enums.
  template <typename T>
  void WriteU8Enum(T value,
                   const char* desc = nullptr,
                   PrintChars print_chars = PrintChars::No) {
    WriteU8(static_cast<uint32_t>(value), desc, print_chars);
  }

  virtual void Flush() {}

 protected:
  virtual Result WriteDataImpl(size_t offset,
                               const void* data,
                               size_t size) = 0;
  virtual Result MoveDataImpl(size_t dst_offset,
                              size_t src_offset,
                              size_t size) = 0;
  virtual Result TruncateImpl(size_t size) = 0;

 private:
  template <typename T>
  void Write(const T& data, const char* desc, PrintChars print_chars) {
#if WABT_BIG_ENDIAN
    char tmp[sizeof(T)];
    memcpy(tmp, &data, sizeof(tmp));
    SwapBytesSized(tmp, sizeof(tmp));
    WriteData(tmp, sizeof(tmp), desc, print_chars);
#else
    WriteData(&data, sizeof(data), desc, print_chars);
#endif
  }

  size_t offset_;
  Result result_;
  // Not owned. If non-null, log all writes to this stream.
  Stream* log_stream_;
};

struct OutputBuffer {
  Result WriteToFile(std::string_view filename) const;
  Result WriteToStdout() const;

  void clear() { data.clear(); }
  size_t size() const { return data.size(); }

  std::vector<uint8_t> data;
};

class MemoryStream : public Stream {
 public:
  WABT_DISALLOW_COPY_AND_ASSIGN(MemoryStream);
  MemoryStream(MemoryStream&&) = default;
  explicit MemoryStream(Stream* log_stream = nullptr);
  explicit MemoryStream(std::unique_ptr<OutputBuffer>&&,
                        Stream* log_stream = nullptr);

  OutputBuffer& output_buffer() { return *buf_; }
  std::unique_ptr<OutputBuffer> ReleaseOutputBuffer();

  void Clear();

  Result WriteToFile(std::string_view filename) {
    return buf_->WriteToFile(filename);
  }

 protected:
  Result WriteDataImpl(size_t offset, const void* data, size_t size) override;
  Result MoveDataImpl(size_t dst_offset,
                      size_t src_offset,
                      size_t size) override;
  Result TruncateImpl(size_t size) override;

 private:
  std::unique_ptr<OutputBuffer> buf_;
};

class FileStream : public Stream {
 public:
  WABT_DISALLOW_COPY_AND_ASSIGN(FileStream);
  explicit FileStream(std::string_view filename, Stream* log_stream = nullptr);
  explicit FileStream(FILE*, Stream* log_stream = nullptr);
  FileStream(FileStream&&);
  FileStream& operator=(FileStream&&);
  ~FileStream() override;

  static std::unique_ptr<FileStream> CreateStdout();
  static std::unique_ptr<FileStream> CreateStderr();

  bool is_open() const { return file_ != nullptr; }

  void Flush() override;

 protected:
  Result WriteDataImpl(size_t offset, const void* data, size_t size) override;
  Result MoveDataImpl(size_t dst_offset,
                      size_t src_offset,
                      size_t size) override;
  Result TruncateImpl(size_t size) override;

 private:
  FILE* file_;
  size_t offset_;
  bool should_close_;
};

}  // namespace wabt

#endif /* WABT_STREAM_H_ */
