//===-- Disassembler.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_Disassembler_h_
#define liblldb_Disassembler_h_

// C Includes
// C++ Includes
#include <vector>
#include <string>

// Other libraries and framework includes
// Project includes
#include "lldb/lldb-private.h"
#include "lldb/Core/Address.h"
#include "lldb/Core/ArchSpec.h"
#include "lldb/Core/EmulateInstruction.h"
#include "lldb/Core/Opcode.h"
#include "lldb/Core/PluginInterface.h"
#include "lldb/Interpreter/OptionValue.h"

namespace lldb_private {

class Instruction
{
public:
    Instruction (const Address &address, 
                 lldb::AddressClass addr_class = lldb::eAddressClassInvalid);

    virtual
   ~Instruction();

    const Address &
    GetAddress () const
    {
        return m_address;
    }
    
    const char *
    GetMnemonic (const ExecutionContext* exe_ctx)
    {
        CalculateMnemonicOperandsAndCommentIfNeeded (exe_ctx);
        return m_opcode_name.c_str();
    }

    const char *
    GetOperands (const ExecutionContext* exe_ctx)
    {
        CalculateMnemonicOperandsAndCommentIfNeeded (exe_ctx);
        return m_mnemonics.c_str();
    }
    
    const char *
    GetComment (const ExecutionContext* exe_ctx)
    {
        CalculateMnemonicOperandsAndCommentIfNeeded (exe_ctx);
        return m_comment.c_str();
    }

    virtual void
    CalculateMnemonicOperandsAndComment (const ExecutionContext* exe_ctx) = 0;
    
    lldb::AddressClass
    GetAddressClass ();

    void
    SetAddress (const Address &addr)
    {
        // Invalidate the address class to lazily discover
        // it if we need to.
        m_address_class = lldb::eAddressClassInvalid; 
        m_address = addr;
    }

    //------------------------------------------------------------------
    /// Dump the text representation of this Instruction to a Stream
    ///
    /// Print the (optional) address, (optional) bytes, opcode, 
    /// operands, and instruction comments to a stream.
    ///
    /// @param[in] s
    ///     The Stream to add the text to.
    ///
    /// @param[in] show_address
    ///     Whether the address (using disassembly_addr_format_spec formatting)
    ///     should be printed.
    ///
    /// @param[in] show_bytes
    ///     Whether the bytes of the assembly instruction should be printed.
    ///
    /// @param[in] max_opcode_byte_size
    ///     The size (in bytes) of the largest instruction in the list that
    ///     we are printing (for text justification/alignment purposes)
    ///     Only needed if show_bytes is true.
    ///
    /// @param[in] exe_ctx
    ///     The current execution context, if available.  May be used in
    ///     the assembling of the operands+comments for this instruction.
    ///     Pass NULL if not applicable.
    ///
    /// @param[in] sym_ctx
    ///     The SymbolContext for this instruction.
    ///     Pass NULL if not available/computed.
    ///     Only needed if show_address is true.
    ///
    /// @param[in] prev_sym_ctx
    ///     The SymbolContext for the previous instruction.  Depending on
    ///     the disassembly address format specification, a change in 
    ///     Symbol / Function may mean that a line is printed with the new
    ///     symbol/function name.
    ///     Pass NULL if unavailable, or if this is the first instruction of
    ///     the InstructionList.
    ///     Only needed if show_address is true.
    ///
    /// @param[in] disassembly_addr_format
    ///     The format specification for how addresses are printed.
    ///     Only needed if show_address is true.
    ///
    /// @param[in] max_address_text_size
    ///     The length of the longest address string at the start of the
    ///     disassembly line that will be printed (the Debugger::FormatDisassemblerAddress() string)
    ///     so this method can properly align the instruction opcodes.
    ///     May be 0 to indicate no indentation/alignment of the opcodes.
    //------------------------------------------------------------------
    virtual void
    Dump (Stream *s,
          uint32_t max_opcode_byte_size,
          bool show_address,
          bool show_bytes,
          const ExecutionContext* exe_ctx,
          const SymbolContext *sym_ctx,
          const SymbolContext *prev_sym_ctx,
          const FormatEntity::Entry *disassembly_addr_format,
          size_t max_address_text_size);
    
    virtual bool
    DoesBranch () = 0;

    virtual bool
    HasDelaySlot ();

    virtual size_t
    Decode (const Disassembler &disassembler, 
            const DataExtractor& data,
            lldb::offset_t data_offset) = 0;
            
    virtual void
    SetDescription (const char *) {}  // May be overridden in sub-classes that have descriptions.
    
    lldb::OptionValueSP
    ReadArray (FILE *in_file, Stream *out_stream, OptionValue::Type data_type);

    lldb::OptionValueSP
    ReadDictionary (FILE *in_file, Stream *out_stream);

    bool
    DumpEmulation (const ArchSpec &arch);
    
    virtual bool
    TestEmulation (Stream *stream, const char *test_file_name);
    
    bool
    Emulate (const ArchSpec &arch,
             uint32_t evaluate_options,
             void *baton,
             EmulateInstruction::ReadMemoryCallback read_mem_callback,
             EmulateInstruction::WriteMemoryCallback write_mem_calback,
             EmulateInstruction::ReadRegisterCallback read_reg_callback,
             EmulateInstruction::WriteRegisterCallback write_reg_callback);
                      
    const Opcode &
    GetOpcode () const
    {
        return m_opcode;
    }
    
    uint32_t
    GetData (DataExtractor &data);

protected:
    Address m_address; // The section offset address of this instruction
    // We include an address class in the Instruction class to
    // allow the instruction specify the eAddressClassCodeAlternateISA
    // (currently used for thumb), and also to specify data (eAddressClassData).
    // The usual value will be eAddressClassCode, but often when
    // disassembling memory, you might run into data. This can
    // help us to disassemble appropriately.
private:
    lldb::AddressClass m_address_class; // Use GetAddressClass () accessor function!
protected:
    Opcode m_opcode; // The opcode for this instruction
    std::string m_opcode_name;
    std::string m_mnemonics;
    std::string m_comment;
    bool m_calculated_strings;

    void
    CalculateMnemonicOperandsAndCommentIfNeeded (const ExecutionContext* exe_ctx)
    {
        if (!m_calculated_strings)
        {
            m_calculated_strings = true;
            CalculateMnemonicOperandsAndComment(exe_ctx);
        }
    }
};

class InstructionList
{
public:
    InstructionList();
    ~InstructionList();

    size_t
    GetSize() const;
    
    uint32_t
    GetMaxOpcocdeByteSize () const;

    lldb::InstructionSP
    GetInstructionAtIndex (size_t idx) const;
    
    uint32_t
    GetIndexOfNextBranchInstruction(uint32_t start, Target &target) const;
    
    uint32_t
    GetIndexOfInstructionAtLoadAddress (lldb::addr_t load_addr, Target &target);

    uint32_t
    GetIndexOfInstructionAtAddress (const Address &addr);

    void
    Clear();

    void
    Append (lldb::InstructionSP &inst_sp);

    void
    Dump (Stream *s,
          bool show_address,
          bool show_bytes,
          const ExecutionContext* exe_ctx);

private:
    typedef std::vector<lldb::InstructionSP> collection;
    typedef collection::iterator iterator;
    typedef collection::const_iterator const_iterator;

    collection m_instructions;
};

class PseudoInstruction : 
    public Instruction
{
public:

    PseudoInstruction ();
    
    ~PseudoInstruction() override;
     
    bool
    DoesBranch() override;

    bool
    HasDelaySlot() override;

    void
    CalculateMnemonicOperandsAndComment(const ExecutionContext* exe_ctx) override
    {
        // TODO: fill this in and put opcode name into Instruction::m_opcode_name,
        // mnemonic into Instruction::m_mnemonics, and any comment into 
        // Instruction::m_comment
    }
    
    size_t
    Decode(const Disassembler &disassembler,
           const DataExtractor &data,
           lldb::offset_t data_offset) override;
            
    void
    SetOpcode (size_t opcode_size, void *opcode_data);
    
    void
    SetDescription(const char *description) override;
    
protected:
    std::string m_description;
    
    DISALLOW_COPY_AND_ASSIGN (PseudoInstruction);
};

class Disassembler :
    public std::enable_shared_from_this<Disassembler>,
    public PluginInterface
{
public:

    enum
    {
        eOptionNone             = 0u,
        eOptionShowBytes        = (1u << 0),
        eOptionRawOuput         = (1u << 1),
        eOptionMarkPCSourceLine = (1u << 2), // Mark the source line that contains the current PC (mixed mode only)
        eOptionMarkPCAddress    = (1u << 3)  // Mark the disassembly line the contains the PC
    };

    enum HexImmediateStyle
    {
        eHexStyleC,
        eHexStyleAsm,
    };

    // FindPlugin should be lax about the flavor string (it is too annoying to have various internal uses of the
    // disassembler fail because the global flavor string gets set wrong.  Instead, if you get a flavor string you
    // don't understand, use the default.  Folks who care to check can use the FlavorValidForArchSpec method on the
    // disassembler they got back.
    static lldb::DisassemblerSP
    FindPlugin (const ArchSpec &arch, const char *flavor, const char *plugin_name);
    
    // This version will use the value in the Target settings if flavor is NULL;
    static lldb::DisassemblerSP
    FindPluginForTarget(const lldb::TargetSP target_sp, const ArchSpec &arch, const char *flavor, const char *plugin_name);

    static lldb::DisassemblerSP
    DisassembleRange (const ArchSpec &arch,
                      const char *plugin_name,
                      const char *flavor,
                      const ExecutionContext &exe_ctx,
                      const AddressRange &disasm_range,
                      bool prefer_file_cache);
    
    static lldb::DisassemblerSP 
    DisassembleBytes (const ArchSpec &arch,
                      const char *plugin_name,
                      const char *flavor,
                      const Address &start,
                      const void *bytes,
                      size_t length,
                      uint32_t max_num_instructions,
                      bool data_from_file);

    static bool
    Disassemble (Debugger &debugger,
                 const ArchSpec &arch,
                 const char *plugin_name,
                 const char *flavor,
                 const ExecutionContext &exe_ctx,
                 const AddressRange &range,
                 uint32_t num_instructions,
                 uint32_t num_mixed_context_lines,
                 uint32_t options,
                 Stream &strm);

    static bool
    Disassemble (Debugger &debugger,
                 const ArchSpec &arch,
                 const char *plugin_name,
                 const char *flavor,
                 const ExecutionContext &exe_ctx,
                 const Address &start,
                 uint32_t num_instructions,
                 uint32_t num_mixed_context_lines,
                 uint32_t options,
                 Stream &strm);

    static size_t
    Disassemble (Debugger &debugger,
                 const ArchSpec &arch,
                 const char *plugin_name,
                 const char *flavor,
                 const ExecutionContext &exe_ctx,
                 SymbolContextList &sc_list,
                 uint32_t num_instructions,
                 uint32_t num_mixed_context_lines,
                 uint32_t options,
                 Stream &strm);
    
    static bool
    Disassemble (Debugger &debugger,
                 const ArchSpec &arch,
                 const char *plugin_name,
                 const char *flavor,
                 const ExecutionContext &exe_ctx,
                 const ConstString &name,
                 Module *module,
                 uint32_t num_instructions,
                 uint32_t num_mixed_context_lines,
                 uint32_t options,
                 Stream &strm);

    static bool
    Disassemble (Debugger &debugger,
                 const ArchSpec &arch,
                 const char *plugin_name,
                 const char *flavor,
                 const ExecutionContext &exe_ctx,
                 uint32_t num_instructions,
                 uint32_t num_mixed_context_lines,
                 uint32_t options,
                 Stream &strm);
    
    //------------------------------------------------------------------
    // Constructors and Destructors
    //------------------------------------------------------------------
    Disassembler(const ArchSpec &arch, const char *flavor);
    ~Disassembler() override;

    typedef const char * (*SummaryCallback)(const Instruction& inst, ExecutionContext *exe_context, void *user_data);

    static bool 
    PrintInstructions (Disassembler *disasm_ptr,
                       Debugger &debugger,
                       const ArchSpec &arch,
                       const ExecutionContext &exe_ctx,
                       uint32_t num_instructions,
                       uint32_t num_mixed_context_lines,
                       uint32_t options,
                       Stream &strm);
    
    size_t
    ParseInstructions (const ExecutionContext *exe_ctx,
                       const AddressRange &range,
                       Stream *error_strm_ptr,
                       bool prefer_file_cache);

    size_t
    ParseInstructions (const ExecutionContext *exe_ctx,
                       const Address &range,
                       uint32_t num_instructions,
                       bool prefer_file_cache);

    virtual size_t
    DecodeInstructions (const Address &base_addr,
                        const DataExtractor& data,
                        lldb::offset_t data_offset,
                        size_t num_instructions,
                        bool append,
                        bool data_from_file) = 0;
    
    InstructionList &
    GetInstructionList ();

    const InstructionList &
    GetInstructionList () const;

    const ArchSpec &
    GetArchitecture () const
    {
        return m_arch;
    }
    
    const char *
    GetFlavor () const
    {
        return m_flavor.c_str();
    }
    
    virtual bool
    FlavorValidForArchSpec (const lldb_private::ArchSpec &arch, const char *flavor) = 0;    

protected:
    //------------------------------------------------------------------
    // Classes that inherit from Disassembler can see and modify these
    //------------------------------------------------------------------
    ArchSpec m_arch;
    InstructionList m_instruction_list;
    lldb::addr_t m_base_addr;
    std::string m_flavor;

private:
    //------------------------------------------------------------------
    // For Disassembler only
    //------------------------------------------------------------------
    DISALLOW_COPY_AND_ASSIGN (Disassembler);
};

} // namespace lldb_private

#endif // liblldb_Disassembler_h_
