//===-- XCoreInstPrinter.cpp - Convert XCore MCInst to assembly syntax --------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This class prints an XCore MCInst to a .s file.
//
//===----------------------------------------------------------------------===//

/* Capstone Disassembly Engine */
/* By Nguyen Anh Quynh <aquynh@gmail.com>, 2013-2014 */

#ifdef CAPSTONE_HAS_XCORE

#if defined (WIN32) || defined (WIN64) || defined (_WIN32) || defined (_WIN64)
#pragma warning(disable : 4996)			// disable MSVC's warning on strcpy()
#pragma warning(disable : 28719)		// disable MSVC's warning on strcpy()
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <platform.h>

#include "XCoreInstPrinter.h"
#include "../../MCInst.h"
#include "../../utils.h"
#include "../../SStream.h"
#include "../../MCRegisterInfo.h"
#include "../../MathExtras.h"
#include "XCoreMapping.h"

static const char *getRegisterName(unsigned RegNo);

void XCore_post_printer(csh ud, cs_insn *insn, char *insn_asm, MCInst *mci)
{
	/*
	   if (((cs_struct *)ud)->detail != CS_OPT_ON)
	   return;
	 */
}

// stw sed, sp[3]
void XCore_insn_extract(MCInst *MI, const char *code)
{
	int id;
	char *p, *p2;
	char tmp[128];

	strcpy(tmp, code); // safe because code is way shorter than 128 bytes

	// find the first space
	p = strchr(tmp, ' ');
	if (p) {
		p++;
		// find the next ','
		p2 = strchr(p, ',');
		if (p2) {
			*p2 = '\0';
			id = XCore_reg_id(p);
			if (id) {
				// register
				if (MI->csh->detail) {
					MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].type = XCORE_OP_REG;
					MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].reg = id;
					MI->flat_insn->detail->xcore.op_count++;
				}
			}
			// next should be register, or memory?
			// skip space
			p2++;
			while(*p2 && *p2 == ' ')
				p2++;
			if (*p2) {
				// find '['
				p = p2;
				while(*p && *p != '[')
					p++;
				if (*p) {
					// this is '['
					*p = '\0';
					id = XCore_reg_id(p2);
					if (id) {
						// base register
						if (MI->csh->detail) {
							MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].type = XCORE_OP_MEM;
							MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].mem.base = (uint8_t)id;
							MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].mem.index = XCORE_REG_INVALID;
							MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].mem.disp = 0;
							MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].mem.direct = 1;
						}

						p++;
						p2 = p;
						// until ']'
						while(*p && *p != ']')
							p++;
						if (*p) {
							*p = '\0';
							// p2 is either index, or disp
							id = XCore_reg_id(p2);
							if (id) {
								// index register
								if (MI->csh->detail) {
									MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].mem.index = (uint8_t)id;
								}
							} else {
								// a number means disp
								if (MI->csh->detail) {
									MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].mem.disp = atoi(p2);
								}
							}
						}

						if (MI->csh->detail) {
							MI->flat_insn->detail->xcore.op_count++;
						}
					}
				} else {
					// a register?
					id = XCore_reg_id(p2);
					if (id) {
						// register
						if (MI->csh->detail) {
							MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].type = XCORE_OP_REG;
							MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].reg = id;
							MI->flat_insn->detail->xcore.op_count++;
						}
					}
				}
			}
		} else {
			id = XCore_reg_id(p);
			if (id) {
				// register
				if (MI->csh->detail) {
					MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].type = XCORE_OP_REG;
					MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].reg = id;
					MI->flat_insn->detail->xcore.op_count++;
				}
			}
		}
	}
}

static void set_mem_access(MCInst *MI, bool status, int reg)
{
	if (MI->csh->detail != CS_OPT_ON)
		return;

	MI->csh->doing_mem = status;
	if (status) {
		if (reg != 0xffff && reg != -0xffff) {
			MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].type = XCORE_OP_MEM;
			if (reg) {
				MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].mem.base = (uint8_t)reg;
			} else {
				MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].mem.base = XCORE_REG_INVALID;
			}
			MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].mem.index = XCORE_REG_INVALID;
			MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].mem.disp = 0;
			MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].mem.direct = 1;
		} else {
			// the last op should be the memory base
			MI->flat_insn->detail->xcore.op_count--;
			MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].type = XCORE_OP_MEM;
			MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].mem.base = (uint8_t)MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].reg;
			MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].mem.index = XCORE_REG_INVALID;
			MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].mem.disp = 0;
			if (reg > 0)
				MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].mem.direct = 1;
			else
				MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].mem.direct = -1;
		}
	} else {
		if (reg) {
			MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].mem.index = (uint8_t)reg;
			// done, create the next operand slot
			MI->flat_insn->detail->xcore.op_count++;
		}
	}
}

static void _printOperand(MCInst *MI, MCOperand *MO, SStream *O)
{
	if (MCOperand_isReg(MO)) {
		unsigned reg;

		reg = MCOperand_getReg(MO);
		SStream_concat0(O, getRegisterName(reg));

		if (MI->csh->detail) {
			if (MI->csh->doing_mem) {
				if (MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].mem.base == ARM_REG_INVALID)
					MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].mem.base = (uint8_t)reg;
				else
					MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].mem.index = (uint8_t)reg;
			} else {
				MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].type = XCORE_OP_REG;
				MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].reg = reg;
				MI->flat_insn->detail->xcore.op_count++;
			}
		}
	} else if (MCOperand_isImm(MO)) {
		int32_t Imm = (int32_t)MCOperand_getImm(MO);

		if (Imm >= 0) {
			if (Imm > HEX_THRESHOLD)
				SStream_concat(O, "0x%x", Imm);
			else
				SStream_concat(O, "%u", Imm);
		} else {
			if (Imm < -HEX_THRESHOLD)
				SStream_concat(O, "-0x%x", -Imm);
			else
				SStream_concat(O, "-%u", -Imm);
		}

		if (MI->csh->detail) {
			if (MI->csh->doing_mem) {
				MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].mem.disp = Imm;
			} else {
				MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].type = XCORE_OP_IMM;
				MI->flat_insn->detail->xcore.operands[MI->flat_insn->detail->xcore.op_count].imm = Imm;
				MI->flat_insn->detail->xcore.op_count++;
			}
		}
	}
}

static void printOperand(MCInst *MI, int OpNum, SStream *O)
{
	if (OpNum >= MI->size)
		return;

	_printOperand(MI, MCInst_getOperand(MI, OpNum), O);
}

static void printInlineJT(MCInst *MI, int OpNum, SStream *O)
{
}

static void printInlineJT32(MCInst *MI, int OpNum, SStream *O)
{
}

#define PRINT_ALIAS_INSTR
#include "XCoreGenAsmWriter.inc"

void XCore_printInst(MCInst *MI, SStream *O, void *Info)
{
	printInstruction(MI, O, Info);
	set_mem_access(MI, false, 0);
}

#endif
