package bifs

import (
	"bytes"
	"fmt"
	"regexp"
	"strconv"
	"strings"

	"github.com/johnkerl/miller/v6/pkg/lib"
	"github.com/johnkerl/miller/v6/pkg/mlrval"
)

func BIF_strlen(input1 *mlrval.Mlrval) *mlrval.Mlrval {
	// Strings have UTF-8 rune-count length; bytes have byte-count length.
	if input1.IsBytes() {
		return mlrval.FromInt(int64(len(input1.AcquireBytesValue())))
	}
	if !input1.IsStringOrVoid() {
		return mlrval.FromTypeErrorUnary("strlen", input1)
	}
	return mlrval.FromInt(lib.UTF8Strlen(input1.AcquireStringValue()))
}

func BIF_string(input1 *mlrval.Mlrval) *mlrval.Mlrval {
	// For bytes, reinterpret the raw bytes as a string -- the inverse of
	// bytes(s) -- rather than using the hex String() rendering. This is what
	// makes string(base64_decode("aGVsbG8=")) yield "hello".
	if input1.IsBytes() {
		return mlrval.FromString(string(input1.AcquireBytesValue()))
	}
	return mlrval.FromString(input1.String())
}

// Dot operator, with loose typecasting.
//
// For most operations, I don't like loose typecasting -- for example, in PHP
// "10" + 2 is the number 12 and in JavaScript it's the string "102", and I
// find both of those horrid and error-prone. In Miller, "10"+2 is MT_ERROR, by
// design, unless intentional casting is done like '$x=int("10")+2'.
//
// However, for dotting, in practice I tipped over and allowed dotting of
// strings and ints: so while "10" + 2 is an error in Miller, '"10". 2' is
// "102". Unlike with "+", with "." there is no ambiguity about what the output
// should be: always the string concatenation of the string representations of
// the two arguments. So, we do the string-cast for the user.

func dot_s_xx(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	return mlrval.FromString(input1.String() + input2.String())
}

func dot_te(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	return mlrval.FromTypeErrorBinary(".", input1, input2)
}

// Concatenation of two bytes values is a bytes value; dotting bytes with
// anything else is a type error, since silently reinterpreting binary data as
// text (or vice versa) is exactly what the bytes type exists to prevent.
func dot_y_yy(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	a := input1.AcquireBytesValue()
	b := input2.AcquireBytesValue()
	c := make([]byte, 0, len(a)+len(b))
	c = append(c, a...)
	c = append(c, b...)
	return mlrval.FromBytes(c)
}

var dot_dispositions = [mlrval.MT_DIM][mlrval.MT_DIM]BinaryFunc{
	// . INT FLOAT BOOL VOID STRING BYTES ARRAY MAP FUNC ERROR NULL ABSENT
	/*INT    */ {dot_s_xx, dot_s_xx, dot_s_xx, _s1__, dot_s_xx, dot_te, dot_te, dot_te, dot_te, dot_te, _1___, _s1__},
	/*FLOAT  */ {dot_s_xx, dot_s_xx, dot_s_xx, _s1__, dot_s_xx, dot_te, dot_te, dot_te, dot_te, dot_te, _1___, _s1__},
	/*BOOL   */ {dot_s_xx, dot_s_xx, dot_s_xx, _s1__, dot_s_xx, dot_te, dot_te, dot_te, dot_te, dot_te, _1___, _s1__},
	/*VOID   */ {_s2__, _s2__, _s2__, _void, _2___, _2___, _absn, _absn, dot_te, dot_te, _void, _void},
	/*STRING */ {dot_s_xx, dot_s_xx, dot_s_xx, _1___, dot_s_xx, dot_te, dot_te, dot_te, dot_te, dot_te, _1___, _1___},
	/*BYTES  */ {dot_te, dot_te, dot_te, _1___, dot_te, dot_y_yy, dot_te, dot_te, dot_te, dot_te, _1___, _1___},
	/*ARRAY  */ {dot_te, dot_te, dot_te, dot_te, dot_te, dot_te, dot_te, dot_te, dot_te, dot_te, dot_te, dot_te},
	/*MAP    */ {dot_te, dot_te, dot_te, dot_te, dot_te, dot_te, dot_te, dot_te, dot_te, dot_te, dot_te, dot_te},
	/*FUNC   */ {dot_te, dot_te, dot_te, dot_te, dot_te, dot_te, dot_te, dot_te, dot_te, dot_te, dot_te, dot_te},
	/*ERROR  */ {dot_te, dot_te, dot_te, dot_te, dot_te, dot_te, _absn, _absn, dot_te, dot_te, dot_te, dot_te},
	/*NULL   */ {_s2__, _s2__, _s2__, _void, _2___, _2___, _absn, _absn, dot_te, dot_te, _null, _null},
	/*ABSENT */ {_s2__, _s2__, _s2__, _void, _2___, _2___, _absn, _absn, dot_te, dot_te, _null, _absn},
}

func BIF_dot(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	return dot_dispositions[input1.Type()][input2.Type()](input1, input2)
}

// substr1(s,m,n) gives substring of s from 1-up position m to n inclusive.
// Negative indices -len .. -1 alias to 0 .. len-1.

func BIF_substr_1_up(input1, input2, input3 *mlrval.Mlrval) *mlrval.Mlrval {
	if input1.IsAbsent() {
		return mlrval.ABSENT
	}
	if input1.IsError() {
		return mlrval.FromTypeErrorUnary("substr1", input1)
	}
	if input1.IsBytes() {
		return bytes_substr(input1, input2, input3, false)
	}
	sinput := input1.String()

	// Handle UTF-8 correctly: len(input1.AcquireStringValue()) will count bytes, not runes.
	runes := []rune(sinput)
	strlen := int(len(runes))

	sliceIsEmpty, absentOrError, lowerZindex, upperZindex := MillerSliceAccess(input2, input3, strlen, false)

	if sliceIsEmpty {
		return mlrval.VOID
	}
	if absentOrError != nil {
		return absentOrError
	}

	// Note Golang slice indices are 0-up, and the 1st index is inclusive
	// while the 2nd is exclusive. For Miller, indices are 1-up and both
	// are inclusive.
	return mlrval.FromString(string(runes[lowerZindex : upperZindex+1]))
}

// substr0(s,m,n) gives substring of s from 0-up position m to n inclusive.
// Negative indices -len .. -1 alias to 0 .. len-1.

func BIF_substr_0_up(input1, input2, input3 *mlrval.Mlrval) *mlrval.Mlrval {
	if input1.IsAbsent() {
		return mlrval.ABSENT
	}
	if input1.IsError() {
		return mlrval.FromTypeErrorUnary("substr0", input1)
	}
	if input1.IsBytes() {
		return bytes_substr(input1, input2, input3, true)
	}
	sinput := input1.String()

	// Handle UTF-8 correctly: len(input1.AcquireStringValue()) will count bytes, not runes.
	runes := []rune(sinput)
	strlen := int(len(runes))

	sliceIsEmpty, absentOrError, lowerZindex, upperZindex := MillerSliceAccess(input2, input3, strlen, true)

	if sliceIsEmpty {
		return mlrval.VOID
	}
	if absentOrError != nil {
		return absentOrError
	}

	// Note Golang slice indices are 0-up, and the 1st index is inclusive
	// while the 2nd is exclusive. For Miller, indices are 1-up and both
	// are inclusive.
	return mlrval.FromString(string(runes[lowerZindex : upperZindex+1]))
}

// bytes_substr slices bytes by byte position, not rune position. An empty
// slice yields empty bytes rather than VOID, preserving the type.
func bytes_substr(input1, input2, input3 *mlrval.Mlrval, zeroUp bool) *mlrval.Mlrval {
	b := input1.AcquireBytesValue()

	sliceIsEmpty, absentOrError, lowerZindex, upperZindex := MillerSliceAccess(input2, input3, len(b), zeroUp)

	if sliceIsEmpty {
		return mlrval.FromBytes([]byte{})
	}
	if absentOrError != nil {
		return absentOrError
	}

	return mlrval.FromBytes(append([]byte(nil), b[lowerZindex:upperZindex+1]...))
}

// index(string, substring) returns the index of substring within string (if found), or -1 if not
// found.

func BIF_index(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	if input1.IsAbsent() {
		return mlrval.ABSENT
	}
	if input1.IsError() {
		return mlrval.FromTypeErrorUnary("index", input1)
	}
	if input1.IsArrayOrMap() || input2.IsArrayOrMap() {
		return mlrval.FromTypeErrorBinary("index", input1, input2)
	}
	sinput1 := input1.String()
	sinput2 := input2.String()

	// Handle UTF-8 correctly, since Go's strings.Index counts bytes
	iindex := strings.Index(sinput1, sinput2)
	if iindex < 0 {
		return mlrval.FromInt(int64(iindex))
	}

	// Go indices are 0-up; Miller indices are 1-up.
	return mlrval.FromInt(lib.UTF8Strlen(sinput1[:iindex]) + 1)
}

// contains(string, substring) returns true if string contains substring, else false.

func BIF_contains(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	if input1.IsAbsent() {
		return mlrval.ABSENT
	}
	if input1.IsError() {
		return input1
	}
	if input1.IsArrayOrMap() || input2.IsArrayOrMap() {
		return mlrval.FromTypeErrorBinary("contains", input1, input2)
	}

	return mlrval.FromBool(strings.Contains(input1.String(), input2.String()))
}

func BIF_truncate(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	if input1.IsErrorOrAbsent() {
		return input1
	}
	if input2.IsErrorOrAbsent() {
		return input2
	}
	if !input1.IsStringOrVoid() {
		return mlrval.FromTypeErrorUnary("truncate", input1)
	}
	if !input2.IsInt() {
		return mlrval.FromTypeErrorUnary("truncate", input2)
	}
	if input2.AcquireIntValue() < 0 {
		return mlrval.FromTypeErrorUnary("truncate", input2)
	}

	// Handle UTF-8 correctly: len(input1.AcquireStringValue()) will count bytes, not runes.
	runes := []rune(input1.AcquireStringValue())
	oldLength := int(len(runes))
	maxLength := int(input2.AcquireIntValue())
	if oldLength <= maxLength {
		return input1
	}
	return mlrval.FromString(string(runes[0:maxLength]))
}

func BIF_leftpad(input1, input2, input3 *mlrval.Mlrval) *mlrval.Mlrval {
	if input1.IsErrorOrAbsent() {
		return input1
	}
	if input2.IsErrorOrAbsent() {
		return input2
	}
	if input3.IsErrorOrAbsent() {
		return input3
	}

	if !input2.IsInt() {
		return mlrval.FromTypeErrorUnary("leftpad", input2)
	}

	inputString := input1.String()
	padString := input3.String()

	inputLength := lib.UTF8Strlen(inputString)
	padLength := lib.UTF8Strlen(padString)
	targetLength := input2.AcquireIntValue()
	outputLength := inputLength

	var buffer bytes.Buffer
	for outputLength+padLength <= targetLength {
		buffer.WriteString(padString)
		outputLength += padLength
	}
	buffer.WriteString(inputString)

	return mlrval.FromString(buffer.String())
}

func BIF_rightpad(input1, input2, input3 *mlrval.Mlrval) *mlrval.Mlrval {
	if input1.IsErrorOrAbsent() {
		return input1
	}
	if input2.IsErrorOrAbsent() {
		return input2
	}
	if input3.IsErrorOrAbsent() {
		return input3
	}

	if !input2.IsInt() {
		return mlrval.FromTypeErrorUnary("rightpad", input2)
	}

	inputString := input1.String()
	padString := input3.String()

	inputLength := lib.UTF8Strlen(inputString)
	padLength := lib.UTF8Strlen(padString)
	targetLength := input2.AcquireIntValue()
	outputLength := inputLength

	var buffer bytes.Buffer
	buffer.WriteString(inputString)
	for outputLength+padLength <= targetLength {
		buffer.WriteString(padString)
		outputLength += padLength
	}

	return mlrval.FromString(buffer.String())
}

func BIF_lstrip(input1 *mlrval.Mlrval) *mlrval.Mlrval {
	if input1.IsString() {
		return mlrval.FromString(strings.TrimLeft(input1.AcquireStringValue(), " \t"))
	}
	return input1
}

func BIF_rstrip(input1 *mlrval.Mlrval) *mlrval.Mlrval {
	if input1.IsString() {
		return mlrval.FromString(strings.TrimRight(input1.AcquireStringValue(), " \t"))
	}
	return input1
}

func BIF_strip(input1 *mlrval.Mlrval) *mlrval.Mlrval {
	if input1.IsString() {
		return mlrval.FromString(strings.Trim(input1.AcquireStringValue(), " \t"))
	}
	return input1
}

func BIF_collapse_whitespace(input1 *mlrval.Mlrval) *mlrval.Mlrval {
	return BIF_collapse_whitespace_regexp(input1, _whitespace_regexp)
}

func BIF_collapse_whitespace_regexp(input1 *mlrval.Mlrval, whitespaceRegexp *regexp.Regexp) *mlrval.Mlrval {
	if input1.IsString() {
		return mlrval.FromString(whitespaceRegexp.ReplaceAllString(input1.AcquireStringValue(), " "))
	}
	return input1
}

var _whitespace_regexp = regexp.MustCompile(`\s+`)

func BIF_toupper(input1 *mlrval.Mlrval) *mlrval.Mlrval {
	if input1.IsString() {
		return mlrval.FromString(strings.ToUpper(input1.AcquireStringValue()))
	} else if input1.IsVoid() {
		return input1
	}
	return input1
}

func BIF_tolower(input1 *mlrval.Mlrval) *mlrval.Mlrval {
	if input1.IsString() {
		return mlrval.FromString(strings.ToLower(input1.AcquireStringValue()))
	} else if input1.IsVoid() {
		return input1
	}
	return input1
}

func BIF_capitalize(input1 *mlrval.Mlrval) *mlrval.Mlrval {
	if input1.IsString() {
		if input1.AcquireStringValue() == "" {
			return input1
		}
		runes := []rune(input1.AcquireStringValue())
		rfirst := runes[0]
		rrest := runes[1:]
		sfirst := strings.ToUpper(string(rfirst))
		srest := string(rrest)
		return mlrval.FromString(sfirst + srest)
	}
	return input1
}

func BIF_clean_whitespace(input1 *mlrval.Mlrval) *mlrval.Mlrval {
	mv := BIF_strip(
		BIF_collapse_whitespace_regexp(
			input1, _whitespace_regexp,
		),
	)
	return mlrval.FromInferredType(mv.String())
}

// Matches either a sequential placeholder "{}" or a positional placeholder
// like "{1}", "{2}", etc.
var _format_placeholder_regexp = regexp.MustCompile(`\{[0-9]*\}`)

func BIF_format(mlrvals []*mlrval.Mlrval) *mlrval.Mlrval {
	if len(mlrvals) == 0 {
		return mlrval.VOID
	}
	formatString, ok := mlrvals[0].GetStringValue()
	if !ok { // not a string
		return mlrval.FromTypeErrorUnary("format", mlrvals[0])
	}

	// Example: format("{}:{}", 8, 9) gives "8:9".
	//
	// Placeholders come in two forms:
	// * "{}": consumes the next argument in sequence. The sequence counter is
	//   advanced only by "{}" placeholders, independently of any positional
	//   placeholders (same as Rust's format!).
	// * "{N}" with N >= 1: refers to the Nth argument after the format string
	//   (1-based). Arguments may be repeated and/or reordered this way:
	//   format("{1}:{2}:{1}", 8, 9) gives "8:9:8".
	//
	// Q: What if too few arguments for format, or a positional index exceeds
	//    the number of arguments?
	// A: Interpolate the empty string.
	// Q: What if too many arguments for format?
	// A: Leave them off.
	// Q: What about "{0}"?
	// A: It's an error value, since positional placeholders are 1-based.

	var buffer bytes.Buffer

	n := len(mlrvals) // arguments are mlrvals[1] .. mlrvals[n-1]
	position := 1     // implicit counter for "{}" placeholders
	remaining := formatString

	for {
		loc := _format_placeholder_regexp.FindStringIndex(remaining)
		if loc == nil {
			buffer.WriteString(remaining)
			break
		}
		buffer.WriteString(remaining[:loc[0]])
		placeholder := remaining[loc[0]:loc[1]]
		remaining = remaining[loc[1]:]

		var index int
		if placeholder == "{}" {
			index = position
			position++
		} else {
			numString := placeholder[1 : len(placeholder)-1]
			num, err := strconv.Atoi(numString)
			if err != nil || num < 1 {
				return mlrval.FromError(
					fmt.Errorf(
						"format: positional placeholder %s is invalid: indices are 1-based",
						placeholder,
					),
				)
			}
			index = num
		}

		if index < n {
			buffer.WriteString(mlrvals[index].String())
		}
		// Else, too few arguments: interpolate the empty string.
	}

	return mlrval.FromString(buffer.String())
}

// unformat("{}:{}:{}",  "1:2:3")    gives [1, 2]
// unformat("{}h{}m{}s", "3h47m22s") gives [3, 47, 22]
func BIF_unformat(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	return bif_unformat_aux(input1, input2, true)
}
func BIF_unformatx(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	return bif_unformat_aux(input1, input2, false)
}

func bif_unformat_aux(input1, input2 *mlrval.Mlrval, inferTypes bool) *mlrval.Mlrval {
	template, ok1 := input1.GetStringValue()
	if !ok1 {
		return mlrval.FromTypeErrorUnary("unformat", input1)
	}
	input, ok2 := input2.GetStringValue()
	if !ok2 {
		return mlrval.FromTypeErrorUnary("unformat", input2)
	}

	templatePieces := strings.Split(template, "{}")
	output := mlrval.FromEmptyArray()

	// template "{}h{}m{}s"
	// input    "12h34m56s"
	// templatePieces   ["", "h", "m", "s"]

	remaining := input

	if !strings.HasPrefix(remaining, templatePieces[0]) {
		return mlrval.FromError(
			fmt.Errorf(
				"unformat(\"%s\", \"%s\"): component \"%s\" lacks prefix \"%s\"",
				input1.OriginalString(),
				input2.OriginalString(),
				remaining,
				templatePieces[0],
			),
		)
	}
	remaining = remaining[len(templatePieces[0]):]
	templatePieces = templatePieces[1:]

	n := len(templatePieces)
	for i, templatePiece := range templatePieces {

		var index int
		if i == n-1 && templatePiece == "" {
			// strings.Index("", ...) will match the *start* of what's
			// remaining, whereas we want it to match the end.
			index = len(remaining)
		} else {
			index = strings.Index(remaining, templatePiece)
			if index < 0 {
				return mlrval.FromError(
					fmt.Errorf(
						"unformat(\"%s\", \"%s\"): component \"%s\" lacks prefix \"%s\"",
						input1.OriginalString(),
						input2.OriginalString(),
						remaining,
						templatePiece,
					),
				)
			}
		}

		inputPiece := remaining[:index]
		remaining = remaining[index+len(templatePiece):]
		if inferTypes {
			output.ArrayAppend(mlrval.FromInferredType(inputPiece))
		} else {
			output.ArrayAppend(mlrval.FromString(inputPiece))
		}
	}

	return output
}

func BIF_hexfmt(input1 *mlrval.Mlrval) *mlrval.Mlrval {
	if input1.IsInt() {
		return mlrval.FromString("0x" + strconv.FormatUint(uint64(input1.AcquireIntValue()), 16))
	}
	return input1
}

func fmtnum_is(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	if !input2.IsString() {
		return mlrval.FromTypeErrorUnary("fmtnum", input2)
	}
	formatString := input2.AcquireStringValue()
	formatter, err := mlrval.GetFormatter(formatString)
	if err != nil {
		return mlrval.FromError(err)
	}

	return formatter.Format(input1)
}

func fmtnum_fs(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	if !input2.IsString() {
		return mlrval.FromTypeErrorUnary("fmtnum", input2)
	}
	formatString := input2.AcquireStringValue()
	formatter, err := mlrval.GetFormatter(formatString)
	if err != nil {
		return mlrval.FromError(err)
	}

	return formatter.Format(input1)
}

func fmtnum_bs(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	if !input2.IsString() {
		return mlrval.FromTypeErrorUnary("fmtnum", input2)
	}
	formatString := input2.AcquireStringValue()
	formatter, err := mlrval.GetFormatter(formatString)
	if err != nil {
		return mlrval.FromError(err)
	}

	intMv := mlrval.FromInt(lib.BoolToInt(input1.AcquireBoolValue()))

	return formatter.Format(intMv)
}

func fmtnum_te(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	return mlrval.FromTypeErrorBinary("fmtnum", input1, input2)
}

var fmtnum_dispositions = [mlrval.MT_DIM][mlrval.MT_DIM]BinaryFunc{
	// . INT FLOAT BOOL VOID STRING BYTES ARRAY MAP FUNC ERROR NULL ABSENT
	/*INT    */ {fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_is, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, _absn},
	/*FLOAT  */ {fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_fs, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, _absn},
	/*BOOL   */ {fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_bs, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, _absn},
	/*VOID   */ {fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, _absn},
	/*STRING */ {fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, _absn},
	/*BYTES  */ {fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, _absn},
	/*ARRAY  */ {fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, _absn},
	/*MAP    */ {fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, _absn},
	/*FUNC   */ {fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te},
	/*ERROR  */ {fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te},
	/*NULL   */ {fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, _absn},
	/*ABSENT */ {_absn, _absn, fmtnum_te, _absn, _absn, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, fmtnum_te, _absn, _absn},
}

func BIF_fmtnum(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	if input1.IsArray() || input1.IsMap() {
		return recurseBinaryFuncOnInput1(BIF_fmtnum, input1, input2)
	}
	return fmtnum_dispositions[input1.Type()][input2.Type()](input1, input2)
}

func BIF_fmtifnum(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	if input1.IsArray() || input1.IsMap() {
		return recurseBinaryFuncOnInput1(BIF_fmtifnum, input1, input2)
	}
	output := fmtnum_dispositions[input1.Type()][input2.Type()](input1, input2)
	if output.IsError() {
		return input1
	} else {
		return output
	}
}

func BIF_latin1_to_utf8(input1 *mlrval.Mlrval) *mlrval.Mlrval {
	if input1.IsArray() || input1.IsMap() {
		return recurseUnaryFuncOnInput1(BIF_latin1_to_utf8, input1)
	} else if input1.IsString() {
		output, err := lib.TryLatin1ToUTF8(input1.String())
		if err != nil {
			// Somewhat arbitrary design decision
			// return input1
			return mlrval.FromError(err)
		}
		return mlrval.FromString(output)
	}
	return input1
}

func BIF_utf8_to_latin1(input1 *mlrval.Mlrval) *mlrval.Mlrval {
	if input1.IsArray() || input1.IsMap() {
		return recurseUnaryFuncOnInput1(BIF_utf8_to_latin1, input1)
	} else if input1.IsString() {
		output, err := lib.TryUTF8ToLatin1(input1.String())
		if err != nil {
			// Somewhat arbitrary design decision
			// return input1
			return mlrval.FromError(err)
		}
		return mlrval.FromString(output)
	}
	return input1
}
