/*
 * 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_COMMON_H_
#define WABT_COMMON_H_

#include <algorithm>
#include <cassert>
#include <cstdarg>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>

#include "wabt/config.h"

#include "wabt/base-types.h"
#include "wabt/result.h"
#include "wabt/string-format.h"
#include "wabt/type.h"

#define WABT_FATAL(...) fprintf(stderr, __VA_ARGS__), exit(1)
#define WABT_ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))

#define WABT_USE(x) static_cast<void>(x)

// 64k
#define WABT_PAGE_SIZE 0x10000
// # of pages that fit in 32-bit address space
#define WABT_MAX_PAGES32 0x10000
// # of pages that fit in 64-bit address space
#define WABT_MAX_PAGES64 0x1000000000000
#define WABT_BYTES_TO_PAGES(x) ((x) >> 16)
#define WABT_ALIGN_UP_TO_PAGE(x) \
  (((x) + WABT_PAGE_SIZE - 1) & ~(WABT_PAGE_SIZE - 1))

#define WABT_ENUM_COUNT(name) \
  (static_cast<int>(name::Last) - static_cast<int>(name::First) + 1)

#define WABT_DISALLOW_COPY_AND_ASSIGN(type) \
  type(const type&) = delete;               \
  type& operator=(const type&) = delete;

#if WITH_EXCEPTIONS
#define WABT_TRY try {
#define WABT_CATCH_BAD_ALLOC \
  }                          \
  catch (std::bad_alloc&) {  \
  }
#define WABT_CATCH_BAD_ALLOC_AND_EXIT           \
  }                                             \
  catch (std::bad_alloc&) {                     \
    WABT_FATAL("Memory allocation failure.\n"); \
  }
#else
#define WABT_TRY
#define WABT_CATCH_BAD_ALLOC
#define WABT_CATCH_BAD_ALLOC_AND_EXIT
#endif

#define PRIindex "u"
#define PRIaddress PRIu64
#define PRIoffset PRIzx

namespace wabt {
inline void MemcpyEndianAware(void* dst,
                              const void* src,
                              size_t dsize,
                              size_t ssize,
                              size_t doff,
                              size_t soff,
                              size_t len) {
#if WABT_BIG_ENDIAN
  memcpy(static_cast<char*>(dst) + (dsize) - (len) - (doff),
         static_cast<const char*>(src) + (ssize) - (len) - (soff), (len));
#else
  memcpy(static_cast<char*>(dst) + (doff),
         static_cast<const char*>(src) + (soff), (len));
#endif
}
}

struct v128 {
  v128() = default;
  v128(uint32_t x0, uint32_t x1, uint32_t x2, uint32_t x3) {
    set_u32(0, x0);
    set_u32(1, x1);
    set_u32(2, x2);
    set_u32(3, x3);
  }

  bool operator==(const v128& other) const {
    return std::equal(std::begin(v), std::end(v), std::begin(other.v));
  }
  bool operator!=(const v128& other) const { return !(*this == other); }

  uint8_t u8(int lane) const { return To<uint8_t>(lane); }
  uint16_t u16(int lane) const { return To<uint16_t>(lane); }
  uint32_t u32(int lane) const { return To<uint32_t>(lane); }
  uint64_t u64(int lane) const { return To<uint64_t>(lane); }
  uint32_t f32_bits(int lane) const { return To<uint32_t>(lane); }
  uint64_t f64_bits(int lane) const { return To<uint64_t>(lane); }

  void set_u8(int lane, uint8_t x) { return From<uint8_t>(lane, x); }
  void set_u16(int lane, uint16_t x) { return From<uint16_t>(lane, x); }
  void set_u32(int lane, uint32_t x) { return From<uint32_t>(lane, x); }
  void set_u64(int lane, uint64_t x) { return From<uint64_t>(lane, x); }
  void set_f32_bits(int lane, uint32_t x) { return From<uint32_t>(lane, x); }
  void set_f64_bits(int lane, uint64_t x) { return From<uint64_t>(lane, x); }

  bool is_zero() const {
    return std::all_of(std::begin(v), std::end(v),
                       [](uint8_t x) { return x == 0; });
  }
  void set_zero() { std::fill(std::begin(v), std::end(v), 0); }

  template <typename T>
  T To(int lane) const {
    static_assert(sizeof(T) <= sizeof(v), "Invalid cast!");
    assert((lane + 1) * sizeof(T) <= sizeof(v));
    T result;
    wabt::MemcpyEndianAware(&result, v, sizeof(result), sizeof(v), 0,
                            lane * sizeof(T), sizeof(result));
    return result;
  }

  template <typename T>
  void From(int lane, T data) {
    static_assert(sizeof(T) <= sizeof(v), "Invalid cast!");
    assert((lane + 1) * sizeof(T) <= sizeof(v));
    wabt::MemcpyEndianAware(v, &data, sizeof(v), sizeof(data), lane * sizeof(T),
                            0, sizeof(data));
  }

  uint8_t v[16];
};

namespace wabt {

template <typename Dst, typename Src>
Dst WABT_VECTORCALL Bitcast(Src&& value) {
  static_assert(sizeof(Src) == sizeof(Dst), "Bitcast sizes must match.");
  Dst result;
  memcpy(&result, &value, sizeof(result));
  return result;
}

template <typename T>
void ZeroMemory(T& v) {
  WABT_STATIC_ASSERT(std::is_pod<T>::value);
  memset(&v, 0, sizeof(v));
}

// Placement construct
template <typename T, typename... Args>
void Construct(T& placement, Args&&... args) {
  new (&placement) T(std::forward<Args>(args)...);
}

// Placement destruct
template <typename T>
void Destruct(T& placement) {
  placement.~T();
}

enum class LabelType {
  Func,
  InitExpr,
  Block,
  Loop,
  If,
  Else,
  Try,
  Catch,

  First = Func,
  Last = Catch,
};
constexpr int kLabelTypeCount = WABT_ENUM_COUNT(LabelType);

struct Location {
  enum class Type {
    Text,
    Binary,
  };

  Location() : line(0), first_column(0), last_column(0) {}
  Location(std::string_view filename,
           int line,
           int first_column,
           int last_column)
      : filename(filename),
        line(line),
        first_column(first_column),
        last_column(last_column) {}
  explicit Location(size_t offset) : offset(offset) {}

  std::string_view filename;
  union {
    // For text files.
    struct {
      int line;
      int first_column;
      int last_column;
    };
    // For binary files.
    struct {
      size_t offset;
    };
  };
};

enum class SegmentKind {
  Active,
  Passive,
  Declared,
};

// Used in test asserts for special expected values "nan:canonical" and
// "nan:arithmetic"
enum class ExpectedNan {
  None,
  Canonical,
  Arithmetic,
};

// Matches binary format, do not change.
enum SegmentFlags : uint8_t {
  SegFlagsNone = 0,
  SegPassive = 1,        // bit 0: Is passive
  SegExplicitIndex = 2,  // bit 1: Has explict index (Implies table 0 if absent)
  SegDeclared = 3,       // Only used for declared segments
  SegUseElemExprs = 4,   // bit 2: Is elemexpr (Or else index sequence)

  SegFlagMax = (SegUseElemExprs << 1) - 1,  // All bits set.
};

enum class RelocType {
  FuncIndexLEB = 0,       // e.g. Immediate of call instruction
  TableIndexSLEB = 1,     // e.g. Loading address of function
  TableIndexI32 = 2,      // e.g. Function address in DATA
  MemoryAddressLEB = 3,   // e.g. Memory address in load/store offset immediate
  MemoryAddressSLEB = 4,  // e.g. Memory address in i32.const
  MemoryAddressI32 = 5,   // e.g. Memory address in DATA
  TypeIndexLEB = 6,       // e.g. Immediate type in call_indirect
  GlobalIndexLEB = 7,     // e.g. Immediate of global.get inst
  FunctionOffsetI32 = 8,  // e.g. Code offset in DWARF metadata
  SectionOffsetI32 = 9,   // e.g. Section offset in DWARF metadata
  TagIndexLEB = 10,       // Used in throw instructions
  MemoryAddressRelSLEB = 11,  // In PIC code, addr relative to __memory_base
  TableIndexRelSLEB = 12,   // In PIC code, table index relative to __table_base
  GlobalIndexI32 = 13,      // e.g. Global index in data (e.g. DWARF)
  MemoryAddressLEB64 = 14,  // Memory64: Like MemoryAddressLEB
  MemoryAddressSLEB64 = 15,     // Memory64: Like MemoryAddressSLEB
  MemoryAddressI64 = 16,        // Memory64: Like MemoryAddressI32
  MemoryAddressRelSLEB64 = 17,  // Memory64: Like MemoryAddressRelSLEB
  TableIndexSLEB64 = 18,        // Memory64: Like TableIndexSLEB
  TableIndexI64 = 19,           // Memory64: Like TableIndexI32
  TableNumberLEB = 20,          // e.g. Immediate of table.get
  MemoryAddressTLSSLEB = 21,    // Address relative to __tls_base
  MemoryAddressTLSI32 = 22,     // Address relative to __tls_base

  First = FuncIndexLEB,
  Last = MemoryAddressTLSI32,
};
constexpr int kRelocTypeCount = WABT_ENUM_COUNT(RelocType);

struct Reloc {
  Reloc(RelocType, size_t offset, Index index, int32_t addend = 0);

  RelocType type;
  size_t offset;
  Index index;
  int32_t addend;
};

enum class LinkingEntryType {
  SegmentInfo = 5,
  InitFunctions = 6,
  ComdatInfo = 7,
  SymbolTable = 8,
};

enum class DylinkEntryType {
  MemInfo = 1,
  Needed = 2,
  ExportInfo = 3,
  ImportInfo = 4,
};

enum class SymbolType {
  Function = 0,
  Data = 1,
  Global = 2,
  Section = 3,
  Tag = 4,
  Table = 5,
};

enum class ComdatType {
  Data = 0x0,
  Function = 0x1,
};

#define WABT_SYMBOL_MASK_VISIBILITY 0x4
#define WABT_SYMBOL_MASK_BINDING 0x3
#define WABT_SYMBOL_FLAG_UNDEFINED 0x10
#define WABT_SYMBOL_FLAG_EXPORTED 0x20
#define WABT_SYMBOL_FLAG_EXPLICIT_NAME 0x40
#define WABT_SYMBOL_FLAG_NO_STRIP 0x80
#define WABT_SYMBOL_FLAG_TLS 0x100
#define WABT_SYMBOL_FLAG_MAX 0x1ff

#define WABT_SEGMENT_FLAG_STRINGS 0x1
#define WABT_SEGMENT_FLAG_TLS 0x2
#define WABT_SEGMENT_FLAG_MAX 0xff

enum class SymbolVisibility {
  Default = 0,
  Hidden = 4,
};

enum class SymbolBinding {
  Global = 0,
  Weak = 1,
  Local = 2,
};

/* matches binary format, do not change */
enum class ExternalKind {
  Func = 0,
  Table = 1,
  Memory = 2,
  Global = 3,
  Tag = 4,

  First = Func,
  Last = Tag,
};
constexpr int kExternalKindCount = WABT_ENUM_COUNT(ExternalKind);

struct Limits {
  Limits() = default;
  explicit Limits(uint64_t initial) : initial(initial) {}
  Limits(uint64_t initial, uint64_t max)
      : initial(initial), max(max), has_max(true) {}
  Limits(uint64_t initial, uint64_t max, bool is_shared)
      : initial(initial), max(max), has_max(true), is_shared(is_shared) {}
  Limits(uint64_t initial, uint64_t max, bool is_shared, bool is_64)
      : initial(initial),
        max(max),
        has_max(true),
        is_shared(is_shared),
        is_64(is_64) {}
  Type IndexType() const { return is_64 ? Type::I64 : Type::I32; }

  uint64_t initial = 0;
  uint64_t max = 0;
  bool has_max = false;
  bool is_shared = false;
  bool is_64 = false;
};

enum { WABT_USE_NATURAL_ALIGNMENT = 0xFFFFFFFFFFFFFFFF };

Result ReadFile(std::string_view filename, std::vector<uint8_t>* out_data);

void InitStdio();

/* external kind */

extern const char* g_kind_name[];

static inline const char* GetKindName(ExternalKind kind) {
  return static_cast<size_t>(kind) < kExternalKindCount
             ? g_kind_name[static_cast<size_t>(kind)]
             : "<error_kind>";
}

/* reloc */

extern const char* g_reloc_type_name[];

static inline const char* GetRelocTypeName(RelocType reloc) {
  return static_cast<size_t>(reloc) < kRelocTypeCount
             ? g_reloc_type_name[static_cast<size_t>(reloc)]
             : "<error_reloc_type>";
}

/* symbol */

static inline const char* GetSymbolTypeName(SymbolType type) {
  switch (type) {
    case SymbolType::Function:
      return "func";
    case SymbolType::Global:
      return "global";
    case SymbolType::Data:
      return "data";
    case SymbolType::Section:
      return "section";
    case SymbolType::Tag:
      return "tag";
    case SymbolType::Table:
      return "table";
    default:
      return "<error_symbol_type>";
  }
}

template <typename T>
void ConvertBackslashToSlash(T begin, T end) {
  std::replace(begin, end, '\\', '/');
}

inline void ConvertBackslashToSlash(char* s, size_t length) {
  ConvertBackslashToSlash(s, s + length);
}

inline void ConvertBackslashToSlash(char* s) {
  ConvertBackslashToSlash(s, strlen(s));
}

inline void ConvertBackslashToSlash(std::string* s) {
  ConvertBackslashToSlash(s->begin(), s->end());
}

inline void SwapBytesSized(void* addr, size_t size) {
  auto bytes = static_cast<uint8_t*>(addr);
  for (size_t i = 0; i < size / 2; i++) {
    std::swap(bytes[i], bytes[size - 1 - i]);
  }
}

}  // namespace wabt

#endif  // WABT_COMMON_H_
