package bsv

import "strconv"

// Opcode represents a script opcode, mirroring lib/opcode.
type Opcode struct {
	Num int
}

// Opcode map (name -> number), mirroring Opcode.map.
var opcodeMap = map[string]int{
	"OP_FALSE": 0, "OP_0": 0,
	"OP_PUSHDATA1": 76, "OP_PUSHDATA2": 77, "OP_PUSHDATA4": 78,
	"OP_1NEGATE": 79, "OP_RESERVED": 80, "OP_TRUE": 81, "OP_1": 81,
	"OP_2": 82, "OP_3": 83, "OP_4": 84, "OP_5": 85, "OP_6": 86,
	"OP_7": 87, "OP_8": 88, "OP_9": 89, "OP_10": 90, "OP_11": 91,
	"OP_12": 92, "OP_13": 93, "OP_14": 94, "OP_15": 95, "OP_16": 96,
	"OP_NOP": 97, "OP_VER": 98, "OP_IF": 99, "OP_NOTIF": 100,
	"OP_VERIF": 101, "OP_VERNOTIF": 102, "OP_ELSE": 103, "OP_ENDIF": 104,
	"OP_VERIFY": 105, "OP_RETURN": 106,
	"OP_TOALTSTACK": 107, "OP_FROMALTSTACK": 108, "OP_2DROP": 109,
	"OP_2DUP": 110, "OP_3DUP": 111, "OP_2OVER": 112, "OP_2ROT": 113,
	"OP_2SWAP": 114, "OP_IFDUP": 115, "OP_DEPTH": 116, "OP_DROP": 117,
	"OP_DUP": 118, "OP_NIP": 119, "OP_OVER": 120, "OP_PICK": 121,
	"OP_ROLL": 122, "OP_ROT": 123, "OP_SWAP": 124, "OP_TUCK": 125,
	"OP_CAT": 126, "OP_SPLIT": 127, "OP_NUM2BIN": 128, "OP_BIN2NUM": 129,
	"OP_SIZE": 130,
	"OP_INVERT": 131, "OP_AND": 132, "OP_OR": 133, "OP_XOR": 134,
	"OP_EQUAL": 135, "OP_EQUALVERIFY": 136, "OP_RESERVED1": 137, "OP_RESERVED2": 138,
	"OP_1ADD": 139, "OP_1SUB": 140, "OP_2MUL": 141, "OP_2DIV": 142,
	"OP_NEGATE": 143, "OP_ABS": 144, "OP_NOT": 145, "OP_0NOTEQUAL": 146,
	"OP_ADD": 147, "OP_SUB": 148, "OP_MUL": 149, "OP_DIV": 150, "OP_MOD": 151,
	"OP_LSHIFT": 152, "OP_RSHIFT": 153,
	"OP_BOOLAND": 154, "OP_BOOLOR": 155, "OP_NUMEQUAL": 156,
	"OP_NUMEQUALVERIFY": 157, "OP_NUMNOTEQUAL": 158, "OP_LESSTHAN": 159,
	"OP_GREATERTHAN": 160, "OP_LESSTHANOREQUAL": 161, "OP_GREATERTHANOREQUAL": 162,
	"OP_MIN": 163, "OP_MAX": 164,
	"OP_WITHIN": 165,
	"OP_RIPEMD160": 166, "OP_SHA1": 167, "OP_SHA256": 168, "OP_HASH160": 169,
	"OP_HASH256": 170, "OP_CODESEPARATOR": 171, "OP_CHECKSIG": 172,
	"OP_CHECKSIGVERIFY": 173, "OP_CHECKMULTISIG": 174, "OP_CHECKMULTISIGVERIFY": 175,
	"OP_CHECKLOCKTIMEVERIFY": 177, "OP_CHECKSEQUENCEVERIFY": 178,
	"OP_NOP1": 176, "OP_NOP2": 177, "OP_NOP3": 178, "OP_NOP4": 179,
	"OP_NOP5": 180, "OP_NOP6": 181, "OP_NOP7": 182, "OP_NOP8": 183,
	"OP_NOP9": 184, "OP_NOP10": 185,
	"OP_PUBKEYHASH": 253, "OP_PUBKEY": 254, "OP_INVALIDOPCODE": 255,
}

// reverseMap (number -> name), mirroring Opcode.reverseMap. The JS loop
// assigns in map-definition order, so for colliding values the later key
// wins: OP_0 over OP_FALSE (value 0) and OP_1 over OP_TRUE (value 81).
// Go map iteration is unordered, so we fix those two collisions explicitly.
var opcodeReverseMap = map[int]string{}

func init() {
	for k, v := range opcodeMap {
		opcodeReverseMap[v] = k
	}
	opcodeReverseMap[0] = "OP_0"
	opcodeReverseMap[81] = "OP_1"
}

// NewOpcode creates an Opcode from a number or name string.
func NewOpcode(num interface{}) (*Opcode, error) {
	switch v := num.(type) {
	case int:
		return &Opcode{Num: v}, nil
	case string:
		val, ok := opcodeMap[v]
		if !ok {
			return nil, newError("InvalidOpcode", "Invalid opcodestr: %s", v)
		}
		return &Opcode{Num: val}, nil
	default:
		return nil, newError("InvalidOpcode", "Unrecognized num type for Opcode")
	}
}

// OpcodeFromBuffer reads an opcode from a single byte buffer.
func OpcodeFromBuffer(buf []byte) (*Opcode, error) {
	if len(buf) != 1 {
		return nil, newError("InvalidOpcode", "opcode buffer must be 1 byte")
	}
	return &Opcode{Num: int(buf[0])}, nil
}

// OpcodeFromNumber creates an Opcode from a number.
func OpcodeFromNumber(num int) *Opcode { return &Opcode{Num: num} }

// OpcodeFromString creates an Opcode from a name.
func OpcodeFromString(str string) (*Opcode, error) { return NewOpcode(str) }

// ToHex returns the hex serialization of the opcode number.
func (o *Opcode) ToHex() string { return strconv.FormatInt(int64(o.Num), 16) }

// ToBuffer returns the single-byte serialization.
func (o *Opcode) ToBuffer() []byte { return []byte{byte(o.Num)} }

// ToNumber returns the opcode number.
func (o *Opcode) ToNumber() int { return o.Num }

// ToString returns the opcode name.
func (o *Opcode) ToString() (string, error) {
	str, ok := opcodeReverseMap[o.Num]
	if !ok {
		return "", newError("InvalidOpcode", "Opcode does not have a string representation")
	}
	return str, nil
}

// ToSafeString returns the opcode name, or the hex number if unknown.
func (o *Opcode) ToSafeString() string {
	if str, ok := opcodeReverseMap[o.Num]; ok {
		return str
	}
	return o.ToHex()
}

// SmallInt returns an Opcode for a number 0-16 (OP_0, OP_1, ..., OP_16).
func SmallInt(n int) (*Opcode, error) {
	if n < 0 || n > 16 {
		return nil, newError("InvalidArgument", "Invalid Argument: n must be between 0 and 16")
	}
	if n == 0 {
		return NewOpcode("OP_0")
	}
	return &Opcode{Num: opcodeMap["OP_1"] + n - 1}, nil
}

// IsSmallIntOp reports whether the opcode is OP_0 .. OP_16.
func IsSmallIntOp(opcode int) bool {
	return opcode == opcodeMap["OP_0"] || (opcode >= opcodeMap["OP_1"] && opcode <= opcodeMap["OP_16"])
}