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

#ifndef lldb_Opcode_h
#define lldb_Opcode_h

// C Includes
#include <string.h>

// C++ Includes
// Other libraries and framework includes
#include "llvm/Support/MathExtras.h"

// Project includes
#include "lldb/Host/Endian.h"
#include "lldb/lldb-public.h"

namespace lldb
{
    class SBInstruction;
} // namespace lldb

namespace lldb_private {

    class Opcode
    {
    public:
        enum Type
        {
            eTypeInvalid,
            eType8,
            eType16,
            eType16_2, // a 32-bit Thumb instruction, made up of two words
            eType32,
            eType64,
            eTypeBytes
        };

        Opcode () : m_byte_order (lldb::eByteOrderInvalid), m_type (eTypeInvalid)
        {
        }

        Opcode (uint8_t inst, lldb::ByteOrder order) : m_byte_order (order), m_type (eType8)
        {
            m_data.inst8 = inst;
        }

        Opcode (uint16_t inst, lldb::ByteOrder order) : m_byte_order (order), m_type (eType16)
        {
            m_data.inst16 = inst;
        }

        Opcode (uint32_t inst, lldb::ByteOrder order) : m_byte_order (order), m_type (eType32)
        {
            m_data.inst32 = inst;
        }

        Opcode (uint64_t inst, lldb::ByteOrder order) : m_byte_order (order), m_type (eType64)
        {
            m_data.inst64 = inst;
        }

        Opcode (uint8_t *bytes, size_t length) : m_byte_order (lldb::eByteOrderInvalid)
        {
            SetOpcodeBytes (bytes, length);
        }

        void
        Clear()
        {
            m_byte_order = lldb::eByteOrderInvalid;
            m_type = Opcode::eTypeInvalid;
        }

        Opcode::Type
        GetType () const
        {
            return m_type;
        }

        uint8_t
        GetOpcode8 (uint8_t invalid_opcode = UINT8_MAX) const
        {
            switch (m_type)
            {
            case Opcode::eTypeInvalid:  break;
            case Opcode::eType8:        return m_data.inst8;
            case Opcode::eType16:       break;
            case Opcode::eType16_2:     break;
            case Opcode::eType32:       break;
            case Opcode::eType64:       break;
            case Opcode::eTypeBytes:    break;
            }
            return invalid_opcode;
        }

        uint16_t
        GetOpcode16 (uint16_t invalid_opcode = UINT16_MAX) const
        {
            switch (m_type)
            {
            case Opcode::eTypeInvalid:  break;
            case Opcode::eType8:        return m_data.inst8;
            case Opcode::eType16:       return GetEndianSwap() ? llvm::ByteSwap_16(m_data.inst16) : m_data.inst16;
            case Opcode::eType16_2:     break;
            case Opcode::eType32:       break;
            case Opcode::eType64:       break;
            case Opcode::eTypeBytes:    break;
            }
            return invalid_opcode;
        }

        uint32_t
        GetOpcode32 (uint32_t invalid_opcode = UINT32_MAX) const
        {
            switch (m_type)
            {
            case Opcode::eTypeInvalid:  break;
            case Opcode::eType8:        return m_data.inst8;
            case Opcode::eType16:       return GetEndianSwap() ? llvm::ByteSwap_16(m_data.inst16) : m_data.inst16;
            case Opcode::eType16_2:     // passthrough
            case Opcode::eType32:       return GetEndianSwap() ? llvm::ByteSwap_32(m_data.inst32) : m_data.inst32;
            case Opcode::eType64:       break;
            case Opcode::eTypeBytes:    break;
            }
            return invalid_opcode;
        }

        uint64_t
        GetOpcode64 (uint64_t invalid_opcode = UINT64_MAX) const
        {
            switch (m_type)
            {
            case Opcode::eTypeInvalid:  break;
            case Opcode::eType8:        return m_data.inst8;
            case Opcode::eType16:       return GetEndianSwap() ? llvm::ByteSwap_16(m_data.inst16) : m_data.inst16;
            case Opcode::eType16_2:     // passthrough
            case Opcode::eType32:       return GetEndianSwap() ? llvm::ByteSwap_32(m_data.inst32) : m_data.inst32;
            case Opcode::eType64:       return GetEndianSwap() ? llvm::ByteSwap_64(m_data.inst64) : m_data.inst64;
            case Opcode::eTypeBytes:    break;
            }
            return invalid_opcode;
        }

        void
        SetOpcode8 (uint8_t inst, lldb::ByteOrder order)
        {
            m_type = eType8;
            m_data.inst8 = inst;
            m_byte_order = order;
        }

        void
        SetOpcode16 (uint16_t inst, lldb::ByteOrder order)
        {
            m_type = eType16;
            m_data.inst16 = inst;
            m_byte_order = order;
        }

        void
        SetOpcode16_2 (uint32_t inst, lldb::ByteOrder order)
        {
            m_type = eType16_2;
            m_data.inst32 = inst;
            m_byte_order = order;
        }

        void
        SetOpcode32 (uint32_t inst, lldb::ByteOrder order)
        {
            m_type = eType32;
            m_data.inst32 = inst;
            m_byte_order = order;
        }

        void
        SetOpcode64 (uint64_t inst, lldb::ByteOrder order)
        {
            m_type = eType64;
            m_data.inst64 = inst;
            m_byte_order = order;
        }

        void
        SetOpcodeBytes (const void *bytes, size_t length)
        {
            if (bytes != nullptr && length > 0)
            {
                m_type = eTypeBytes;
                m_data.inst.length = length;
                assert (length < sizeof (m_data.inst.bytes));
                memcpy (m_data.inst.bytes, bytes, length);
                m_byte_order = lldb::eByteOrderInvalid;
            }
            else
            {
                m_type = eTypeInvalid;
                m_data.inst.length = 0;
            }
        }

        int
        Dump (Stream *s, uint32_t min_byte_width);

        const void *
        GetOpcodeBytes () const
        {
            return ((m_type == Opcode::eTypeBytes) ? m_data.inst.bytes : nullptr);
        }

        uint32_t
        GetByteSize () const
        {
            switch (m_type)
            {
            case Opcode::eTypeInvalid: break;
            case Opcode::eType8:     return sizeof(m_data.inst8);
            case Opcode::eType16:    return sizeof(m_data.inst16);
            case Opcode::eType16_2:  // passthrough
            case Opcode::eType32:    return sizeof(m_data.inst32);
            case Opcode::eType64:    return sizeof(m_data.inst64);
            case Opcode::eTypeBytes: return m_data.inst.length;
            }
            return 0;
        }

        // Get the opcode exactly as it would be laid out in memory.
        uint32_t
        GetData (DataExtractor &data) const;

    protected:

        friend class lldb::SBInstruction;

        const void *
        GetOpcodeDataBytes () const
        {
            switch (m_type)
            {
                case Opcode::eTypeInvalid: break;
                case Opcode::eType8:     return &m_data.inst8;
                case Opcode::eType16:    return &m_data.inst16;
                case Opcode::eType16_2:  // passthrough
                case Opcode::eType32:    return &m_data.inst32;
                case Opcode::eType64:    return &m_data.inst64;
                case Opcode::eTypeBytes: return m_data.inst.bytes;
            }
            return nullptr;
        }

        lldb::ByteOrder
        GetDataByteOrder () const;

        bool
        GetEndianSwap() const
        {
            return (m_byte_order == lldb::eByteOrderBig && endian::InlHostByteOrder() == lldb::eByteOrderLittle) ||
                   (m_byte_order == lldb::eByteOrderLittle && endian::InlHostByteOrder() == lldb::eByteOrderBig);
        }

        lldb::ByteOrder m_byte_order;

        Opcode::Type m_type;
        union
        {
            uint8_t inst8;
            uint16_t inst16;
            uint32_t inst32;
            uint64_t inst64;
            struct
            {
                uint8_t bytes[16]; // This must be big enough to handle any opcode for any supported target.
                uint8_t length;
            } inst;
        } m_data;
    };

} // namespace lldb_private

#endif // lldb_Opcode_h
