/*
Copyright (C) 2006 Evan Teran
                   eteran@alum.rit.edu

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

#ifndef OPERAND_20080421_TCC_
#define OPERAND_20080421_TCC_

#include "Instruction.h"
#include <sstream>
#include <limits>
#include <algorithm>
#include <iomanip>
#include <functional>
#include <cstring>

template <int N>
const char *Operand<N>::RegisterName[] = {
	"",

	"rax",	"rcx",	"rdx",	"rbx",
	"rsp",	"rbp",	"rsi",	"rdi",
	"r8",	"r9",	"r10",	"r11",
	"r12",	"r13",	"r14",	"r15",
	
	"eax",	"ecx",	"edx",	"ebx",
	"esp",	"ebp",	"esi",	"edi",
	"r8d",	"r9d",	"r10d",	"r11d",
	"r12d",	"r13d",	"r14d",	"r15d",
	
	"ax",	"cx",	"dx",	"bx",
	"sp",	"bp",	"si",	"di",
	"r8w",	"r9w",	"r10w",	"r11w",
	"r12w",	"r13w",	"r14w",	"r15w",
	
	"al",	"cl",	"dl",	"bl",
	"ah",	"ch",	"dh",	"bh",
	"r8b",	"r9b",	"r10b",	"r11b",
	"r12b",	"r13b",	"r14b",	"r15b",
	
	"es",	"cs",	"ss",	"ds",
	"fs",	"gs",	"seg7",	"seg8",
	
	"cr0",	"cr1",	"cr2",	"cr3",
	"cr4",	"cr5",	"cr6",	"cr7",
	"cr8",	"cr9",	"cr10",	"cr11",
	"cr12",	"cr13",	"cr14",	"cr15",
	
	"dr0",	"dr1",	"dr2",	"dr3",
	"dr4",	"dr5",	"dr6",	"dr7",
	"dr8",	"dr9",	"dr10",	"dr11",
	"dr12",	"dr13",	"dr14",	"dr15",
	
	"tr0",	"tr1",	"tr2",	"tr3",
	"tr4",	"tr5",	"tr6",	"tr7",
	
	"mm0",	"mm1",	"mm2",	"mm3",
	"mm4",	"mm5",	"mm6",	"mm7",
	
	"xmm0",	"xmm1",	"xmm2",	"xmm3",
	"xmm4",	"xmm5",	"xmm6",	"xmm7",
	"xmm8",	"xmm9",	"xmm10","xmm11",
	"xmm12","xmm13","xmm14","xmm15",
		
	"st",
	"st(0)", "st(1)", "st(2)", "st(3)",
	"st(4)", "st(5)", "st(6)", "st(7)",
	
	"rip",
	"eip",
	
	"(invalid)"
};

//------------------------------------------------------------------------------
// Name: Operand()
//------------------------------------------------------------------------------
template <int N>
Operand<N>::Operand() {
	// i think this is the biggest member of the union
	std::memset(&expression, 0, sizeof(expression));
}

//------------------------------------------------------------------------------
// Name: formatImmediate() const
//------------------------------------------------------------------------------
template <int N>
std::string Operand<N>::formatImmediate() const {
	std::ostringstream ss;
		
	switch(m_Type) {
	case TYPE_IMMEDIATE64:
		if(qword < std::numeric_limits<uint32_t>::max()) {
			// this will lead to a fall through, we can print smaller
		} else {
			if(sqword > -127 && sqword < 128) {
				ss << sqword;
			} else {
				ss << outputHexString(sqword);
			}
			break;
		}
		// FALL THROUGH
	case TYPE_IMMEDIATE32:	
		if(dword < std::numeric_limits<uint16_t>::max()) {
			// this will lead to a fall through, we can print smaller
		} else {
			if(sdword > -127 && sdword < 128) {
				ss << sdword;
			} else {
				ss << outputHexString(sdword);
			}
			break;
		}
		// FALL THROUGH
	case TYPE_IMMEDIATE16:
		if(word < std::numeric_limits<uint8_t>::max()) {
			// this will lead to a fall through, we can print smaller
		} else {		
			if(sword > -127 && sword < 128) {
				ss << sword;
			} else {
				ss << outputHexString(sword);
			}
			break;
		}
		// FALL THROUGH
	case TYPE_IMMEDIATE8:
		if(sbyte & 0x80) {
			ss << outputHexString(byte);
		} else {
			ss << static_cast<int>(sbyte);
		}
		break;
	default:
		break;
	}
	
	return ss.str();
}

//------------------------------------------------------------------------------
// Name: relativeTarget() const
//------------------------------------------------------------------------------
template <int N>
typename Operand<N>::address_t Operand<N>::relativeTarget() const {
	address_t ret = 0;
	
	const address_t rva		= m_Owner->rva();
	const unsigned int size	= m_Owner->size();
	const address_t offset	= rva + size;
	
	switch(m_Type) {
	case TYPE_REL8:
		ret = static_cast<address_t>(sbyte + offset);
		break;
	case TYPE_REL16:
		// NOTE: intel truncates EIP to 16-bit here
		ret = static_cast<address_t>((sword + offset) & 0xffff);
		break;
	case TYPE_REL32:
		ret = static_cast<address_t>(sdword + offset);
		break;
	case TYPE_REL64:
		ret = static_cast<address_t>(sqword + offset);
		break;
	default:
		break;
	}
	
	return ret;
}

//------------------------------------------------------------------------------
// Name: formatRelative() const
//------------------------------------------------------------------------------
template <int N>
std::string Operand<N>::formatRelative() const {
	return outputHexString(static_cast<address_t>(relativeTarget()));
}

//------------------------------------------------------------------------------
// Name: formatExpression() const
//------------------------------------------------------------------------------
template <int N>
std::string Operand<N>::formatExpression() const {
	std::ostringstream ss;

	static const char *expressionStrings[] = {
		"",
		"byte ptr ",
		"word ptr ",
		"dword ptr ",
		"fword ptr ",
		"qword ptr ",
		"tbyte ptr ",
		"xmmword ptr ",
	};

	ss << expressionStrings[m_Type - TYPE_EXPRESSION];
	
	const uint32_t prefix = m_Owner->prefix();

	if(prefix & instruction_t::PREFIX_CS) {
		ss << "cs:";
	} else if(prefix & instruction_t::PREFIX_SS) {
		ss << "ss:";
	} else if(prefix & instruction_t::PREFIX_DS) {
		ss << "ds:";
	} else if(prefix & instruction_t::PREFIX_ES) {
		ss << "es:";
	} else if(prefix & instruction_t::PREFIX_FS) {
		ss << "fs:";
	} else if(prefix & instruction_t::PREFIX_GS) {
		ss << "gs:";
	}

	bool only_disp = true;

	ss << '[';

	// the base, if any
	if(expression.base != REG_NULL) {
		ss << registerName(expression.base);
		only_disp = false;
	}

	// the index, if any
	if(expression.index != REG_NULL) {
		if(!only_disp) {
			ss << '+';
		}
		ss << registerName(expression.index);
		only_disp = false;
		
		// the scale, if any
		if(expression.scale != 1) {
			ss << '*' << static_cast<int>(expression.scale);
		}
	}

	// the displacement, if any
	switch(expression.displacement_type) {
	case DISP_U32:
		if(expression.u_disp32 <= std::numeric_limits<uint16_t>::max()) {
			// this will lead to a fall through, we can print smaller
		} else {
			if(!only_disp) {
				ss << '+';
			}
			ss << outputHexString(expression.u_disp32);
			break;
		}
		// FALL THROUGH
	case DISP_U16:
		if(expression.u_disp16 <= std::numeric_limits<uint8_t>::max()) {
			// this will lead to a fall through, we can print smaller
		} else {
			if(!only_disp) {
				ss << '+';
			}
			ss << outputHexString(expression.u_disp16);
			break;
		}
		// FALL THROUGH
	case DISP_U8:
		if(expression.u_disp8 != 0 || only_disp) {
			if(!only_disp) {
				ss << '+';
			}
			ss << outputHexString(expression.u_disp8);
		}
		break;

	case DISP_S32:
		if(expression.s_disp32 <= std::numeric_limits<int16_t>::max() && expression.s_disp32 >= std::numeric_limits<int16_t>::min()) {
			// this will lead to a fall through, we can print smaller
		} else {
			if(only_disp) {
				// we only have a displacement, so we wanna display in hex since it is likely an address
				ss << outputHexString(expression.s_disp32);		
			} else {
				//ss << std::showpos << static_cast<int32_t>(expression.s_disp32);
				ss << '+';
				ss << outputHexString(expression.s_disp32);		
			}
			break;
		}
		// FALL THROUGH
	case DISP_S16:
		if(expression.s_disp16 <= std::numeric_limits<int8_t>::max() && expression.s_disp16 >= std::numeric_limits<int8_t>::min()) {
			// this will lead to a fall through, we can print smaller
		} else {
			if(only_disp) {
				// we only have a displacement, so we wanna display in hex since it is likely an address
				ss << outputHexString(expression.s_disp16);		
			} else {
				ss << std::showpos << static_cast<int32_t>(expression.s_disp16);
			}
			break;
		}
		// FALL THROUGH
	case DISP_S8:
		if(expression.s_disp8 != 0 || only_disp) {
			if(only_disp) {
				// we only have a displacement, so we wanna display in hex since it is likely an address
				ss << outputHexString(expression.s_disp8);		
			} else {
				ss << std::showpos << static_cast<int32_t>(expression.s_disp8);
			}
		}
		break;

	default:
		break;
	}
	ss << ']';
	
	return ss.str();
}

//------------------------------------------------------------------------------
// Name: formatAbsolute() const
//------------------------------------------------------------------------------
template <int N>
std::string Operand<N>::formatAbsolute() const {
	std::ostringstream ss;
	ss << "far " << outputHexString(absolute.seg) << ':' << outputHexString(absolute.offset);
	return ss.str();
}

//------------------------------------------------------------------------------
// Name: formatRegister() const
//------------------------------------------------------------------------------
template <int N>
std::string Operand<N>::formatRegister() const {
	return registerName(reg);
}

//------------------------------------------------------------------------------
// Name: format() const
//------------------------------------------------------------------------------
template <int N>
std::string Operand<N>::format() const {

	switch(generalType()) {
	case TYPE_REGISTER:
		return formatRegister();
	case TYPE_IMMEDIATE:
		return formatImmediate();
	case TYPE_REL:
		return formatRelative();
	case TYPE_EXPRESSION:
		return formatExpression();
	case TYPE_ABSOLUTE:
		return formatAbsolute();
	default:
		return "(invalid)";
		throw invalid_operand(m_Owner->size());
	}
}

//------------------------------------------------------------------------------
// Name: format(bool upper) const
//------------------------------------------------------------------------------
template <int N>
std::string Operand<N>::format(bool upper) const {
	std::string ret = format();
	if(upper) {
		return edisassm::util::toupper(ret);
	}
	return ret;
}

//------------------------------------------------------------------------------
// Name: displacement() const
//------------------------------------------------------------------------------
template <int N>
int32_t Operand<N>::displacement() const {
	
	switch(expression.displacement_type) {
	case DISP_U8:
		return static_cast<int32_t>(expression.u_disp8);
	case DISP_U16:
		return static_cast<int32_t>(expression.u_disp16);
	case DISP_U32:
		return static_cast<int32_t>(expression.u_disp32);
	case DISP_S8:
		return static_cast<int32_t>(expression.s_disp8);
	case DISP_S16:
		return static_cast<int32_t>(expression.s_disp16);
	case DISP_S32:
		return static_cast<int32_t>(expression.s_disp32);
	default:
		return 0;
	}
}


//------------------------------------------------------------------------------
// Name: immediate() const
//------------------------------------------------------------------------------
template <int N>
int64_t Operand<N>::immediate() const {
	switch(m_Type) {
	case TYPE_IMMEDIATE8:
		return static_cast<int64_t>(sbyte);
	case TYPE_IMMEDIATE16:
		return static_cast<int64_t>(sword);
	case TYPE_IMMEDIATE32:
		return static_cast<int64_t>(sdword);
	case TYPE_IMMEDIATE64:
		return static_cast<int64_t>(sqword);
	default:
		return 0;
	}
}

//------------------------------------------------------------------------------
// Name: generalType() const
//------------------------------------------------------------------------------
template <int N>
typename Operand<N>::Type Operand<N>::generalType() const {
	return static_cast<Type>(static_cast<unsigned int>(m_Type) & TYPE_MASK);
}

//------------------------------------------------------------------------------
// Name: generalType() const
//------------------------------------------------------------------------------
template <int N>
template <class T>
std::string Operand<N>::outputHexString(T value) {

	// we do this so we can handle sign extension properly for the current 
	// disassembly mode
	typedef typename edisassm::util::type_from_size<BITS>::type native_t;

	std::ostringstream ss;
	if(value != 0) {
		ss << std::showbase << std::internal << std::hex << std::setw(sizeof(T) * 2 + 2) << std::setfill('0') << static_cast<native_t>(value);
	} else {
		ss << 0;
	}
	return ss.str();
}

#endif
