#include "operand.hpp"
#include "../ex.hpp"
#include "insts.hpp"

static const char* regs[] = {"al", "cl", "dl", "bl", "ah",  "ch",  "dh",  "bh",  "ax",  "cx",  "dx",  "bx",
							 "sp", "bp", "si", "di", "eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi"};

static void opError()
{
	throw BlitzException("error in operand");
}

static void sizeError()
{
	throw BlitzException("illegal operand size");
}

Operand::Operand() : mode(NONE), reg(-1), imm(0), offset(0), baseReg(-1), indexReg(-1), shift(0) {}

Operand::Operand(const std::string& s)
	: mode(NONE), reg(-1), imm(0), offset(0), baseReg(-1), indexReg(-1), shift(0), s(s)
{}

bool Operand::parseSize(int* sz)
{
	if (!s.size())
		return false;
	if (s.find("byte ") == 0) {
		*sz = 1;
		s   = s.substr(5);
	} else if (s.find("word ") == 0) {
		*sz = 2;
		s   = s.substr(5);
	} else if (s.find("dword ") == 0) {
		*sz = 4;
		s   = s.substr(6);
	} else
		return false;

	return true;
}

bool Operand::parseChar(char c)
{
	if (!s.size() || s[0] != c)
		return false;
	s = s.substr(1);
	return true;
}

bool Operand::parseReg(int* reg)
{
	int i;
	for (i = 0; i < s.size() && isalpha(s[i]); ++i) {
	}
	if (!i)
		return false;
	std::string t = s.substr(0, i);
	for (int j = 0; j < 24; ++j) {
		if (t == regs[j]) {
			*reg = j;
			s    = s.substr(i);
			return true;
		}
	}
	return false;
}

bool Operand::parseFPReg(int* reg)
{
	//eg: st(0)
	if (s.size() < 5)
		return false;
	if (s[0] != 's' || s[1] != 't' || s[2] != '(' || s[4] != ')')
		return false;
	if (s[3] < '0' || s[3] > '7')
		return false;
	*reg = s[3] - '0';
	s    = s.substr(5);
	return true;
}

bool Operand::parseLabel(std::string* label)
{
	if (!s.size() || (!isalpha(s[0]) && s[0] != '_'))
		return false;
	int i;
	for (i = 1; i < s.size() && (isalnum(s[i]) || s[i] == '_'); ++i) {
	}
	*label = s.substr(0, i);
	s      = s.substr(i);
	return true;
}

bool Operand::parseConst(int* iconst)
{
	int i, sgn = s.size() && (s[0] == '-' || s[0] == '+');
	for (i = sgn; i < s.size() && isdigit(s[i]); ++i) {
	}
	if (i == sgn)
		return false;
	int n   = atoi(s.c_str());
	*iconst = n;
	s       = s.substr(i);
	return true;
}

void Operand::parse()
{
	if (!s.size())
		return;

	int sz;
	if (!parseSize(&sz))
		sz = 0;

	if (s[0] != '[') {
		int r;
		if (parseReg(&r)) {
			mode = REG | R_M;
			if (r < 8) {
				if (sz && sz != 1)
					sizeError();
				mode |= REG8 | R_M8;
				if (r == 0)
					mode |= AL;
				else if (r == 1)
					mode |= CL;
			} else if (r < 16) {
				if (sz && sz != 2)
					sizeError();
				mode |= REG16 | R_M16;
				if (r == 8)
					mode |= AX;
				else if (r == 9)
					mode |= CX;
			} else {
				if (sz && sz != 4)
					sizeError();
				mode |= REG32 | R_M32;
				if (r == 16)
					mode |= EAX;
				else if (r == 17)
					mode |= ECX;
			}
			reg = r & 7;
		} else if (parseFPReg(&r)) {
			mode = FPUREG;
			if (!r)
				mode |= ST0;
			reg = r;
		} else if (parseLabel(&immLabel)) {
			if (sz && sz != 4)
				sizeError();
			mode = IMM | IMM32;
		} else if (parseConst(&imm)) {
			mode = IMM;
			if (sz == 1)
				mode |= IMM8;
			else if (sz == 2)
				mode |= IMM16;
			else
				mode |= IMM32;
		} else
			opError();
		if (s.size())
			opError();
		return;
	}

	if (s[s.size() - 1] != ']')
		opError();
	s = s.substr(1, s.size() - 2);

	mode = MEM | R_M;
	if (sz == 1)
		mode |= MEM8 | R_M8;
	else if (sz == 2)
		mode |= MEM16 | R_M16;
	else
		mode |= MEM32 | R_M32;

	for (;;) {
		int         n;
		std::string l;
		if (parseReg(&n)) {
			if (n < 16)
				throw BlitzException("register must be 32 bit");
			n &= 7;
			if (parseChar('*')) {
				if (n == 4)
					break; //esp cannot be index reg!
				if (indexReg >= 0)
					break;
				if (parseChar('1'))
					shift = 0;
				else if (parseChar('2'))
					shift = 1;
				else if (parseChar('4'))
					shift = 2;
				else if (parseChar('8'))
					shift = 3;
				else
					break;
				indexReg = n;
			} else {
				if (baseReg < 0)
					baseReg = n;
				else if (indexReg < 0) {
					indexReg = n;
				} else
					break;
			}
		} else if (parseLabel(&l)) {
			if (baseLabel.size())
				opError();
			baseLabel = l;
		} else if (parseConst(&n)) {
			offset += n;
		} else
			break;
		if (!s.size())
			return;
	}
	opError();
}
