// Copyright 2014 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package x86asm

import (
	"fmt"
	"strings"
)

// IntelSyntax returns the Intel assembler syntax for the instruction, as defined by Intel's XED tool.
func IntelSyntax(inst Inst, pc uint64, symname SymLookup) string {
	if symname == nil {
		symname = func(uint64) (string, uint64) { return "", 0 }
	}

	var iargs []Arg
	for _, a := range inst.Args {
		if a == nil {
			break
		}
		iargs = append(iargs, a)
	}

	switch inst.Op {
	case INSB, INSD, INSW, OUTSB, OUTSD, OUTSW, LOOPNE, JCXZ, JECXZ, JRCXZ, LOOP, LOOPE, MOV, XLATB:
		if inst.Op == MOV && (inst.Opcode>>16)&0xFFFC != 0x0F20 {
			break
		}
		for i, p := range inst.Prefix {
			if p&0xFF == PrefixAddrSize {
				inst.Prefix[i] &^= PrefixImplicit
			}
		}
	}

	switch inst.Op {
	case MOV:
		dst, _ := inst.Args[0].(Reg)
		src, _ := inst.Args[1].(Reg)
		if ES <= dst && dst <= GS && EAX <= src && src <= R15L {
			src -= EAX - AX
			iargs[1] = src
		}
		if ES <= dst && dst <= GS && RAX <= src && src <= R15 {
			src -= RAX - AX
			iargs[1] = src
		}

		if inst.Opcode>>24&^3 == 0xA0 {
			for i, p := range inst.Prefix {
				if p&0xFF == PrefixAddrSize {
					inst.Prefix[i] |= PrefixImplicit
				}
			}
		}
	}

	switch inst.Op {
	case AAM, AAD:
		if imm, ok := iargs[0].(Imm); ok {
			if inst.DataSize == 32 {
				iargs[0] = Imm(uint32(int8(imm)))
			} else if inst.DataSize == 16 {
				iargs[0] = Imm(uint16(int8(imm)))
			}
		}

	case PUSH:
		if imm, ok := iargs[0].(Imm); ok {
			iargs[0] = Imm(uint32(imm))
		}
	}

	for _, p := range inst.Prefix {
		if p&PrefixImplicit != 0 {
			for j, pj := range inst.Prefix {
				if pj&0xFF == p&0xFF {
					inst.Prefix[j] |= PrefixImplicit
				}
			}
		}
	}

	if inst.Op != 0 {
		for i, p := range inst.Prefix {
			switch p &^ PrefixIgnored {
			case PrefixData16, PrefixData32, PrefixCS, PrefixDS, PrefixES, PrefixSS:
				inst.Prefix[i] |= PrefixImplicit
			}
			if p.IsREX() {
				inst.Prefix[i] |= PrefixImplicit
			}
			if p.IsVEX() {
				if p == PrefixVEX3Bytes {
					inst.Prefix[i+2] |= PrefixImplicit
				}
				inst.Prefix[i] |= PrefixImplicit
				inst.Prefix[i+1] |= PrefixImplicit
			}
		}
	}

	if isLoop[inst.Op] || inst.Op == JCXZ || inst.Op == JECXZ || inst.Op == JRCXZ {
		for i, p := range inst.Prefix {
			if p == PrefixPT || p == PrefixPN {
				inst.Prefix[i] |= PrefixImplicit
			}
		}
	}

	switch inst.Op {
	case AAA, AAS, CBW, CDQE, CLC, CLD, CLI, CLTS, CMC, CPUID, CQO, CWD, DAA, DAS,
		FDECSTP, FINCSTP, FNCLEX, FNINIT, FNOP, FWAIT, HLT,
		ICEBP, INSB, INSD, INSW, INT, INTO, INVD, IRET, IRETQ,
		LAHF, LEAVE, LRET, MONITOR, MWAIT, NOP, OUTSB, OUTSD, OUTSW,
		PAUSE, POPA, POPF, POPFQ, PUSHA, PUSHF, PUSHFQ,
		RDMSR, RDPMC, RDTSC, RDTSCP, RET, RSM,
		SAHF, STC, STD, STI, SYSENTER, SYSEXIT, SYSRET,
		UD2, WBINVD, WRMSR, XEND, XLATB, XTEST:

		if inst.Op == NOP && inst.Opcode>>24 != 0x90 {
			break
		}
		if inst.Op == RET && inst.Opcode>>24 != 0xC3 {
			break
		}
		if inst.Op == INT && inst.Opcode>>24 != 0xCC {
			break
		}
		if inst.Op == LRET && inst.Opcode>>24 != 0xcb {
			break
		}
		for i, p := range inst.Prefix {
			if p&0xFF == PrefixDataSize {
				inst.Prefix[i] &^= PrefixImplicit | PrefixIgnored
			}
		}

	case 0:
		// ok
	}

	switch inst.Op {
	case INSB, INSD, INSW, OUTSB, OUTSD, OUTSW, MONITOR, MWAIT, XLATB:
		iargs = nil

	case STOSB, STOSW, STOSD, STOSQ:
		iargs = iargs[:1]

	case LODSB, LODSW, LODSD, LODSQ, SCASB, SCASW, SCASD, SCASQ:
		iargs = iargs[1:]
	}

	const (
		haveData16 = 1 << iota
		haveData32
		haveAddr16
		haveAddr32
		haveXacquire
		haveXrelease
		haveLock
		haveHintTaken
		haveHintNotTaken
		haveBnd
	)
	var prefixBits uint32
	prefix := ""
	for _, p := range inst.Prefix {
		if p == 0 {
			break
		}
		if p&0xFF == 0xF3 {
			prefixBits &^= haveBnd
		}
		if p&(PrefixImplicit|PrefixIgnored) != 0 {
			continue
		}
		switch p {
		default:
			prefix += strings.ToLower(p.String()) + " "
		case PrefixCS, PrefixDS, PrefixES, PrefixFS, PrefixGS, PrefixSS:
			if inst.Op == 0 {
				prefix += strings.ToLower(p.String()) + " "
			}
		case PrefixREPN:
			prefix += "repne "
		case PrefixLOCK:
			prefixBits |= haveLock
		case PrefixData16, PrefixDataSize:
			prefixBits |= haveData16
		case PrefixData32:
			prefixBits |= haveData32
		case PrefixAddrSize, PrefixAddr16:
			prefixBits |= haveAddr16
		case PrefixAddr32:
			prefixBits |= haveAddr32
		case PrefixXACQUIRE:
			prefixBits |= haveXacquire
		case PrefixXRELEASE:
			prefixBits |= haveXrelease
		case PrefixPT:
			prefixBits |= haveHintTaken
		case PrefixPN:
			prefixBits |= haveHintNotTaken
		case PrefixBND:
			prefixBits |= haveBnd
		}
	}
	switch inst.Op {
	case JMP:
		if inst.Opcode>>24 == 0xEB {
			prefixBits &^= haveBnd
		}
	case RET, LRET:
		prefixBits &^= haveData16 | haveData32
	}

	if prefixBits&haveXacquire != 0 {
		prefix += "xacquire "
	}
	if prefixBits&haveXrelease != 0 {
		prefix += "xrelease "
	}
	if prefixBits&haveLock != 0 {
		prefix += "lock "
	}
	if prefixBits&haveBnd != 0 {
		prefix += "bnd "
	}
	if prefixBits&haveHintTaken != 0 {
		prefix += "hint-taken "
	}
	if prefixBits&haveHintNotTaken != 0 {
		prefix += "hint-not-taken "
	}
	if prefixBits&haveAddr16 != 0 {
		prefix += "addr16 "
	}
	if prefixBits&haveAddr32 != 0 {
		prefix += "addr32 "
	}
	if prefixBits&haveData16 != 0 {
		prefix += "data16 "
	}
	if prefixBits&haveData32 != 0 {
		prefix += "data32 "
	}

	if inst.Op == 0 {
		if prefix == "" {
			return "<no instruction>"
		}
		return prefix[:len(prefix)-1]
	}

	var args []string
	for _, a := range iargs {
		if a == nil {
			break
		}
		args = append(args, intelArg(&inst, pc, symname, a))
	}

	var op string
	switch inst.Op {
	case NOP:
		if inst.Opcode>>24 == 0x0F {
			if inst.DataSize == 16 {
				args = append(args, "ax")
			} else {
				args = append(args, "eax")
			}
		}

	case BLENDVPD, BLENDVPS, PBLENDVB:
		args = args[:2]

	case INT:
		if inst.Opcode>>24 == 0xCC {
			args = nil
			op = "int3"
		}

	case LCALL, LJMP:
		if len(args) == 2 {
			args[0], args[1] = args[1], args[0]
		}

	case FCHS, FABS, FTST, FLDPI, FLDL2E, FLDLG2, F2XM1, FXAM, FLD1, FLDL2T, FSQRT, FRNDINT, FCOS, FSIN:
		if len(args) == 0 {
			args = append(args, "st0")
		}

	case FPTAN, FSINCOS, FUCOMPP, FCOMPP, FYL2X, FPATAN, FXTRACT, FPREM1, FPREM, FYL2XP1, FSCALE:
		if len(args) == 0 {
			args = []string{"st0", "st1"}
		}

	case FST, FSTP, FISTTP, FIST, FISTP, FBSTP:
		if len(args) == 1 {
			args = append(args, "st0")
		}

	case FLD, FXCH, FCOM, FCOMP, FIADD, FIMUL, FICOM, FICOMP, FISUBR, FIDIV, FUCOM, FUCOMP, FILD, FBLD, FADD, FMUL, FSUB, FSUBR, FISUB, FDIV, FDIVR, FIDIVR:
		if len(args) == 1 {
			args = []string{"st0", args[0]}
		}

	case MASKMOVDQU, MASKMOVQ, XLATB, OUTSB, OUTSW, OUTSD:
	FixSegment:
		for i := len(inst.Prefix) - 1; i >= 0; i-- {
			p := inst.Prefix[i] & 0xFF
			switch p {
			case PrefixCS, PrefixES, PrefixFS, PrefixGS, PrefixSS:
				if inst.Mode != 64 || p == PrefixFS || p == PrefixGS {
					args = append(args, strings.ToLower((inst.Prefix[i] & 0xFF).String()))
					break FixSegment
				}
			case PrefixDS:
				if inst.Mode != 64 {
					break FixSegment
				}
			}
		}
	}

	if op == "" {
		op = intelOp[inst.Op]
	}
	if op == "" {
		op = strings.ToLower(inst.Op.String())
	}
	if args != nil {
		op += " " + strings.Join(args, ", ")
	}
	return prefix + op
}

func intelArg(inst *Inst, pc uint64, symname SymLookup, arg Arg) string {
	switch a := arg.(type) {
	case Imm:
		if s, base := symname(uint64(a)); s != "" {
			suffix := ""
			if uint64(a) != base {
				suffix = fmt.Sprintf("%+d", uint64(a)-base)
			}
			return fmt.Sprintf("$%s%s", s, suffix)
		}
		if inst.Mode == 32 {
			return fmt.Sprintf("%#x", uint32(a))
		}
		if Imm(int32(a)) == a {
			return fmt.Sprintf("%#x", int64(a))
		}
		return fmt.Sprintf("%#x", uint64(a))
	case Mem:
		if a.Base == EIP {
			a.Base = RIP
		}
		prefix := ""
		switch inst.MemBytes {
		case 1:
			prefix = "byte "
		case 2:
			prefix = "word "
		case 4:
			prefix = "dword "
		case 8:
			prefix = "qword "
		case 16:
			prefix = "xmmword "
		case 32:
			prefix = "ymmword "
		}
		switch inst.Op {
		case INVLPG:
			prefix = "byte "
		case STOSB, MOVSB, CMPSB, LODSB, SCASB:
			prefix = "byte "
		case STOSW, MOVSW, CMPSW, LODSW, SCASW:
			prefix = "word "
		case STOSD, MOVSD, CMPSD, LODSD, SCASD:
			prefix = "dword "
		case STOSQ, MOVSQ, CMPSQ, LODSQ, SCASQ:
			prefix = "qword "
		case LAR:
			prefix = "word "
		case BOUND:
			if inst.Mode == 32 {
				prefix = "qword "
			} else {
				prefix = "dword "
			}
		case PREFETCHW, PREFETCHNTA, PREFETCHT0, PREFETCHT1, PREFETCHT2, CLFLUSH:
			prefix = "zmmword "
		}
		switch inst.Op {
		case MOVSB, MOVSW, MOVSD, MOVSQ, CMPSB, CMPSW, CMPSD, CMPSQ, STOSB, STOSW, STOSD, STOSQ, SCASB, SCASW, SCASD, SCASQ, LODSB, LODSW, LODSD, LODSQ:
			switch a.Base {
			case DI, EDI, RDI:
				if a.Segment == ES {
					a.Segment = 0
				}
			case SI, ESI, RSI:
				if a.Segment == DS {
					a.Segment = 0
				}
			}
		case LEA:
			a.Segment = 0
		default:
			switch a.Base {
			case SP, ESP, RSP, BP, EBP, RBP:
				if a.Segment == SS {
					a.Segment = 0
				}
			default:
				if a.Segment == DS {
					a.Segment = 0
				}
			}
		}

		if inst.Mode == 64 && a.Segment != FS && a.Segment != GS {
			a.Segment = 0
		}

		prefix += "ptr "
		if s, disp := memArgToSymbol(a, pc, inst.Len, symname); s != "" {
			suffix := ""
			if disp != 0 {
				suffix = fmt.Sprintf("%+d", disp)
			}
			return prefix + fmt.Sprintf("[%s%s]", s, suffix)
		}
		if a.Segment != 0 {
			prefix += strings.ToLower(a.Segment.String()) + ":"
		}
		prefix += "["
		if a.Base != 0 {
			prefix += intelArg(inst, pc, symname, a.Base)
		}
		if a.Scale != 0 && a.Index != 0 {
			if a.Base != 0 {
				prefix += "+"
			}
			prefix += fmt.Sprintf("%s*%d", intelArg(inst, pc, symname, a.Index), a.Scale)
		}
		if a.Disp != 0 {
			if prefix[len(prefix)-1] == '[' && (a.Disp >= 0 || int64(int32(a.Disp)) != a.Disp) {
				prefix += fmt.Sprintf("%#x", uint64(a.Disp))
			} else {
				prefix += fmt.Sprintf("%+#x", a.Disp)
			}
		}
		prefix += "]"
		return prefix
	case Rel:
		if pc == 0 {
			return fmt.Sprintf(".%+#x", int64(a))
		} else {
			addr := pc + uint64(inst.Len) + uint64(a)
			if s, base := symname(addr); s != "" && addr == base {
				return fmt.Sprintf("%s", s)
			} else {
				addr := pc + uint64(inst.Len) + uint64(a)
				return fmt.Sprintf("%#x", addr)
			}
		}
	case Reg:
		if int(a) < len(intelReg) && intelReg[a] != "" {
			switch inst.Op {
			case VMOVDQA, VMOVDQU, VMOVNTDQA, VMOVNTDQ:
				return strings.Replace(intelReg[a], "xmm", "ymm", -1)
			default:
				return intelReg[a]
			}
		}
	}
	return strings.ToLower(arg.String())
}

var intelOp = map[Op]string{
	JAE:       "jnb",
	JA:        "jnbe",
	JGE:       "jnl",
	JNE:       "jnz",
	JG:        "jnle",
	JE:        "jz",
	SETAE:     "setnb",
	SETA:      "setnbe",
	SETGE:     "setnl",
	SETNE:     "setnz",
	SETG:      "setnle",
	SETE:      "setz",
	CMOVAE:    "cmovnb",
	CMOVA:     "cmovnbe",
	CMOVGE:    "cmovnl",
	CMOVNE:    "cmovnz",
	CMOVG:     "cmovnle",
	CMOVE:     "cmovz",
	LCALL:     "call far",
	LJMP:      "jmp far",
	LRET:      "ret far",
	ICEBP:     "int1",
	MOVSD_XMM: "movsd",
	XLATB:     "xlat",
}

var intelReg = [...]string{
	F0:  "st0",
	F1:  "st1",
	F2:  "st2",
	F3:  "st3",
	F4:  "st4",
	F5:  "st5",
	F6:  "st6",
	F7:  "st7",
	M0:  "mmx0",
	M1:  "mmx1",
	M2:  "mmx2",
	M3:  "mmx3",
	M4:  "mmx4",
	M5:  "mmx5",
	M6:  "mmx6",
	M7:  "mmx7",
	X0:  "xmm0",
	X1:  "xmm1",
	X2:  "xmm2",
	X3:  "xmm3",
	X4:  "xmm4",
	X5:  "xmm5",
	X6:  "xmm6",
	X7:  "xmm7",
	X8:  "xmm8",
	X9:  "xmm9",
	X10: "xmm10",
	X11: "xmm11",
	X12: "xmm12",
	X13: "xmm13",
	X14: "xmm14",
	X15: "xmm15",

	// TODO: Maybe the constants are named wrong.
	SPB: "spl",
	BPB: "bpl",
	SIB: "sil",
	DIB: "dil",

	R8L:  "r8d",
	R9L:  "r9d",
	R10L: "r10d",
	R11L: "r11d",
	R12L: "r12d",
	R13L: "r13d",
	R14L: "r14d",
	R15L: "r15d",
}
