#pragma once

#include "spirv_include.h"

namespace dxvk {
  
  /**
   * \brief SPIR-V instruction
   * 
   * Helps parsing a single instruction, providing
   * access to the op code, instruction length and
   * instruction arguments.
   */
  class SpirvInstruction {
    
  public:
    
    SpirvInstruction() { }
    SpirvInstruction(uint32_t* code, uint32_t offset, uint32_t length)
    : m_code(code), m_offset(offset), m_length(length) { }
    
    /**
     * \brief SPIR-V Op code
     * \returns The op code
     */
    spv::Op opCode() const {
      return static_cast<spv::Op>(
        this->arg(0) & spv::OpCodeMask);
    }
    
    /**
     * \brief Instruction length
     * \returns Number of DWORDs
     */
    uint32_t length() const {
      return this->arg(0) >> spv::WordCountShift;
    }
    
    /**
     * \brief Instruction offset
     * \returns Offset in DWORDs
     */
    uint32_t offset() const {
      return m_offset;
    }
    
    /**
     * \brief Argument value
     * 
     * Retrieves an argument DWORD. Note that some instructions
     * take 64-bit arguments which require more than one DWORD.
     * Arguments start at index 1. Calling this method with an
     * argument ID of 0 will return the opcode token.
     * \param [in] idx Argument index, starting at 1
     * \returns The argument value
     */
    uint32_t arg(uint32_t idx) const {
      const uint32_t index = m_offset + idx;
      return index < m_length ? m_code[index] : 0;
    }

    /**
     * \brief Argument string
     *
     * Retrieves a pointer to a UTF-8-encoded string.
     * \param [in] idx Argument index, starting at 1
     * \returns Pointer to the literal string
     */
    const char* chr(uint32_t idx) const {
      const uint32_t index = m_offset + idx;
      return index < m_length ? reinterpret_cast<const char*>(&m_code[index]) : nullptr;
    }
    
    /**
     * \brief Changes the value of an argument
     * 
     * \param [in] idx Argument index, starting at 1
     * \param [in] word New argument word
     */
    void setArg(uint32_t idx, uint32_t word) const {
      if (m_offset + idx < m_length)
        m_code[m_offset + idx] = word;
    }
    
  private:
    
    uint32_t* m_code   = nullptr;
    uint32_t  m_offset = 0;
    uint32_t  m_length = 0;
    
  };
  
  
  /**
   * \brief SPIR-V instruction iterator
   * 
   * Convenient iterator that can be used
   * to process raw SPIR-V shader code.
   */
  class SpirvInstructionIterator {
    
  public:
    
    SpirvInstructionIterator() { }
    SpirvInstructionIterator(uint32_t* code, uint32_t offset, uint32_t length)
    : m_code  (length != 0 ? code   : nullptr),
      m_offset(length != 0 ? offset : 0),
      m_length(length) {
      if ((length >= 5) && (offset == 0) && (m_code[0] == spv::MagicNumber))
        this->advance(5);
    }
    
    SpirvInstructionIterator& operator ++ () {
      this->advance(SpirvInstruction(m_code, m_offset, m_length).length());
      return *this;
    }
    
    SpirvInstructionIterator operator ++ (int) {
      SpirvInstructionIterator result = *this;
      this->advance(SpirvInstruction(m_code, m_offset, m_length).length());
      return result;
    }
    
    SpirvInstruction operator * () const {
      return SpirvInstruction(m_code, m_offset, m_length);
    }
    
    bool operator == (const SpirvInstructionIterator& other) const {
      return this->m_code   == other.m_code
          && this->m_offset == other.m_offset
          && this->m_length == other.m_length;
    }
    
    bool operator != (const SpirvInstructionIterator& other) const {
      return this->m_code   != other.m_code
          || this->m_offset != other.m_offset
          || this->m_length != other.m_length;
    }
    
  private:
    
    uint32_t* m_code   = nullptr;
    uint32_t  m_offset = 0;
    uint32_t  m_length = 0;
    
    void advance(uint32_t n) {
      if (m_offset + n < m_length) {
        m_offset += n;
      } else {
        m_code   = nullptr;
        m_offset = 0;
        m_length = 0;
      }
    }
    
  };
  
}
