// Copyright 2018 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 xeddata

import (
	"encoding/json"
	"strings"
)

// An Object is a single "dec/enc-instruction" XED object from datafiles.
//
// Field names and their comments are borrowed from Intel XED
// engineering notes (see "$XED/misc/engineering-notes.txt").
//
// Field values are always trimmed (i.e. no leading/trailing whitespace).
//
// Missing optional members are expressed with an empty string.
//
// Object contains multiple Inst elements that represent concrete
// instruction with encoding pattern and operands description.
type Object struct {
	// Pos is the file position of the start of this object.
	Pos Pos

	// Iclass is instruction class name (opcode).
	// Iclass alone is not enough to uniquely identify machine instructions.
	// Example: "PSRLW".
	Iclass string

	// Disasm is substituted name when a simple conversion
	// from iclass is inappropriate.
	// Never combined with DisasmIntel or DisasmATTSV.
	// Example: "syscall".
	//
	// Optional.
	Disasm string

	// DisasmIntel is like Disasm, but with Intel syntax.
	// If present, usually comes with DisasmATTSV.
	// Example: "jmp far".
	//
	// Optional.
	DisasmIntel string

	// DisasmATTSV is like Disasm, but with AT&T/SysV syntax.
	// If present, usually comes with DisasmIntel.
	// Example: "ljmp".
	//
	// Optional.
	DisasmATTSV string

	// Attributes describes name set for bits in the binary attributes field.
	// Example: "NOP X87_CONTROL NOTSX".
	//
	// Optional. If not present, zero attribute set is implied.
	Attributes string

	// Uname is unique name used for deleting / replacing instructions.
	//
	// Optional. Provided for completeness, mostly useful for XED internal usage.
	Uname string

	// CPL is instruction current privilege level restriction.
	// Can have value of "0" or "3".
	CPL string

	// Category is an ad-hoc categorization of instructions.
	// Example: "SEMAPHORE".
	Category string

	// Extension is an ad-hoc grouping of instructions.
	// If no ISASet is specified, this is used instead.
	// Example: "3DNOW"
	Extension string

	// Exceptions is an exception set name.
	// Example: "SSE_TYPE_7".
	//
	// Optional. Empty exception category generally means that
	// instruction generates no exceptions.
	Exceptions string

	// ISASet is a name for the group of instructions that
	// introduced this feature.
	// Example: "I286PROTECTED".
	//
	// Older objects only defined Extension field.
	// Newer objects may contain both Extension and ISASet fields.
	// For some objects Extension==ISASet.
	// Both fields are required to do precise CPUID-like decisions.
	//
	// Optional.
	ISASet string

	// Flags describes read/written flag bit values.
	// Example: "MUST [ of-u sf-u af-u pf-u cf-mod ]".
	//
	// Optional. If not present, no flags are neither read nor written.
	Flags string

	// A hopefully useful comment.
	//
	// Optional.
	Comment string

	// The object revision.
	//
	// Optional.
	Version string

	// RealOpcode marks unstable (not in SDM yet) instructions with "N".
	// Normally, always "Y" or not present at all.
	//
	// Optional.
	RealOpcode string

	// Insts are concrete instruction templates that are derived from containing Object.
	// Inst contains fields PATTERN, OPERANDS, IFORM in enc/dec instruction.
	Insts []*Inst
}

// Inst represents a single instruction template.
//
// Some templates contain expandable (macro) pattern and operands
// which tells that there are more than one real instructions
// that are expressed by the template.
type Inst struct {
	// Object that contains properties that are shared with multiple
	// Inst objects.
	*Object

	// Pos is the file position of this Inst's PATTERN.
	Pos Pos

	// Index is the position inside XED object.
	// Object.Insts[Index] returns this inst.
	Index int

	// Pattern is the sequence of bits and nonterminals used to
	// decode/encode an instruction.
	// Example: "0x0F 0x28 no_refining_prefix MOD[0b11] MOD=3 REG[rrr] RM[nnn]".
	Pattern string

	// Operands are instruction arguments, typicall registers,
	// memory operands and pseudo-resources. Separated by space.
	// Example: "MEM0:rcw:b REG0=GPR8_R():r REG1=XED_REG_AL:rcw:SUPP".
	Operands string

	// Iform is a name for the pattern that starts with the
	// iclass and bakes in the operands. If omitted, XED
	// tries to generate one. We often add custom suffixes
	// to these to disambiguate certain combinations.
	// Example: "MOVAPS_XMMps_XMMps_0F28".
	//
	// Optional.
	Iform string
}

// Opcode returns instruction name or empty string,
// if appropriate Object fields are not initialized.
func (o *Object) Opcode() string {
	switch {
	case o.Iclass != "":
		return o.Iclass
	case o.Disasm != "":
		return o.Disasm
	case o.DisasmIntel != "":
		return o.DisasmIntel
	case o.DisasmATTSV != "":
		return o.DisasmATTSV
	case o.Uname != "":
		return o.Uname
	}
	return ""
}

// HasAttribute checks that o has attribute with specified name.
// Note that check is done at "word" level, substring names will not match.
func (o *Object) HasAttribute(name string) bool {
	return containsWord(o.Attributes, name)
}

// String returns pretty-printed inst representation.
//
// Outputs valid JSON string. This property is
// not guaranteed to be preserved.
func (inst *Inst) String() string {
	// Do not use direct inst marshalling to achieve
	// flat object printed representation.
	// Map is avoided to ensure consistent props order.
	type flatObject struct {
		Iclass      string
		Disasm      string `json:",omitempty"`
		DisasmIntel string `json:",omitempty"`
		DisasmATTSV string `json:",omitempty"`
		Attributes  string `json:",omitempty"`
		Uname       string `json:",omitempty"`
		CPL         string
		Category    string
		Extension   string
		Exceptions  string `json:",omitempty"`
		ISASet      string `json:",omitempty"`
		Flags       string `json:",omitempty"`
		Comment     string `json:",omitempty"`
		Version     string `json:",omitempty"`
		RealOpcode  string `json:",omitempty"`
		Pattern     string
		Operands    string
		Iform       string `json:",omitempty"`
	}

	flat := flatObject{
		Iclass:      inst.Iclass,
		Disasm:      inst.Disasm,
		DisasmIntel: inst.DisasmIntel,
		DisasmATTSV: inst.DisasmATTSV,
		Attributes:  inst.Attributes,
		Uname:       inst.Uname,
		CPL:         inst.CPL,
		Category:    inst.Category,
		Extension:   inst.Extension,
		Exceptions:  inst.Exceptions,
		ISASet:      inst.ISASet,
		Flags:       inst.Flags,
		Comment:     inst.Comment,
		Version:     inst.Version,
		RealOpcode:  inst.RealOpcode,
		Pattern:     inst.Pattern,
		Operands:    inst.Operands,
		Iform:       inst.Iform,
	}

	b, err := json.MarshalIndent(flat, "", "  ")
	if err != nil {
		panic(err)
	}
	return string(b)
}

// ExpandStates returns a copy of s where all state macros
// are expanded.
// This requires db "states" to be loaded.
func ExpandStates(db *Database, s string) string {
	substs := db.states
	parts := strings.Fields(s)
	for i := range parts {
		if repl := substs[parts[i]]; repl != "" {
			parts[i] = repl
		}
	}
	return strings.Join(parts, " ")
}

// containsWord searches for whole word match in s.
func containsWord(s, word string) bool {
	i := strings.Index(s, word)
	if i == -1 {
		return false
	}
	leftOK := i == 0 ||
		(s[i-1] == ' ')
	rigthOK := i+len(word) == len(s) ||
		(s[i+len(word)] == ' ')
	return leftOK && rigthOK
}
