/*
 * Copyright 2020 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_INTERP_ISTREAM_H_
#define WABT_INTERP_ISTREAM_H_

#include <cstdint>
#include <string>
#include <vector>

#include "wabt/common.h"
#include "wabt/opcode.h"
#include "wabt/stream.h"

namespace wabt {
namespace interp {

using u8 = uint8_t;
using u16 = uint16_t;
using u32 = uint32_t;
using u64 = uint64_t;
using f32 = float;
using f64 = double;

using Buffer = std::vector<u8>;

using ValueType = wabt::Type;

// Group instructions based on their immediates their operands. This way we can
// simplify instruction decoding, disassembling, and tracing. There is an
// example of an instruction that uses this encoding on the right.
enum class InstrKind {
  Imm_0_Op_0,                  // Nop
  Imm_0_Op_1,                  // i32.eqz
  Imm_0_Op_2,                  // i32.add
  Imm_0_Op_3,                  // select
  Imm_Jump_Op_0,               // br
  Imm_Jump_Op_1,               // br_if
  Imm_Index_Op_0,              // global.get
  Imm_Index_Op_1,              // global.set
  Imm_Index_Op_2,              // table.set
  Imm_Index_Op_3,              // memory.fill
  Imm_Index_Op_N,              // call
  Imm_Index_Index_Op_3,        // memory.init
  Imm_Index_Index_Op_N,        // call_indirect
  Imm_Index_Offset_Op_1,       // i32.load
  Imm_Index_Offset_Op_2,       // i32.store
  Imm_Index_Offset_Op_3,       // i32.atomic.rmw.cmpxchg
  Imm_Index_Offset_Lane_Op_2,  // v128.load8_lane
  Imm_I32_Op_0,                // i32.const
  Imm_I64_Op_0,                // i64.const
  Imm_F32_Op_0,                // f32.const
  Imm_F64_Op_0,                // f64.const
  Imm_I32_I32_Op_0,            // drop_keep
  Imm_I8_Op_1,                 // i32x4.extract_lane
  Imm_I8_Op_2,                 // i32x4.replace_lane
  Imm_V128_Op_0,               // v128.const
  Imm_V128_Op_2,               // i8x16.shuffle
};

struct Instr {
  Opcode op;
  InstrKind kind;
  union {
    u8 imm_u8;
    u32 imm_u32;
    f32 imm_f32;
    u64 imm_u64;
    f64 imm_f64;
    v128 imm_v128;
    struct {
      u32 fst, snd;
    } imm_u32x2;
    struct {
      u32 fst, snd;
      u8 idx;
    } imm_u32x2_u8;
  };
};

class Istream {
 public:
  using SerializedOpcode = u32;  // TODO: change to u16
  using Offset = u32;
  static constexpr Offset kInvalidOffset = ~0;
  // Each br_table entry is made up of three instructions:
  //
  //   interp_drop_keep $drop $keep
  //   interp_catch_drop $catches
  //   br $label
  //
  // Each opcode is a SerializedOpcode, and each immediate is a u32.
  static constexpr Offset kBrTableEntrySize =
      sizeof(SerializedOpcode) * 3 + 4 * sizeof(u32);

  // Emit API.
  void Emit(u32);
  void Emit(Opcode::Enum);
  void Emit(Opcode::Enum, u8);
  void Emit(Opcode::Enum, u32);
  void Emit(Opcode::Enum, u64);
  void Emit(Opcode::Enum, v128);
  void Emit(Opcode::Enum, u32, u32);
  void Emit(Opcode::Enum, u32, u32, u8);
  void EmitDropKeep(u32 drop, u32 keep);
  void EmitCatchDrop(u32 drop);

  Offset EmitFixupU32();
  void ResolveFixupU32(Offset);

  Offset end() const;

  // Read API.
  Instr Read(Offset*) const;

  // Disassemble/Trace API.
  // TODO separate out disassembly/tracing?
  struct TraceSource {
    virtual ~TraceSource() {}
    // Whatever content should go before the instruction on each line, e.g. the
    // call stack size, value stack size, and istream offset.
    virtual std::string Header(Offset) = 0;
    virtual std::string Pick(Index, Instr) = 0;
  };

  struct DisassemblySource : TraceSource {
    std::string Header(Offset) override;
    std::string Pick(Index, Instr) override;
  };

  void Disassemble(Stream*) const;
  Offset Disassemble(Stream*, Offset) const;
  void Disassemble(Stream*, Offset from, Offset to) const;

  Offset Trace(Stream*, Offset, TraceSource*) const;

 private:
  template <typename T>
  void WABT_VECTORCALL EmitAt(Offset, T val);
  template <typename T>
  void WABT_VECTORCALL EmitInternal(T val);

  template <typename T>
  T WABT_VECTORCALL ReadAt(Offset*) const;

  Buffer data_;
};

}  // namespace interp
}  // namespace wabt

#endif  // WABT_INTERP_ISTREAM_H_
