//===-- DisassemblerLLVMC.h -------------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#ifndef liblldb_DisassemblerLLVMC_h_
#define liblldb_DisassemblerLLVMC_h_

// C Includes
// C++ Includes
#include <memory>
#include <mutex>
#include <string>

// Other libraries and framework includes
#include "llvm-c/Disassembler.h"

// Project includes
#include "lldb/Core/Address.h"
#include "lldb/Core/Disassembler.h"
#include "lldb/Core/PluginManager.h"

// Opaque references to C++ Objects in LLVM's MC.
namespace llvm
{
    class MCContext;
    class MCInst;
    class MCInstrInfo;
    class MCRegisterInfo;
    class MCDisassembler;
    class MCInstPrinter;
    class MCAsmInfo;
    class MCSubtargetInfo;
} // namespace llvm

class InstructionLLVMC;

class DisassemblerLLVMC : public lldb_private::Disassembler
{
    // Since we need to make two actual MC Disassemblers for ARM (ARM & THUMB), and there's a bit of goo to set up and own
    // in the MC disassembler world, I added this class to manage the actual disassemblers.
    class LLVMCDisassembler
    {
    public:
        LLVMCDisassembler (const char *triple, const char *cpu, const char *features_str, unsigned flavor, DisassemblerLLVMC &owner);

        ~LLVMCDisassembler();

        uint64_t GetMCInst (const uint8_t *opcode_data, size_t opcode_data_len, lldb::addr_t pc, llvm::MCInst &mc_inst);
        void PrintMCInst (llvm::MCInst &mc_inst, std::string &inst_string, std::string &comments_string);
        void     SetStyle (bool use_hex_immed, HexImmediateStyle hex_style);
        bool     CanBranch (llvm::MCInst &mc_inst);
        bool     HasDelaySlot (llvm::MCInst &mc_inst);
        bool     IsValid()
        {
            return m_is_valid;
        }

    private:
        bool                                     m_is_valid;
        std::unique_ptr<llvm::MCContext>         m_context_ap;
        std::unique_ptr<llvm::MCAsmInfo>         m_asm_info_ap;
        std::unique_ptr<llvm::MCSubtargetInfo>   m_subtarget_info_ap;
        std::unique_ptr<llvm::MCInstrInfo>       m_instr_info_ap;
        std::unique_ptr<llvm::MCRegisterInfo>    m_reg_info_ap;
        std::unique_ptr<llvm::MCInstPrinter>     m_instr_printer_ap;
        std::unique_ptr<llvm::MCDisassembler>    m_disasm_ap;
    };

public:
    DisassemblerLLVMC(const lldb_private::ArchSpec &arch, const char *flavor /* = NULL */);

    ~DisassemblerLLVMC() override;

    //------------------------------------------------------------------
    // Static Functions
    //------------------------------------------------------------------
    static void
    Initialize();

    static void
    Terminate();

    static lldb_private::ConstString
    GetPluginNameStatic();

    static lldb_private::Disassembler *
    CreateInstance(const lldb_private::ArchSpec &arch, const char *flavor);

    size_t
    DecodeInstructions(const lldb_private::Address &base_addr,
                       const lldb_private::DataExtractor& data,
                       lldb::offset_t data_offset,
                       size_t num_instructions,
                       bool append,
                       bool data_from_file) override;

    //------------------------------------------------------------------
    // PluginInterface protocol
    //------------------------------------------------------------------
    lldb_private::ConstString
    GetPluginName() override;

    uint32_t
    GetPluginVersion() override;

protected:
    friend class InstructionLLVMC;

    bool
    FlavorValidForArchSpec(const lldb_private::ArchSpec &arch, const char *flavor) override;

    bool
    IsValid()
    {
        return (m_disasm_ap.get() != NULL && m_disasm_ap->IsValid());
    }

    int OpInfo(uint64_t PC,
               uint64_t Offset,
               uint64_t Size,
               int TagType,
               void *TagBug);

    const char *SymbolLookup (uint64_t ReferenceValue,
                              uint64_t *ReferenceType,
                              uint64_t ReferencePC,
                              const char **ReferenceName);

    static int OpInfoCallback (void *DisInfo,
                               uint64_t PC,
                               uint64_t Offset,
                               uint64_t Size,
                               int TagType,
                               void *TagBug);

    static const char *SymbolLookupCallback(void *DisInfo,
                                            uint64_t ReferenceValue,
                                            uint64_t *ReferenceType,
                                            uint64_t ReferencePC,
                                            const char **ReferenceName);

    void Lock(InstructionLLVMC *inst,
              const lldb_private::ExecutionContext *exe_ctx)
    {
        m_mutex.lock();
        m_inst = inst;
        m_exe_ctx = exe_ctx;
    }

    void Unlock()
    {
        m_inst = NULL;
        m_exe_ctx = NULL;
        m_mutex.unlock();
    }

    const lldb_private::ExecutionContext *m_exe_ctx;
    InstructionLLVMC *m_inst;
    std::mutex m_mutex;
    bool m_data_from_file;

    std::unique_ptr<LLVMCDisassembler> m_disasm_ap;
    std::unique_ptr<LLVMCDisassembler> m_alternate_disasm_ap;
};

#endif // liblldb_DisassemblerLLVM_h_
