#include <stdio.h>
#include <assert.h>
#include <limits.h>
#include "enc_base.h"
#include "enc_wrapper.h"
#include "dec_base.h"
#include "utils/Log.h"

bool dump_x86_inst = false;

const RegName map_of_regno_2_regname[] = {
    RegName_EAX,    RegName_EBX,    RegName_ECX,    RegName_EDX,
    RegName_EDI,    RegName_ESI,    RegName_ESP,    RegName_EBP,
    RegName_XMM0,   RegName_XMM1,   RegName_XMM2,   RegName_XMM3,
    RegName_XMM4,   RegName_XMM5,   RegName_XMM6,   RegName_XMM7,
    RegName_Null,   RegName_Null,   RegName_Null,   RegName_Null,
    RegName_Null,   RegName_Null,   RegName_Null,   RegName_Null,
    RegName_Null,
    RegName_Null,   RegName_Null,   RegName_Null,   RegName_Null,   RegName_Null,
    RegName_Null,   RegName_Null,   RegName_Null,   RegName_Null,
    RegName_Null,   RegName_Null,   RegName_Null,   RegName_Null,
    RegName_Null,   RegName_Null,   
    RegName_Null,   RegName_Null,   RegName_Null,   RegName_Null
};

inline void add_r(EncoderBase::Operands & args, int physicalReg, OpndSize sz, OpndExt ext = OpndExt_None) {
    RegName reg = map_of_regno_2_regname[physicalReg];
    if (sz != getRegSize(reg)) {
       reg = getAliasReg(reg, sz);
    }
    args.add(EncoderBase::Operand(reg, ext));
}
inline void add_m(EncoderBase::Operands & args, int baseReg, int disp, OpndSize sz, OpndExt ext = OpndExt_None) {
    args.add(EncoderBase::Operand(sz,
                                  map_of_regno_2_regname[baseReg],
                                  RegName_Null, 0,
                                  disp, ext));
}
inline void add_m_scale(EncoderBase::Operands & args, int baseReg, int indexReg, int scale,
                        OpndSize sz, OpndExt ext = OpndExt_None) {
    args.add(EncoderBase::Operand(sz,
                                  map_of_regno_2_regname[baseReg],
                                  map_of_regno_2_regname[indexReg], scale,
                                  0, ext));
}
inline void add_m_disp_scale(EncoderBase::Operands & args, int baseReg, int disp, int indexReg, int scale,
                        OpndSize sz, OpndExt ext = OpndExt_None) {
    args.add(EncoderBase::Operand(sz,
                                  map_of_regno_2_regname[baseReg],
                                  map_of_regno_2_regname[indexReg], scale,
                                  disp, ext));
}

inline void add_fp(EncoderBase::Operands & args, unsigned i, bool dbl) {
    return args.add((RegName)( (dbl ? RegName_FP0D : RegName_FP0S) + i));
}
inline void add_imm(EncoderBase::Operands & args, OpndSize sz, int value, bool is_signed) {

    args.add(EncoderBase::Operand(sz, value,
             is_signed ? OpndExt_Signed : OpndExt_Zero));
}

#define MAX_DECODED_STRING_LEN 1024
char tmpBuffer[MAX_DECODED_STRING_LEN];

void printOperand(const EncoderBase::Operand & opnd) {
    unsigned int sz;
    if(!dump_x86_inst) return;
    sz = strlen(tmpBuffer);
    if(opnd.size() != OpndSize_32) {
        sz += snprintf(&tmpBuffer[sz], MAX_DECODED_STRING_LEN-sz, "%s ",
                       getOpndSizeString(opnd.size()));
    }
    if(opnd.is_mem()) {
        if(opnd.scale() != 0) {
            sz += snprintf(&tmpBuffer[sz], MAX_DECODED_STRING_LEN-sz,
                           "%d(%s,%s,%d)", opnd.disp(),
                           getRegNameString(opnd.base()),
                           getRegNameString(opnd.index()), opnd.scale());
        } else {
            sz += snprintf(&tmpBuffer[sz], MAX_DECODED_STRING_LEN-sz, "%d(%s)",
                           opnd.disp(), getRegNameString(opnd.base()));
        }
    }
    if(opnd.is_imm()) {
        sz += snprintf(&tmpBuffer[sz], MAX_DECODED_STRING_LEN-sz, "#%x",
                       (int)opnd.imm());
    }
    if(opnd.is_reg()) {
        sz += snprintf(&tmpBuffer[sz], MAX_DECODED_STRING_LEN-sz, "%s",
                       getRegNameString(opnd.reg()));
    }
}

void printDecoderInst(Inst & decInst) {
    unsigned int sz;
    if(!dump_x86_inst) return;
    sz = strlen(tmpBuffer);
    sz += snprintf(&tmpBuffer[sz], MAX_DECODED_STRING_LEN-sz, "%s ",
                   EncoderBase::toStr(decInst.mn));
    for(unsigned int k = 0; k < decInst.argc; k++) {
        if(k > 0) {
            sz = strlen(tmpBuffer);
            sz += snprintf(&tmpBuffer[sz], MAX_DECODED_STRING_LEN-sz, ", ");
        }
        printOperand(decInst.operands[decInst.argc-1-k]);
    }
    ALOGE("%s", tmpBuffer);
}
void printOperands(EncoderBase::Operands& opnds) {
    unsigned int sz;
    if(!dump_x86_inst) return;
    for(unsigned int k = 0; k < opnds.count(); k++) {
        if(k > 0) {
            sz = strlen(tmpBuffer);
            sz += snprintf(&tmpBuffer[sz], MAX_DECODED_STRING_LEN-sz, ", ");
        }
        printOperand(opnds[opnds.count()-1-k]);
    }
}
void printEncoderInst(Mnemonic m, EncoderBase::Operands& opnds) {
    if(!dump_x86_inst) return;
    snprintf(tmpBuffer, MAX_DECODED_STRING_LEN, "--- ENC %s ",
             EncoderBase::toStr(m));
    printOperands(opnds);
    ALOGE("%s", tmpBuffer);
}
int decodeThenPrint(char* stream_start) {
    if(!dump_x86_inst) return 0;
    snprintf(tmpBuffer, MAX_DECODED_STRING_LEN, "--- INST @ %p: ",
             stream_start);
    Inst decInst;
    unsigned numBytes = DecoderBase::decode(stream_start, &decInst);
    printDecoderInst(decInst);
    return numBytes;
}

extern "C" ENCODER_DECLARE_EXPORT char * encoder_imm(Mnemonic m, OpndSize size, int imm, char * stream) {
    EncoderBase::Operands args;

    add_imm(args, size, imm, true);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, m, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(m, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}
extern "C" ENCODER_DECLARE_EXPORT unsigned encoder_get_inst_size(char * stream) {
    Inst decInst;
    unsigned numBytes = DecoderBase::decode(stream, &decInst);
    return numBytes;
}

extern "C" ENCODER_DECLARE_EXPORT unsigned encoder_get_cur_operand_offset(int opnd_id)
{
    return (unsigned)EncoderBase::getOpndLocation(opnd_id);
}

extern "C" ENCODER_DECLARE_EXPORT char * encoder_update_imm(int imm, char * stream) {
    Inst decInst;
    unsigned numBytes = DecoderBase::decode(stream, &decInst);
    EncoderBase::Operands args;

    add_imm(args, decInst.operands[0].size(), imm, true);
    char* stream_next = (char *)EncoderBase::encode(stream, decInst.mn, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(decInst.mn, args);
    decodeThenPrint(stream);
#endif
    return stream_next;
}
extern "C" ENCODER_DECLARE_EXPORT char * encoder_mem(Mnemonic m, OpndSize size,
               int disp, int base_reg, bool isBasePhysical, char * stream) {
    EncoderBase::Operands args;
    add_m(args, base_reg, disp, size);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, m, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(m, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}
extern "C" ENCODER_DECLARE_EXPORT char * encoder_reg(Mnemonic m, OpndSize size,
               int reg, bool isPhysical, LowOpndRegType type, char * stream) {
    EncoderBase::Operands args;
    if(m == Mnemonic_IDIV || m == Mnemonic_MUL || m == Mnemonic_IMUL) {
      add_r(args, 0, size);
      add_r(args, 3, size);
    }
    add_r(args, reg, size);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, m, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(m, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}

extern "C" ENCODER_DECLARE_EXPORT char * encoder_reg_reg(Mnemonic m, OpndSize size,
                   int reg, bool isPhysical,
                   int reg2, bool isPhysical2, LowOpndRegType type, char * stream) {
    if((m == Mnemonic_MOV || m == Mnemonic_MOVQ) && reg == reg2) return stream;
    EncoderBase::Operands args;
    add_r(args, reg2, size); 
    if(m == Mnemonic_SAL || m == Mnemonic_SHR || m == Mnemonic_SHL || m == Mnemonic_SAR)
      add_r(args, reg, OpndSize_8);
    else
      add_r(args, reg, size);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, m, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(m, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}
extern "C" ENCODER_DECLARE_EXPORT char * encoder_mem_reg(Mnemonic m, OpndSize size,
                   int disp, int base_reg, bool isBasePhysical,
                   int reg, bool isPhysical, LowOpndRegType type, char * stream) {
    EncoderBase::Operands args;
    add_r(args, reg, size);
    add_m(args, base_reg, disp, size);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, m, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(m, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}
extern "C" ENCODER_DECLARE_EXPORT char * encoder_mem_scale_reg(Mnemonic m, OpndSize size,
                         int base_reg, bool isBasePhysical, int index_reg, bool isIndexPhysical, int scale,
                         int reg, bool isPhysical, LowOpndRegType type, char * stream) {
    EncoderBase::Operands args;
    add_r(args, reg, size);
    add_m_scale(args, base_reg, index_reg, scale, size);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, m, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(m, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}
extern "C" ENCODER_DECLARE_EXPORT char * encoder_reg_mem_scale(Mnemonic m, OpndSize size,
                         int reg, bool isPhysical,
                         int base_reg, bool isBasePhysical, int index_reg, bool isIndexPhysical, int scale,
                         LowOpndRegType type, char * stream) {
    EncoderBase::Operands args;
    add_m_scale(args, base_reg, index_reg, scale, size);
    add_r(args, reg, size);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, m, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(m, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}
extern "C" ENCODER_DECLARE_EXPORT char * encoder_mem_disp_scale_reg(Mnemonic m, OpndSize size,
                         int base_reg, bool isBasePhysical, int disp, int index_reg, bool isIndexPhysical, int scale,
                         int reg, bool isPhysical, LowOpndRegType type, char * stream) {
    EncoderBase::Operands args;
    add_r(args, reg, size);
    add_m_disp_scale(args, base_reg, disp, index_reg, scale, size);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, m, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(m, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}
extern "C" ENCODER_DECLARE_EXPORT char * encoder_movzs_mem_disp_scale_reg(Mnemonic m, OpndSize size,
                         int base_reg, bool isBasePhysical, int disp, int index_reg, bool isIndexPhysical, int scale,
                         int reg, bool isPhysical, LowOpndRegType type, char * stream) {
    EncoderBase::Operands args;
    add_r(args, reg, OpndSize_32);
    add_m_disp_scale(args, base_reg, disp, index_reg, scale, size);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, m, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(m, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}

extern "C" ENCODER_DECLARE_EXPORT char* encoder_reg_mem_disp_scale(Mnemonic m, OpndSize size,
                         int reg, bool isPhysical,
                         int base_reg, bool isBasePhysical, int disp, int index_reg, bool isIndexPhysical, int scale,
                         LowOpndRegType type, char* stream) {
    EncoderBase::Operands args;
    add_m_disp_scale(args, base_reg, disp, index_reg, scale, size);
    add_r(args, reg, size);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, m, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(m, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}

extern "C" ENCODER_DECLARE_EXPORT char * encoder_reg_mem(Mnemonic m, OpndSize size,
                   int reg, bool isPhysical,
                   int disp, int base_reg, bool isBasePhysical, LowOpndRegType type, char * stream) {
    EncoderBase::Operands args;
    add_m(args, base_reg, disp, size);
    add_r(args, reg, size);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, m, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(m, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}
extern "C" ENCODER_DECLARE_EXPORT char * encoder_imm_reg(Mnemonic m, OpndSize size,
                   int imm, int reg, bool isPhysical, LowOpndRegType type, char * stream) {
    EncoderBase::Operands args;
    add_r(args, reg, size); 
    if(m == Mnemonic_IMUL) add_r(args, reg, size); 
    if(m == Mnemonic_SAL || m == Mnemonic_SHR || m == Mnemonic_SHL
       || m == Mnemonic_SAR || m == Mnemonic_ROR)  
      add_imm(args, OpndSize_8, imm, true);
    else
      add_imm(args, size, imm, true);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, m, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(m, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}
extern "C" ENCODER_DECLARE_EXPORT char * encoder_update_imm_rm(int imm, char * stream) {
    Inst decInst;
    unsigned numBytes = DecoderBase::decode(stream, &decInst);
    EncoderBase::Operands args;
    args.add(decInst.operands[0]);
    add_imm(args, decInst.operands[1].size(), imm, true);
    char* stream_next = (char *)EncoderBase::encode(stream, decInst.mn, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(decInst.mn, args);
    decodeThenPrint(stream);
#endif
    return stream_next;
}
extern "C" ENCODER_DECLARE_EXPORT char * encoder_imm_mem(Mnemonic m, OpndSize size,
                   int imm,
                   int disp, int base_reg, bool isBasePhysical, char * stream) {
    EncoderBase::Operands args;
    add_m(args, base_reg, disp, size);
    if (m == Mnemonic_SAL || m == Mnemonic_SHR || m == Mnemonic_SHL
        || m == Mnemonic_SAR || m == Mnemonic_ROR)
        size = OpndSize_8;
    add_imm(args, size, imm, true);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, m, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(m, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}
extern "C" ENCODER_DECLARE_EXPORT char * encoder_fp_mem(Mnemonic m, OpndSize size, int reg,
                  int disp, int base_reg, bool isBasePhysical, char * stream) {
    EncoderBase::Operands args;
    add_m(args, base_reg, disp, size);

    add_fp(args, reg, size == OpndSize_64);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, m, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(m, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}
extern "C" ENCODER_DECLARE_EXPORT char * encoder_mem_fp(Mnemonic m, OpndSize size,
                  int disp, int base_reg, bool isBasePhysical,
                  int reg, char * stream) {
    EncoderBase::Operands args;

    add_fp(args, reg, size == OpndSize_64);
    add_m(args, base_reg, disp, size);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, m, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(m, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}

extern "C" ENCODER_DECLARE_EXPORT char * encoder_return(char * stream) {
    EncoderBase::Operands args;
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, Mnemonic_RET, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(Mnemonic_RET, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}
extern "C" ENCODER_DECLARE_EXPORT char * encoder_compare_fp_stack(bool pop, int reg, bool isDouble, char * stream) {

    Mnemonic m = pop ? Mnemonic_FUCOMIP : Mnemonic_FUCOMI;

    EncoderBase::Operands args;
    add_fp(args, reg, isDouble);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, m, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(m, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}
extern "C" ENCODER_DECLARE_EXPORT char * encoder_movez_mem_to_reg(OpndSize size,
                      int disp, int base_reg, bool isBasePhysical,
                      int reg, bool isPhysical, char * stream) {
    EncoderBase::Operands args;
    add_r(args, reg, OpndSize_32);
    add_m(args, base_reg, disp, size);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, Mnemonic_MOVZX, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(Mnemonic_MOVZX, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}
extern "C" ENCODER_DECLARE_EXPORT char * encoder_moves_mem_to_reg(OpndSize size,
                      int disp, int base_reg, bool isBasePhysical,
                      int reg, bool isPhysical, char * stream) {
    EncoderBase::Operands args;
    add_r(args, reg, OpndSize_32);
    add_m(args, base_reg, disp, size);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, Mnemonic_MOVSX, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(Mnemonic_MOVSX, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}
extern "C" ENCODER_DECLARE_EXPORT char * encoder_movez_reg_to_reg(OpndSize size,
                      int reg, bool isPhysical, int reg2,
                      bool isPhysical2, LowOpndRegType type, char * stream) {
    EncoderBase::Operands args;
    add_r(args, reg2, OpndSize_32); 
    add_r(args, reg, size);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, Mnemonic_MOVZX, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(Mnemonic_MOVZX, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}
extern "C" ENCODER_DECLARE_EXPORT char * encoder_moves_reg_to_reg(OpndSize size,
                      int reg, bool isPhysical,int reg2,
                      bool isPhysical2, LowOpndRegType type, char * stream) {
    EncoderBase::Operands args;
    add_r(args, reg2, OpndSize_32); 
    add_r(args, reg, size);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, Mnemonic_MOVSX, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(Mnemonic_MOVSX, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}

unsigned int DisassembleOperandToBuf(const EncoderBase::Operand& opnd, char* strbuf, unsigned int len)
{
    unsigned int sz = 0;
    if(opnd.size() != OpndSize_32) {
        sz += snprintf(&strbuf[sz], len-sz, "%s ",
                       getOpndSizeString(opnd.size()));
    }
    if(opnd.is_mem()) {
        if(opnd.scale() != 0) {
            sz += snprintf(&strbuf[sz], len-sz, "%d(%s,%s,%d)", opnd.disp(),
                           getRegNameString(opnd.base()),
                           getRegNameString(opnd.index()), opnd.scale());
        } else {
            sz += snprintf(&strbuf[sz], len-sz, "%d(%s)",
                           opnd.disp(), getRegNameString(opnd.base()));
        }
    } else if(opnd.is_imm()) {
        sz += snprintf(&strbuf[sz], len-sz, "#%x", (int)opnd.imm());
    } else if(opnd.is_reg()) {
        sz += snprintf(&strbuf[sz], len-sz, "%s",
                       getRegNameString(opnd.reg()));
    }
    return sz;
}

void DisassembleInstToBuf(Inst& decInst, char* strbuf, unsigned int len)
{
    unsigned int sz = 0;
    int k;
    sz += snprintf(&strbuf[sz], len-sz, "%s ", EncoderBase::toStr(decInst.mn));
    if (decInst.argc > 0) {
        sz += DisassembleOperandToBuf(decInst.operands[decInst.argc-1],
                                 &strbuf[sz], len-sz);
        for(k = decInst.argc-2; k >= 0; k--) {
            sz += snprintf(&strbuf[sz], len-sz, ", ");
            sz += DisassembleOperandToBuf(decInst.operands[k], &strbuf[sz], len-sz);
        }
    }
}

extern "C" ENCODER_DECLARE_EXPORT
char* decoder_disassemble_instr(char* stream, char* strbuf, unsigned int len)
{
    Inst decInst;
    unsigned numBytes = DecoderBase::decode(stream, &decInst);
    DisassembleInstToBuf(decInst, strbuf, len);
    return (stream + numBytes);
}
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdio.h>
#include <assert.h>
#include <limits.h>
#include "enc_base.h"
#include "enc_wrapper.h"
#include "dec_base.h"
#include "utils/Log.h"

//#define PRINT_ENCODER_STREAM
bool dump_x86_inst = false;
//map_reg
const RegName map_of_regno_2_regname[] = {
    RegName_EAX,    RegName_EBX,    RegName_ECX,    RegName_EDX,
    RegName_EDI,    RegName_ESI,    RegName_ESP,    RegName_EBP,
    RegName_XMM0,   RegName_XMM1,   RegName_XMM2,   RegName_XMM3,
    RegName_XMM4,   RegName_XMM5,   RegName_XMM6,   RegName_XMM7,
    RegName_Null,   RegName_Null,   RegName_Null,   RegName_Null,
    RegName_Null,   RegName_Null,   RegName_Null,   RegName_Null,
    RegName_Null,
    RegName_Null,   RegName_Null,   RegName_Null,   RegName_Null,   RegName_Null,
    RegName_Null,   RegName_Null,   RegName_Null,   RegName_Null,
    RegName_Null,   RegName_Null,   RegName_Null,   RegName_Null,
    RegName_Null,   RegName_Null,   //SCRATCH
    RegName_Null,   RegName_Null,   RegName_Null,   RegName_Null
};

//getRegSize, getAliasReg:
//OpndSize, RegName, OpndExt: enum enc_defs.h
inline void add_r(EncoderBase::Operands & args, int physicalReg, OpndSize sz, OpndExt ext = OpndExt_None) {
    RegName reg = map_of_regno_2_regname[physicalReg];
    if (sz != getRegSize(reg)) {
       reg = getAliasReg(reg, sz);
    }
    args.add(EncoderBase::Operand(reg, ext));
}
inline void add_m(EncoderBase::Operands & args, int baseReg, int disp, OpndSize sz, OpndExt ext = OpndExt_None) {
    args.add(EncoderBase::Operand(sz,
                                  map_of_regno_2_regname[baseReg],
                                  RegName_Null, 0,
                                  disp, ext));
}
inline void add_m_scale(EncoderBase::Operands & args, int baseReg, int indexReg, int scale,
                        OpndSize sz, OpndExt ext = OpndExt_None) {
    args.add(EncoderBase::Operand(sz,
                                  map_of_regno_2_regname[baseReg],
                                  map_of_regno_2_regname[indexReg], scale,
                                  0, ext));
}
inline void add_m_disp_scale(EncoderBase::Operands & args, int baseReg, int disp, int indexReg, int scale,
                        OpndSize sz, OpndExt ext = OpndExt_None) {
    args.add(EncoderBase::Operand(sz,
                                  map_of_regno_2_regname[baseReg],
                                  map_of_regno_2_regname[indexReg], scale,
                                  disp, ext));
}

inline void add_fp(EncoderBase::Operands & args, unsigned i, bool dbl) {
    return args.add((RegName)( (dbl ? RegName_FP0D : RegName_FP0S) + i));
}
inline void add_imm(EncoderBase::Operands & args, OpndSize sz, int value, bool is_signed) {
    //assert(n_size != imm.get_size());
    args.add(EncoderBase::Operand(sz, value,
             is_signed ? OpndExt_Signed : OpndExt_Zero));
}

#define MAX_DECODED_STRING_LEN 1024
char tmpBuffer[MAX_DECODED_STRING_LEN];

void printOperand(const EncoderBase::Operand & opnd) {
    unsigned int sz;
    if(!dump_x86_inst) return;
    sz = strlen(tmpBuffer);
    if(opnd.size() != OpndSize_32) {
        sz += snprintf(&tmpBuffer[sz], MAX_DECODED_STRING_LEN-sz, "%s ",
                       getOpndSizeString(opnd.size()));
    }
    if(opnd.is_mem()) {
        if(opnd.scale() != 0) {
            sz += snprintf(&tmpBuffer[sz], MAX_DECODED_STRING_LEN-sz,
                           "%d(%s,%s,%d)", opnd.disp(),
                           getRegNameString(opnd.base()),
                           getRegNameString(opnd.index()), opnd.scale());
        } else {
            sz += snprintf(&tmpBuffer[sz], MAX_DECODED_STRING_LEN-sz, "%d(%s)",
                           opnd.disp(), getRegNameString(opnd.base()));
        }
    }
    if(opnd.is_imm()) {
        sz += snprintf(&tmpBuffer[sz], MAX_DECODED_STRING_LEN-sz, "#%x",
                       (int)opnd.imm());
    }
    if(opnd.is_reg()) {
        sz += snprintf(&tmpBuffer[sz], MAX_DECODED_STRING_LEN-sz, "%s",
                       getRegNameString(opnd.reg()));
    }
}
//TODO: the order of operands
//to make the printout have the same order as assembly in .S
//I reverse the order here
void printDecoderInst(Inst & decInst) {
    unsigned int sz;
    if(!dump_x86_inst) return;
    sz = strlen(tmpBuffer);
    sz += snprintf(&tmpBuffer[sz], MAX_DECODED_STRING_LEN-sz, "%s ",
                   EncoderBase::toStr(decInst.mn));
    for(unsigned int k = 0; k < decInst.argc; k++) {
        if(k > 0) {
            sz = strlen(tmpBuffer);
            sz += snprintf(&tmpBuffer[sz], MAX_DECODED_STRING_LEN-sz, ", ");
        }
        printOperand(decInst.operands[decInst.argc-1-k]);
    }
    ALOGE("%s", tmpBuffer);
}
void printOperands(EncoderBase::Operands& opnds) {
    unsigned int sz;
    if(!dump_x86_inst) return;
    for(unsigned int k = 0; k < opnds.count(); k++) {
        if(k > 0) {
            sz = strlen(tmpBuffer);
            sz += snprintf(&tmpBuffer[sz], MAX_DECODED_STRING_LEN-sz, ", ");
        }
        printOperand(opnds[opnds.count()-1-k]);
    }
}
void printEncoderInst(Mnemonic m, EncoderBase::Operands& opnds) {
    if(!dump_x86_inst) return;
    snprintf(tmpBuffer, MAX_DECODED_STRING_LEN, "--- ENC %s ",
             EncoderBase::toStr(m));
    printOperands(opnds);
    ALOGE("%s", tmpBuffer);
}
int decodeThenPrint(char* stream_start) {
    if(!dump_x86_inst) return 0;
    snprintf(tmpBuffer, MAX_DECODED_STRING_LEN, "--- INST @ %p: ",
             stream_start);
    Inst decInst;
    unsigned numBytes = DecoderBase::decode(stream_start, &decInst);
    printDecoderInst(decInst);
    return numBytes;
}

extern "C" ENCODER_DECLARE_EXPORT char * encoder_imm(Mnemonic m, OpndSize size, int imm, char * stream) {
    EncoderBase::Operands args;
    //assert(imm.get_size() == size_32);
    add_imm(args, size, imm, true/*is_signed*/);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, m, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(m, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}
extern "C" ENCODER_DECLARE_EXPORT unsigned encoder_get_inst_size(char * stream) {
    Inst decInst;
    unsigned numBytes = DecoderBase::decode(stream, &decInst);
    return numBytes;
}

extern "C" ENCODER_DECLARE_EXPORT unsigned encoder_get_cur_operand_offset(int opnd_id)
{
    return (unsigned)EncoderBase::getOpndLocation(opnd_id);
}

extern "C" ENCODER_DECLARE_EXPORT char * encoder_update_imm(int imm, char * stream) {
    Inst decInst;
    unsigned numBytes = DecoderBase::decode(stream, &decInst);
    EncoderBase::Operands args;
    //assert(imm.get_size() == size_32);
    add_imm(args, decInst.operands[0].size(), imm, true/*is_signed*/);
    char* stream_next = (char *)EncoderBase::encode(stream, decInst.mn, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(decInst.mn, args);
    decodeThenPrint(stream);
#endif
    return stream_next;
}
extern "C" ENCODER_DECLARE_EXPORT char * encoder_mem(Mnemonic m, OpndSize size,
               int disp, int base_reg, bool isBasePhysical, char * stream) {
    EncoderBase::Operands args;
    add_m(args, base_reg, disp, size);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, m, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(m, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}
extern "C" ENCODER_DECLARE_EXPORT char * encoder_reg(Mnemonic m, OpndSize size,
               int reg, bool isPhysical, LowOpndRegType type, char * stream) {
    EncoderBase::Operands args;
    if(m == Mnemonic_IDIV || m == Mnemonic_MUL || m == Mnemonic_IMUL) {
      add_r(args, 0/*eax*/, size);
      add_r(args, 3/*edx*/, size);
    }
    add_r(args, reg, size);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, m, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(m, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}
//both operands have same size
extern "C" ENCODER_DECLARE_EXPORT char * encoder_reg_reg(Mnemonic m, OpndSize size,
                   int reg, bool isPhysical,
                   int reg2, bool isPhysical2, LowOpndRegType type, char * stream) {
    if((m == Mnemonic_MOV || m == Mnemonic_MOVQ) && reg == reg2) return stream;
    EncoderBase::Operands args;
    add_r(args, reg2, size); //destination
    if(m == Mnemonic_SAL || m == Mnemonic_SHR || m == Mnemonic_SHL || m == Mnemonic_SAR)
      add_r(args, reg, OpndSize_8);
    else
      add_r(args, reg, size);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, m, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(m, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}
extern "C" ENCODER_DECLARE_EXPORT char * encoder_mem_reg(Mnemonic m, OpndSize size,
                   int disp, int base_reg, bool isBasePhysical,
                   int reg, bool isPhysical, LowOpndRegType type, char * stream) {
    EncoderBase::Operands args;
    add_r(args, reg, size);
    add_m(args, base_reg, disp, size);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, m, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(m, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}
extern "C" ENCODER_DECLARE_EXPORT char * encoder_mem_scale_reg(Mnemonic m, OpndSize size,
                         int base_reg, bool isBasePhysical, int index_reg, bool isIndexPhysical, int scale,
                         int reg, bool isPhysical, LowOpndRegType type, char * stream) {
    EncoderBase::Operands args;
    add_r(args, reg, size);
    add_m_scale(args, base_reg, index_reg, scale, size);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, m, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(m, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}
extern "C" ENCODER_DECLARE_EXPORT char * encoder_reg_mem_scale(Mnemonic m, OpndSize size,
                         int reg, bool isPhysical,
                         int base_reg, bool isBasePhysical, int index_reg, bool isIndexPhysical, int scale,
                         LowOpndRegType type, char * stream) {
    EncoderBase::Operands args;
    add_m_scale(args, base_reg, index_reg, scale, size);
    add_r(args, reg, size);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, m, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(m, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}
extern "C" ENCODER_DECLARE_EXPORT char * encoder_mem_disp_scale_reg(Mnemonic m, OpndSize size,
                         int base_reg, bool isBasePhysical, int disp, int index_reg, bool isIndexPhysical, int scale,
                         int reg, bool isPhysical, LowOpndRegType type, char * stream) {
    EncoderBase::Operands args;
    add_r(args, reg, size);
    add_m_disp_scale(args, base_reg, disp, index_reg, scale, size);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, m, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(m, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}
extern "C" ENCODER_DECLARE_EXPORT char * encoder_movzs_mem_disp_scale_reg(Mnemonic m, OpndSize size,
                         int base_reg, bool isBasePhysical, int disp, int index_reg, bool isIndexPhysical, int scale,
                         int reg, bool isPhysical, LowOpndRegType type, char * stream) {
    EncoderBase::Operands args;
    add_r(args, reg, OpndSize_32);
    add_m_disp_scale(args, base_reg, disp, index_reg, scale, size);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, m, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(m, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}

extern "C" ENCODER_DECLARE_EXPORT char* encoder_reg_mem_disp_scale(Mnemonic m, OpndSize size,
                         int reg, bool isPhysical,
                         int base_reg, bool isBasePhysical, int disp, int index_reg, bool isIndexPhysical, int scale,
                         LowOpndRegType type, char* stream) {
    EncoderBase::Operands args;
    add_m_disp_scale(args, base_reg, disp, index_reg, scale, size);
    add_r(args, reg, size);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, m, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(m, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}

extern "C" ENCODER_DECLARE_EXPORT char * encoder_reg_mem(Mnemonic m, OpndSize size,
                   int reg, bool isPhysical,
                   int disp, int base_reg, bool isBasePhysical, LowOpndRegType type, char * stream) {
    EncoderBase::Operands args;
    add_m(args, base_reg, disp, size);
    add_r(args, reg, size);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, m, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(m, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}
extern "C" ENCODER_DECLARE_EXPORT char * encoder_imm_reg(Mnemonic m, OpndSize size,
                   int imm, int reg, bool isPhysical, LowOpndRegType type, char * stream) {
    EncoderBase::Operands args;
    add_r(args, reg, size); //dst
    if(m == Mnemonic_IMUL) add_r(args, reg, size); //src CHECK
    if(m == Mnemonic_SAL || m == Mnemonic_SHR || m == Mnemonic_SHL
       || m == Mnemonic_SAR || m == Mnemonic_ROR)  //fix for shift opcodes
      add_imm(args, OpndSize_8, imm, true/*is_signed*/);
    else
      add_imm(args, size, imm, true/*is_signed*/);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, m, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(m, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}
extern "C" ENCODER_DECLARE_EXPORT char * encoder_update_imm_rm(int imm, char * stream) {
    Inst decInst;
    unsigned numBytes = DecoderBase::decode(stream, &decInst);
    EncoderBase::Operands args;
    args.add(decInst.operands[0]);
    add_imm(args, decInst.operands[1].size(), imm, true/*is_signed*/);
    char* stream_next = (char *)EncoderBase::encode(stream, decInst.mn, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(decInst.mn, args);
    decodeThenPrint(stream);
#endif
    return stream_next;
}
extern "C" ENCODER_DECLARE_EXPORT char * encoder_imm_mem(Mnemonic m, OpndSize size,
                   int imm,
                   int disp, int base_reg, bool isBasePhysical, char * stream) {
    EncoderBase::Operands args;
    add_m(args, base_reg, disp, size);
    if (m == Mnemonic_SAL || m == Mnemonic_SHR || m == Mnemonic_SHL
        || m == Mnemonic_SAR || m == Mnemonic_ROR)
        size = OpndSize_8;
    add_imm(args, size, imm, true);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, m, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(m, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}
extern "C" ENCODER_DECLARE_EXPORT char * encoder_fp_mem(Mnemonic m, OpndSize size, int reg,
                  int disp, int base_reg, bool isBasePhysical, char * stream) {
    EncoderBase::Operands args;
    add_m(args, base_reg, disp, size);
    // a fake FP register as operand
    add_fp(args, reg, size == OpndSize_64/*is_double*/);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, m, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(m, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}
extern "C" ENCODER_DECLARE_EXPORT char * encoder_mem_fp(Mnemonic m, OpndSize size,
                  int disp, int base_reg, bool isBasePhysical,
                  int reg, char * stream) {
    EncoderBase::Operands args;
    // a fake FP register as operand
    add_fp(args, reg, size == OpndSize_64/*is_double*/);
    add_m(args, base_reg, disp, size);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, m, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(m, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}

extern "C" ENCODER_DECLARE_EXPORT char * encoder_return(char * stream) {
    EncoderBase::Operands args;
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, Mnemonic_RET, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(Mnemonic_RET, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}
extern "C" ENCODER_DECLARE_EXPORT char * encoder_compare_fp_stack(bool pop, int reg, bool isDouble, char * stream) {
    //Mnemonic m = pop ? Mnemonic_FUCOMP : Mnemonic_FUCOM;
    Mnemonic m = pop ? Mnemonic_FUCOMIP : Mnemonic_FUCOMI;
    //a single operand or 2 operands?
    //FST ST(i) has a single operand in encoder.inl?
    EncoderBase::Operands args;
    add_fp(args, reg, isDouble);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, m, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(m, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}
extern "C" ENCODER_DECLARE_EXPORT char * encoder_movez_mem_to_reg(OpndSize size,
                      int disp, int base_reg, bool isBasePhysical,
                      int reg, bool isPhysical, char * stream) {
    EncoderBase::Operands args;
    add_r(args, reg, OpndSize_32);
    add_m(args, base_reg, disp, size);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, Mnemonic_MOVZX, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(Mnemonic_MOVZX, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}
extern "C" ENCODER_DECLARE_EXPORT char * encoder_moves_mem_to_reg(OpndSize size,
                      int disp, int base_reg, bool isBasePhysical,
                      int reg, bool isPhysical, char * stream) {
    EncoderBase::Operands args;
    add_r(args, reg, OpndSize_32);
    add_m(args, base_reg, disp, size);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, Mnemonic_MOVSX, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(Mnemonic_MOVSX, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}
extern "C" ENCODER_DECLARE_EXPORT char * encoder_movez_reg_to_reg(OpndSize size,
                      int reg, bool isPhysical, int reg2,
                      bool isPhysical2, LowOpndRegType type, char * stream) {
    EncoderBase::Operands args;
    add_r(args, reg2, OpndSize_32); //destination
    add_r(args, reg, size);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, Mnemonic_MOVZX, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(Mnemonic_MOVZX, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}
extern "C" ENCODER_DECLARE_EXPORT char * encoder_moves_reg_to_reg(OpndSize size,
                      int reg, bool isPhysical,int reg2,
                      bool isPhysical2, LowOpndRegType type, char * stream) {
    EncoderBase::Operands args;
    add_r(args, reg2, OpndSize_32); //destination
    add_r(args, reg, size);
    char* stream_start = stream;
    stream = (char *)EncoderBase::encode(stream, Mnemonic_MOVSX, args);
#ifdef PRINT_ENCODER_STREAM
    printEncoderInst(Mnemonic_MOVSX, args);
    decodeThenPrint(stream_start);
#endif
    return stream;
}

// Disassemble the operand "opnd" and put the readable format in "strbuf"
// up to a string length of "len".
unsigned int DisassembleOperandToBuf(const EncoderBase::Operand& opnd, char* strbuf, unsigned int len)
{
    unsigned int sz = 0;
    if(opnd.size() != OpndSize_32) {
        sz += snprintf(&strbuf[sz], len-sz, "%s ",
                       getOpndSizeString(opnd.size()));
    }
    if(opnd.is_mem()) {
        if(opnd.scale() != 0) {
            sz += snprintf(&strbuf[sz], len-sz, "%d(%s,%s,%d)", opnd.disp(),
                           getRegNameString(opnd.base()),
                           getRegNameString(opnd.index()), opnd.scale());
        } else {
            sz += snprintf(&strbuf[sz], len-sz, "%d(%s)",
                           opnd.disp(), getRegNameString(opnd.base()));
        }
    } else if(opnd.is_imm()) {
        sz += snprintf(&strbuf[sz], len-sz, "#%x", (int)opnd.imm());
    } else if(opnd.is_reg()) {
        sz += snprintf(&strbuf[sz], len-sz, "%s",
                       getRegNameString(opnd.reg()));
    }
    return sz;
}

// Disassemble the instruction "decInst" and put the readable format
// in "strbuf" up to a string length of "len".
void DisassembleInstToBuf(Inst& decInst, char* strbuf, unsigned int len)
{
    unsigned int sz = 0;
    int k;
    sz += snprintf(&strbuf[sz], len-sz, "%s ", EncoderBase::toStr(decInst.mn));
    if (decInst.argc > 0) {
        sz += DisassembleOperandToBuf(decInst.operands[decInst.argc-1],
                                 &strbuf[sz], len-sz);
        for(k = decInst.argc-2; k >= 0; k--) {
            sz += snprintf(&strbuf[sz], len-sz, ", ");
            sz += DisassembleOperandToBuf(decInst.operands[k], &strbuf[sz], len-sz);
        }
    }
}

// Disassmble the x86 instruction pointed to by code pointer "stream."
// Put the disassemble text in the "strbuf" up to string length "len".
// Return the code pointer after the disassemble x86 instruction.
extern "C" ENCODER_DECLARE_EXPORT
char* decoder_disassemble_instr(char* stream, char* strbuf, unsigned int len)
{
    Inst decInst;
    unsigned numBytes = DecoderBase::decode(stream, &decInst);
    DisassembleInstToBuf(decInst, strbuf, len);
    return (stream + numBytes);
}
