/*===================== begin_copyright_notice ==================================

Copyright (c) 2017 Intel Corporation

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


======================= end_copyright_notice ==================================*/

#if GED_DISASSEMBLY

#include <iomanip>
#include <cstring>
#include "common/ged_interpreter_types.h"
#include "xcoder/ged_interpreters.h"
#include "xcoder/ged_disassembler.h"

using std::dec;
using std::hex;
using std::setw;
using std::setfill;


/*************************************************************************************************
 * class GEDDisassembler API functions
 *************************************************************************************************/

string GEDDisassembler::Disassemble()
{
    const ged_disassembly_table_t topLevelDisassemblyTable = GetCurrentModelDisassemblyData().DisassemblyTable(GetRawOpcode());
    GEDASSERT(NULL != topLevelDisassemblyTable);
    stringstream strm;
    strm << setfill('0');
    if (IterateDisassemblyBlocks(topLevelDisassemblyTable, strm))
    {
        return strm.str();
    }
    else
    {
        return "BAD INSTRUCTION: " + strm.str() + " " + GetInstructionBytes();
    }
}


/*************************************************************************************************
 * class GEDDisassembler private member functions
 *************************************************************************************************/

bool GEDDisassembler::IterateDisassemblyBlocks(const ged_disassembly_table_t blocks, stringstream& strm)
{
    GEDASSERT(NULL != blocks);
    for (unsigned int i = 0; NULL != blocks[i]; ++i)
    {
        switch (blocks[i]->_entryType)
        {
        case GED_DISASSEMBLY_BLOCK_TYPE_TOKENS:
            if (!PrintDisassemblyTokens(*(blocks[i]), strm)) return false;
            break;
        case GED_DISASSEMBLY_BLOCK_TYPE_FIELD_LIST:
            if (!PrintFieldsList(*(blocks[i]), strm)) return false;
            break;
        case GED_DISASSEMBLY_BLOCK_TYPE_INTERPRETER:
            if (!PrintInterpretedField(*(blocks[i]), strm)) return false;
            break;
        case GED_DISASSEMBLY_BLOCK_TYPE_NEXT_TABLE:
        {
            GEDASSERT(blocks[i]->_nextTable._tableKey._field < GetCurrentModelData().numberOfInstructionFields);
            GED_RETURN_VALUE ret = GED_RETURN_VALUE_INVALID_FIELD;
            uint32_t tableIndex = GetRawField(blocks[i]->_nextTable._tableKey._field, ret);
            if (GED_RETURN_VALUE_SUCCESS != ret) return false;
            if (GED_INTERPRETER_TYPE_NONE != blocks[i]->_nextTable._tableKey._interpType)
            {
                // If the dependee is an interpreted pseudo-field then use the position interpreter to obtain the actual index.
                GEDASSERT(blocks[i]->_nextTable._tableKey._interpId < GetCurrentModelData().numberOfPseudoFields);
                tableIndex = GEDInterpreter::InterpretPositionRaw(tableIndex, blocks[i]->_nextTable._tableKey._interpId,
                             GetCurrentModel(), ret);
                if (GED_RETURN_VALUE_SUCCESS != ret) return false;
            }
            GEDASSERT(NULL != blocks[i]->_nextTable._tablePtr);
            const ged_disassembly_table_t nextBlocks = blocks[i]->_nextTable._tablePtr[tableIndex];
            if (NULL != nextBlocks)
            {
                if (!IterateDisassemblyBlocks(nextBlocks, strm)) return false;
            }
            break;
        }
        default:
            GEDASSERT(0);
            return false;
        }
    }
    return true;
}


bool GEDDisassembler::PrintDisassemblyTokens(const ged_disassembly_block_t& block, stringstream& strm)
{
    GEDASSERT(GED_DISASSEMBLY_BLOCK_TYPE_TOKENS == block._entryType);
    for (unsigned int t = 0; t < block._tokens._numOfTokens; ++t)
    {
        if (!PrintToken(block._tokens._tokens[t], strm)) return false;
    }
    return true;
}


bool GEDDisassembler::PrintFieldsList(const ged_disassembly_block_t& block, stringstream& strm)
{
    GEDASSERT(0 != block._tokens._numOfTokens);
    GEDASSERT(GED_DISASSEMBLY_TOKEN_TYPE_CHAR != block._tokens._tokens[0]._tokenType);
    if (!PrintToken(block._tokens._tokens[0], strm)) return false;

    for (unsigned int f = 1; f < block._tokens._numOfTokens; ++f)
    {
        GEDASSERT(GED_DISASSEMBLY_TOKEN_TYPE_CHAR != block._tokens._tokens[f]._tokenType);
        if (!PrintToken(block._tokens._tokens[f], strm, ", ")) return false;
    }
    return true;
}


bool GEDDisassembler::PrintInterpretedField(const ged_disassembly_block_t& block, stringstream& strm)
{
    GEDASSERT(0 == block._interpreter._padding);
    switch (block._interpreter._interpType)
    {
    case GED_INTERPRETER_TYPE_POSITION:
        GEDASSERT(GED_INTERPRETER_TYPE_POSITION == block._interpreter._field._interpType); // verify consistency
        return PrintPositionInterpretedField(block._interpreter, strm);
    case GED_INTERPRETER_TYPE_REENUM:
    {
        // TODO: need to handle types other than unsigned
        GEDASSERT(GED_INTERPRETER_TYPE_REENUM == block._interpreter._field._interpType); // verify consistency
        GEDASSERT(1 == block._interpreter._numberOfFields);
        uint32_t value = MAX_UINT32_T;
        if (!GetGeneralizedFieldValue(block._interpreter._field, value)) return false;
        strm << value;
        break;
    }
    case GED_INTERPRETER_TYPE_DIV:
    {
        // TODO: need to handle types other than unsigned
        GEDASSERT(2 == block._interpreter._numberOfFields);
        uint32_t leftVal = MAX_UINT32_T;
        if (!GetGeneralizedFieldValue(block._interpreter._fieldList[0], leftVal)) return false;
        uint32_t rightVal = MAX_UINT32_T;
        if (!GetGeneralizedFieldValue(block._interpreter._fieldList[1], rightVal)) return false;
        GEDASSERT(0 != rightVal);
        GEDASSERT(0 == leftVal % rightVal);
        strm << (leftVal / rightVal); // TODO: should this be hex/dec etc.
    }
    break;
    case GED_INTERPRETER_TYPE_NUMTYPE:
    {
        GEDASSERT(2 == block._interpreter._numberOfFields);
        // TODO: Currently only explicit field values may be displayed using this interpreter
        GEDASSERT(GED_INTERPRETER_TYPE_NONE == block._interpreter._fieldList[0]._interpType);
        GED_FIELD_TYPE fieldType = fieldTypesByField[block._interpreter._fieldList[0]._field];
        GEDASSERT(IS_VALID_NUMERIC(fieldType)); // only numeric values may be displayed using a base interpreter
        /* GED_NUMERIC_TYPE */ uint32_t numericType = MAX_UINT32_T;
        if (!GetGeneralizedFieldValue(block._interpreter._fieldList[1], numericType)) return false;
        switch (numericType)
        {
        case GED_NUMERIC_TYPE_INT:
            break;
        case GED_NUMERIC_TYPE_FP:
            fieldType |= GED_FIELD_TYPE_FP_BIT;
            break;
        default:
            GEDASSERT(0);
        };
        return PrintNumericField(block._interpreter._fieldList[0]._field, fieldType, strm);
    }
    case GED_INTERPRETER_TYPE_COLLECT:
        GEDASSERT(0 == block._interpreter._numberOfFields);
        {
            GED_RETURN_VALUE ret = GED_RETURN_VALUE_INVALID_FIELD;
            const uint8_t fieldWidth =
                (ModelsArray[GetCurrentModel()].collectionTables[block._interpreter._noargId]._width + (uint8_t)3) / (uint8_t)4;
            strm << "0x" << hex << setw(fieldWidth) << GEDInterpreter::CollectFields(*this, block._interpreter._noargId, ret) << dec;
            return (GED_RETURN_VALUE_SUCCESS == ret);
        }
    default:
        GEDASSERT(0);
        return false;
    }
    return true;
}


bool GEDDisassembler::PrintToken(const ged_disassembly_token_t& token, stringstream& strm, const string& prefix /* = "" */)
{
    switch (token._tokenType)
    {
    case GED_DISASSEMBLY_TOKEN_TYPE_OPCODE:
        strm << GetMnemonic();
        break;
    case GED_DISASSEMBLY_TOKEN_TYPE_COMPACTED:
        // TODO: This string should probably be stored in the predefined DB and exposed via the autogenerated files.
        if (IsCompact()) strm << prefix << "Compacted";
        break;
    case GED_DISASSEMBLY_TOKEN_TYPE_CHAR:
        strm << token._char;
        break;
    case GED_DISASSEMBLY_TOKEN_TYPE_FIELD:
        return PrintField(token._field, strm, prefix);
    case GED_DISASSEMBLY_TOKEN_TYPE_RAW_FIELD:
        return PrintRawField(token._field, strm, prefix);
    default:
        GEDASSERT(0);
        return false;
    }
    return true;
}


bool GEDDisassembler::PrintField(const uint16_t field, stringstream& strm, const string& prefix /* = "" */)
{
    GEDASSERT(field < GetCurrentModelData().numberOfInstructionFields);
    const GED_FIELD_TYPE fieldType = fieldTypesByField[field];
    if (IS_STRING(fieldType))
    {
        GEDASSERT(IS_VALID_STRING(fieldType));
        return PrintStringField(field, strm, prefix);
    }
    else
    {
        GEDASSERT(IS_VALID_NUMERIC(fieldType));
        return PrintNumericField(field, fieldType, strm, prefix);
    }
}


bool GEDDisassembler::PrintStringField(const uint16_t field, stringstream& strm, const string& prefix /* = "" */)
{
    GED_RETURN_VALUE ret = GED_RETURN_VALUE_INVALID_FIELD;
    const uint32_t tableIndex = GetUnsignedField(field, ret);
    if (GED_RETURN_VALUE_SUCCESS != ret) return false;
    GEDASSERT(NULL != stringGettersByField[field]);
    if (NULL != stringGettersByField[field][tableIndex])
    {
        if (0 != strlen(stringGettersByField[field][tableIndex]))
        {
            strm << prefix << stringGettersByField[field][tableIndex];
        }
    }
    return true;
}


bool GEDDisassembler::PrintNumericField(const uint16_t field, const GED_FIELD_TYPE fieldType, stringstream& strm,
                                        const string& prefix /* = "" */)
{
    GED_RETURN_VALUE ret = GED_RETURN_VALUE_INVALID_FIELD;
    strm << prefix;
    if (IS_FP(fieldType))
    {
        // Only signed values are currently allowed to be displayed in floating-point format.
        GEDASSERT(IS_SIGNED(fieldType) || IS_VARIABLE(fieldType));

        // All floating-point numbers will be displayed in decimal base regardless of the hex-bit in the field's type (which is
        // ignored).
        strm << std::setprecision(16) << dec;
    }
    else if (IS_HEX(fieldType))
    {
        const uint8_t fieldWidth = (GetFieldWidth(field, false) + (uint8_t)3) / (uint8_t)4;
        GEDASSERT((uint8_t)0 != fieldWidth);
        strm << hex << "0x" << setw(fieldWidth);
    }
    else strm << dec;

    if (IS_SIGNED(fieldType))
    {
        if (IS_QWORD(fieldType))
        {
            if (IS_FP(fieldType)) strm << GetSigned64Field(field, ret)._df;
            else strm << GetSigned64Field(field, ret)._qw;
        }
        else if (IS_VARIABLE(fieldType))
        {
            const int64_t temp = GetUnsigned64Field(field, ret);
            const SignedQNum val = *(reinterpret_cast<const SignedQNum*>(&temp));
            if (IS_FP(fieldType)) strm << val._df;
            else
            {
                if (IS_HEX(fieldType)) strm << (val._qw & BitsToMaxValue(GetFieldWidth(field)));
                else strm << val._qw;
            }
        }
        else
        {
            if (IS_FP(fieldType)) strm << GetSignedField(field, ret)._f;
            else strm << GetSignedField(field, ret)._dw;
        }
    }
    else
    {
        if (IS_QWORD(fieldType)) strm << GetUnsigned64Field(field, ret);
        else if (IS_VARIABLE(fieldType))
        {
            strm << (GetUnsigned64Field(field, ret) & BitsToMaxValue(GetFieldWidth(field)));
        }
        else strm << GetUnsignedField(field, ret);
    }
    return (GED_RETURN_VALUE_SUCCESS == ret);
}


bool GEDDisassembler::PrintRawField(const uint16_t field, stringstream& strm, const string& prefix /* = "" */)
{
    GEDASSERT(field < GetCurrentModelData().numberOfInstructionFields);
    GEDASSERT(IS_VALID_STRING(fieldTypesByField[field]) || IS_VALID_NUMERIC(fieldTypesByField[field]));
    GED_RETURN_VALUE ret = GED_RETURN_VALUE_INVALID_FIELD;
    const uint8_t fieldWidth = (GetFieldWidth(field) + (uint8_t)3) / (uint8_t)4;
    GEDASSERT((uint8_t)0 != fieldWidth);
    strm << prefix << hex << "0x" << setw(fieldWidth) << GetRawField(field, ret);
    return (GED_RETURN_VALUE_SUCCESS == ret);
}


bool GEDDisassembler::PrintPositionInterpretedField(const ged_disassembly_block_interpreter_t& interpreter, stringstream& strm)
{
    GEDASSERT(1 == interpreter._numberOfFields);
    uint32_t value = MAX_UINT32_T;
    if (!GetGeneralizedFieldValue(interpreter._field, value)) return false; // TODO: Currently this function returns only uint32_t
    if (IS_STRING(pseudoFieldTypesByField[interpreter._field._interpId]))
    {
        GEDASSERT(IS_VALID_STRING(pseudoFieldTypesByField[interpreter._field._interpId]));
        GEDASSERT(NULL != stringGettersByPseudoField[interpreter._field._interpId]);
        if (NULL != stringGettersByPseudoField[interpreter._field._interpId][value])
        {
            strm << stringGettersByPseudoField[interpreter._field._interpId][value];
        }
    }
    else
    {
        GEDASSERT(IS_VALID_NUMERIC(pseudoFieldTypesByField[interpreter._field._interpId]));

        if (IS_HEX(pseudoFieldTypesByField[interpreter._field._interpId]))
        {
            const uint8_t fieldWidth = (GetFieldWidth(interpreter._field._interpId, true) + (uint8_t)3) / (uint8_t)4;
            GEDASSERT((uint8_t)0 != fieldWidth);
            strm << hex << "0x" << setw(fieldWidth);
        }
        else strm << dec;

        if IS_SIGNED(pseudoFieldTypesByField[interpreter._field._interpId])
        {
            strm << (int32_t)value;
        }
        else
        {
            strm << value;
        }
    }
    return true;
}


bool GEDDisassembler::GetGeneralizedFieldValue(const ged_generalized_field_t& genField, uint32_t& value)
{
    // TODO: need to handle other field types besides unsigned
    GED_RETURN_VALUE ret = GED_RETURN_VALUE_INVALID_FIELD;
    switch (genField._interpType)
    {
    case GED_INTERPRETER_TYPE_NONE:
        value = GetUnsignedField(genField._field, ret);
        return (GED_RETURN_VALUE_SUCCESS == ret);
    case GED_INTERPRETER_TYPE_POSITION:
        value = GetRawField(genField._field, ret);
        if (GED_RETURN_VALUE_SUCCESS != ret) return false;
        value = GEDInterpreter::InterpretPosition(value, genField._interpId, GetCurrentModel(), ret);
        return (GED_RETURN_VALUE_SUCCESS == ret);
    case GED_INTERPRETER_TYPE_REENUM:
        GEDASSERT(IS_ENUM(fieldTypesByField[genField._field]));
        value = GetUnsignedField(genField._field, ret);
        if (GED_RETURN_VALUE_SUCCESS != ret) return false;
        value = GEDInterpreter::ReinterpretEnum(value, genField._interpId, GetCurrentModel(), ret);
        return (GED_RETURN_VALUE_SUCCESS == ret);
    default:
        GEDASSERT(0);
        return false;
    }
}

#endif // GED_DISASSEMBLY
