// Copyright 2015 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 ppc64asm

import (
	"fmt"
	"strings"
)

// GoSyntax returns the Go assembler syntax for the instruction.
// The pc is the program counter of the first instruction, used for expanding
// PC-relative addresses into absolute ones.
// The symname function queries the symbol table for the program
// being disassembled. It returns the name and base address of the symbol
// containing the target, if any; otherwise it returns "", 0.
func GoSyntax(inst Inst, pc uint64, symname func(uint64) (string, uint64)) string {
	if symname == nil {
		symname = func(uint64) (string, uint64) { return "", 0 }
	}
	if inst.Op == 0 && inst.Enc == 0 {
		return "WORD $0"
	} else if inst.Op == 0 {
		return "?"
	}
	var args []string
	for i, a := range inst.Args[:] {
		if a == nil {
			break
		}
		if s := plan9Arg(&inst, i, pc, a, symname); s != "" {
			args = append(args, s)
		}
	}
	var op string
	op = plan9OpMap[inst.Op]
	if op == "" {
		op = strings.ToUpper(inst.Op.String())
		if op[len(op)-1] == '.' {
			op = op[:len(op)-1] + "CC"
		}
	}
	// laid out the instruction
	switch inst.Op {
	default: // dst, sA, sB, ...
		switch len(args) {
		case 0:
			return op
		case 1:
			return fmt.Sprintf("%s %s", op, args[0])
		case 2:
			if inst.Op == COPY || inst.Op == PASTECC {
				return op + " " + args[0] + "," + args[1]
			}
			return op + " " + args[1] + "," + args[0]
		case 3:
			if reverseOperandOrder(inst.Op) {
				return op + " " + args[2] + "," + args[1] + "," + args[0]
			}
		case 4:
			if reverseMiddleOps(inst.Op) {
				return op + " " + args[1] + "," + args[3] + "," + args[2] + "," + args[0]
			}
		}
		args = append(args, args[0])
		return op + " " + strings.Join(args[1:], ",")
	case PASTECC:
		// paste. has two input registers, and an L field, unlike other 3 operand instructions.
		return op + " " + args[0] + "," + args[1] + "," + args[2]
	case SYNC:
		if args[0] == "$1" {
			return "LWSYNC"
		}
		return "HWSYNC"

	case ISEL:
		return "ISEL " + args[3] + "," + args[1] + "," + args[2] + "," + args[0]

	// store instructions always have the memory operand at the end, no need to reorder
	// indexed stores handled separately
	case STB, STBU,
		STH, STHU,
		STW, STWU,
		STD, STDU,
		STFD, STFDU,
		STFS, STFSU,
		STQ, HASHST, HASHSTP:
		return op + " " + strings.Join(args, ",")

	case FCMPU, FCMPO, CMPD, CMPDI, CMPLD, CMPLDI, CMPW, CMPWI, CMPLW, CMPLWI:
		crf := int(inst.Args[0].(CondReg) - CR0)
		cmpstr := op + " " + args[1] + "," + args[2]
		if crf != 0 { // print CRx as the final operand if not implied (i.e BF != 0)
			cmpstr += "," + args[0]
		}
		return cmpstr

	case LIS:
		return "ADDIS $0," + args[1] + "," + args[0]
	// store instructions with index registers
	case STBX, STBUX, STHX, STHUX, STWX, STWUX, STDX, STDUX,
		STHBRX, STWBRX, STDBRX, STSWX, STFIWX:
		return "MOV" + op[2:len(op)-1] + " " + args[0] + ",(" + args[2] + ")(" + args[1] + ")"

	case STDCXCC, STWCXCC, STHCXCC, STBCXCC:
		return op + " " + args[0] + ",(" + args[2] + ")(" + args[1] + ")"

	case STXVX, STXVD2X, STXVW4X, STXVH8X, STXVB16X, STXSDX, STVX, STVXL, STVEBX, STVEHX, STVEWX, STXSIWX, STFDX, STFDUX, STFDPX, STFSX, STFSUX:
		return op + " " + args[0] + ",(" + args[2] + ")(" + args[1] + ")"

	case STXV:
		return op + " " + args[0] + "," + args[1]

	case STXVL, STXVLL:
		return op + " " + args[0] + "," + args[1] + "," + args[2]

	case LWAX, LWAUX, LWZX, LHZX, LBZX, LDX, LHAX, LHAUX, LDARX, LWARX, LHARX, LBARX, LFDX, LFDUX, LFSX, LFSUX, LDBRX, LWBRX, LHBRX, LDUX, LWZUX, LHZUX, LBZUX:
		if args[1] == "0" {
			return op + " (" + args[2] + ")," + args[0]
		}
		return op + " (" + args[2] + ")(" + args[1] + ")," + args[0]

	case LXVX, LXVD2X, LXVW4X, LXVH8X, LXVB16X, LVX, LVXL, LVSR, LVSL, LVEBX, LVEHX, LVEWX, LXSDX, LXSIWAX:
		return op + " (" + args[2] + ")(" + args[1] + ")," + args[0]

	case LXV:
		return op + " " + args[1] + "," + args[0]

	case LXVL, LXVLL:
		return op + " " + args[1] + "," + args[2] + "," + args[0]

	case DCBT, DCBTST, DCBZ, DCBST, ICBI:
		if args[0] == "0" || args[0] == "R0" {
			return op + " (" + args[1] + ")"
		}
		return op + " (" + args[1] + ")(" + args[0] + ")"

	// branch instructions needs additional handling
	case BCLR:
		if int(inst.Args[0].(Imm))&20 == 20 { // unconditional
			return "RET"
		}
		return op + " " + strings.Join(args, ", ")
	case BC:
		bo := int(inst.Args[0].(Imm))
		bi := int(inst.Args[1].(CondReg) - Cond0LT)
		bcname := condName[((bo&0x8)>>1)|(bi&0x3)]
		if bo&0x17 == 4 { // jump only a CR bit set/unset, no hints (at bits) set.
			if bi >= 4 {
				return fmt.Sprintf("B%s CR%d,%s", bcname, bi>>2, args[2])
			} else {
				return fmt.Sprintf("B%s %s", bcname, args[2])
			}
		}
		return op + " " + strings.Join(args, ",")
	case BCCTR:
		if int(inst.Args[0].(Imm))&20 == 20 { // unconditional
			return "BR (CTR)"
		}
		return op + " " + strings.Join(args, ", ")
	case BCCTRL:
		if int(inst.Args[0].(Imm))&20 == 20 { // unconditional
			return "BL (CTR)"
		}
		return op + " " + strings.Join(args, ",")
	case BCA, BCL, BCLA, BCLRL, BCTAR, BCTARL:
		return op + " " + strings.Join(args, ",")
	}
}

// plan9Arg formats arg (which is the argIndex's arg in inst) according to Plan 9 rules.
//
// NOTE: because Plan9Syntax is the only caller of this func, and it receives a copy
// of inst, it's ok to modify inst.Args here.
func plan9Arg(inst *Inst, argIndex int, pc uint64, arg Arg, symname func(uint64) (string, uint64)) string {
	// special cases for load/store instructions
	if _, ok := arg.(Offset); ok {
		if argIndex+1 == len(inst.Args) || inst.Args[argIndex+1] == nil {
			panic(fmt.Errorf("wrong table: offset not followed by register"))
		}
	}
	switch arg := arg.(type) {
	case Reg:
		if isLoadStoreOp(inst.Op) && argIndex == 1 && arg == R0 {
			return "0"
		}
		if arg == R30 {
			return "g"
		}
		return strings.ToUpper(arg.String())
	case CondReg:
		// This op is left as its numerical value, not mapped onto CR + condition
		if inst.Op == ISEL {
			return fmt.Sprintf("$%d", (arg - Cond0LT))
		}
		bit := [4]string{"LT", "GT", "EQ", "SO"}[(arg-Cond0LT)%4]
		if arg <= Cond0SO {
			return bit
		} else if arg > Cond0SO && arg <= Cond7SO {
			return fmt.Sprintf("CR%d%s", int(arg-Cond0LT)/4, bit)
		} else {
			return fmt.Sprintf("CR%d", int(arg-CR0))
		}
	case Imm:
		return fmt.Sprintf("$%d", arg)
	case SpReg:
		switch arg {
		case 8:
			return "LR"
		case 9:
			return "CTR"
		}
		return fmt.Sprintf("SPR(%d)", int(arg))
	case PCRel:
		addr := pc + uint64(int64(arg))
		s, base := symname(addr)
		if s != "" && addr == base {
			return fmt.Sprintf("%s(SB)", s)
		}
		if inst.Op == BL && s != "" && (addr-base) == 8 {
			// When decoding an object built for PIE, a CALL targeting
			// a global entry point will be adjusted to the local entry
			// if any. For now, assume any symname+8 PC is a local call.
			return fmt.Sprintf("%s+%d(SB)", s, addr-base)
		}
		return fmt.Sprintf("%#x", addr)
	case Label:
		return fmt.Sprintf("%#x", int(arg))
	case Offset:
		reg := inst.Args[argIndex+1].(Reg)
		removeArg(inst, argIndex+1)
		if reg == R0 {
			return fmt.Sprintf("%d(0)", int(arg))
		}
		return fmt.Sprintf("%d(R%d)", int(arg), reg-R0)
	}
	return fmt.Sprintf("???(%v)", arg)
}

func reverseMiddleOps(op Op) bool {
	switch op {
	case FMADD, FMADDCC, FMADDS, FMADDSCC, FMSUB, FMSUBCC, FMSUBS, FMSUBSCC, FNMADD, FNMADDCC, FNMADDS, FNMADDSCC, FNMSUB, FNMSUBCC, FNMSUBS, FNMSUBSCC, FSEL, FSELCC:
		return true
	}
	return false
}

func reverseOperandOrder(op Op) bool {
	switch op {
	// Special case for SUBF, SUBFC: not reversed
	case ADD, ADDC, ADDE, ADDCC, ADDCCC:
		return true
	case MULLW, MULLWCC, MULHW, MULHWCC, MULLD, MULLDCC, MULHD, MULHDCC, MULLWO, MULLWOCC, MULHWU, MULHWUCC, MULLDO, MULLDOCC:
		return true
	case DIVD, DIVDCC, DIVDU, DIVDUCC, DIVDE, DIVDECC, DIVDEU, DIVDEUCC, DIVDO, DIVDOCC, DIVDUO, DIVDUOCC:
		return true
	case MODUD, MODSD, MODUW, MODSW:
		return true
	case FADD, FADDS, FSUB, FSUBS, FMUL, FMULS, FDIV, FDIVS, FMADD, FMADDS, FMSUB, FMSUBS, FNMADD, FNMADDS, FNMSUB, FNMSUBS, FMULSCC:
		return true
	case FADDCC, FADDSCC, FSUBCC, FMULCC, FDIVCC, FDIVSCC:
		return true
	case OR, ORCC, ORC, ORCCC, AND, ANDCC, ANDC, ANDCCC, XOR, XORCC, NAND, NANDCC, EQV, EQVCC, NOR, NORCC:
		return true
	case SLW, SLWCC, SLD, SLDCC, SRW, SRAW, SRWCC, SRAWCC, SRD, SRDCC, SRAD, SRADCC:
		return true
	}
	return false
}

// revCondMap maps a conditional register bit to its inverse, if possible.
var revCondMap = map[string]string{
	"LT": "GE", "GT": "LE", "EQ": "NE",
}

// Lookup table to map BI[0:1] and BO[3] to an extended mnemonic for CR ops.
// Bits 0-1 map to a bit with a CR field, and bit 2 selects the inverted (0)
// or regular (1) extended mnemonic.
var condName = []string{
	"GE",
	"LE",
	"NE",
	"NSO",
	"LT",
	"GT",
	"EQ",
	"SO",
}

// plan9OpMap maps an Op to its Plan 9 mnemonics, if different than its GNU mnemonics.
var plan9OpMap = map[Op]string{
	LWARX:     "LWAR",
	LDARX:     "LDAR",
	LHARX:     "LHAR",
	LBARX:     "LBAR",
	LWAX:      "MOVW",
	LHAX:      "MOVH",
	LWAUX:     "MOVWU",
	LHAU:      "MOVHU",
	LHAUX:     "MOVHU",
	LDX:       "MOVD",
	LDUX:      "MOVDU",
	LWZX:      "MOVWZ",
	LWZUX:     "MOVWZU",
	LHZX:      "MOVHZ",
	LHZUX:     "MOVHZU",
	LBZX:      "MOVBZ",
	LBZUX:     "MOVBZU",
	LDBRX:     "MOVDBR",
	LWBRX:     "MOVWBR",
	LHBRX:     "MOVHBR",
	MCRF:      "MOVFL",
	XORI:      "XOR",
	ORI:       "OR",
	ANDICC:    "ANDCC",
	ANDC:      "ANDN",
	ANDCCC:    "ANDNCC",
	ADDEO:     "ADDEV",
	ADDEOCC:   "ADDEVCC",
	ADDO:      "ADDV",
	ADDOCC:    "ADDVCC",
	ADDMEO:    "ADDMEV",
	ADDMEOCC:  "ADDMEVCC",
	ADDCO:     "ADDCV",
	ADDCOCC:   "ADDCVCC",
	ADDZEO:    "ADDZEV",
	ADDZEOCC:  "ADDZEVCC",
	SUBFME:    "SUBME",
	SUBFMECC:  "SUBMECC",
	SUBFZE:    "SUBZE",
	SUBFZECC:  "SUBZECC",
	SUBFZEO:   "SUBZEV",
	SUBFZEOCC: "SUBZEVCC",
	SUBF:      "SUB",
	SUBFC:     "SUBC",
	SUBFCC:    "SUBCC",
	SUBFCCC:   "SUBCCC",
	ORC:       "ORN",
	ORCCC:     "ORNCC",
	MULLWO:    "MULLWV",
	MULLWOCC:  "MULLWVCC",
	MULLDO:    "MULLDV",
	MULLDOCC:  "MULLDVCC",
	DIVDO:     "DIVDV",
	DIVDOCC:   "DIVDVCC",
	DIVDUO:    "DIVDUV",
	DIVDUOCC:  "DIVDUVCC",
	ADDI:      "ADD",
	MULLI:     "MULLD",
	SRADI:     "SRAD",
	STBCXCC:   "STBCCC",
	STWCXCC:   "STWCCC",
	STDCXCC:   "STDCCC",
	LI:        "MOVD",
	LBZ:       "MOVBZ", STB: "MOVB",
	LBZU: "MOVBZU", STBU: "MOVBU",
	LHZ: "MOVHZ", LHA: "MOVH", STH: "MOVH",
	LHZU: "MOVHZU", STHU: "MOVHU",
	LWZ: "MOVWZ", LWA: "MOVW", STW: "MOVW",
	LWZU: "MOVWZU", STWU: "MOVWU",
	LD: "MOVD", STD: "MOVD",
	LDU: "MOVDU", STDU: "MOVDU",
	LFD: "FMOVD", STFD: "FMOVD",
	LFS: "FMOVS", STFS: "FMOVS",
	LFDX: "FMOVD", STFDX: "FMOVD",
	LFDU: "FMOVDU", STFDU: "FMOVDU",
	LFDUX: "FMOVDU", STFDUX: "FMOVDU",
	LFSX: "FMOVS", STFSX: "FMOVS",
	LFSU: "FMOVSU", STFSU: "FMOVSU",
	LFSUX: "FMOVSU", STFSUX: "FMOVSU",
	CMPD: "CMP", CMPDI: "CMP",
	CMPW: "CMPW", CMPWI: "CMPW",
	CMPLD: "CMPU", CMPLDI: "CMPU",
	CMPLW: "CMPWU", CMPLWI: "CMPWU",
	MTSPR: "MOVD", MFSPR: "MOVD", // the width is ambiguous for SPRs
	B:  "BR",
	BL: "CALL",
}
