#pragma once
#include <stdint.h>
#include <string.h>

#include <torch/csrc/profiler/unwind/dwarf_enums.h>
#include <torch/csrc/profiler/unwind/unwind_error.h>

struct Lexer {
  Lexer(void* data, void* base = nullptr)
      : next_((const char*)data), base_((int64_t)base) {}

  template <typename T>
  T read() {
    T result;
    memcpy(&result, next_, sizeof(T));
    next_ += sizeof(T);
    return result;
  }

  // SLEB/ULEB code adapted from LLVM equivalents
  int64_t readSLEB128() {
    int64_t Value = 0;
    unsigned Shift = 0;
    uint8_t Byte;
    do {
      Byte = read<uint8_t>();
      uint64_t Slice = Byte & 0x7f;
      if ((Shift >= 64 && Slice != (Value < 0 ? 0x7f : 0x00)) ||
          (Shift == 63 && Slice != 0 && Slice != 0x7f)) {
        throw UnwindError("sleb128 too big for int64");
      }
      Value |= Slice << Shift;
      Shift += 7;
    } while (Byte >= 128);
    // Sign extend negative numbers if needed.
    if (Shift < 64 && (Byte & 0x40)) {
      Value |= (-1ULL) << Shift;
    }
    return Value;
  }

  uint64_t readULEB128() {
    uint64_t Value = 0;
    unsigned Shift = 0;
    uint8_t p;
    do {
      p = read<uint8_t>();
      uint64_t Slice = p & 0x7f;
      if ((Shift >= 64 && Slice != 0) || Slice << Shift >> Shift != Slice) {
        throw UnwindError("uleb128 too big for uint64");
      }
      Value += Slice << Shift;
      Shift += 7;
    } while (p >= 128);
    return Value;
  }
  const char* readCString() {
    auto result = next_;
    next_ += strlen(next_) + 1;
    return result;
  }
  int64_t readEncoded(uint8_t enc) {
    int64_t r = 0;
    switch (enc & (~DW_EH_PE_indirect & 0xF0)) {
      case DW_EH_PE_absptr:
        break;
      case DW_EH_PE_pcrel:
        r = (int64_t)next_;
        break;
      case DW_EH_PE_datarel:
        r = base_;
        break;
      default:
        throw UnwindError("unknown encoding");
    }
    return r + readEncodedValue(enc);
  }
  int64_t readEncodedOr(uint8_t enc, int64_t orelse) {
    if (enc == DW_EH_PE_omit) {
      return orelse;
    }
    return readEncoded(enc);
  }
  int64_t read4or8Length() {
    int64_t length = read<uint32_t>();
    if (length == 0xFFFFFFFF) {
      length = read<int64_t>();
    }
    return length;
  }
  void* loc() const {
    return (void*)next_;
  }
  Lexer& skip(int64_t bytes) {
    next_ += bytes;
    return *this;
  }
  int64_t readEncodedValue(uint8_t enc) {
    switch (enc & 0xF) {
      case DW_EH_PE_udata2:
        return read<uint16_t>();
      case DW_EH_PE_sdata2:
        return read<int16_t>();
      case DW_EH_PE_udata4:
        return read<uint32_t>();
      case DW_EH_PE_sdata4:
        return read<int32_t>();
      case DW_EH_PE_udata8:
        return read<uint64_t>();
      case DW_EH_PE_sdata8:
        return read<int64_t>();
      case DW_EH_PE_uleb128:
        return readULEB128();
      case DW_EH_PE_sleb128:
        return readSLEB128();
      default:
        throw UnwindError("not implemented");
    }
  }

 private:
  const char* next_;
  int64_t base_;
};
