// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.

// builtin-base.go
package expr

import (
	"fmt"
	"math"
	"strconv"
)

const (
	ParamDenominator = "denominator"
)

func isNilFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
	result = args[ParamValue] == nil
	return
}

func isIntFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
	result = IsInteger(args[ParamValue])
	return
}

func isFloatFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
	result = IsFloat(args[ParamValue])
	return
}

func isBoolFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
	result = IsBool(args[ParamValue])
	return
}

func isStringFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
	result = IsString(args[ParamValue])
	return
}

func isFractionFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
	result = IsFract(args[ParamValue])
	return
}

func isRationalFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
	result = IsRational(args[ParamValue])
	return
}

func isListFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
	result = IsList(args[ParamValue])
	return
}

func isDictionaryFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
	result = IsDict(args[ParamValue])
	return
}

func boolFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
	switch v := args[ParamValue].(type) {
	case int64:
		result = (v != 0)
	case *FractionType:
		result = v.num != 0
	case float64:
		result = v != 0.0
	case bool:
		result = v
	case string:
		result = len(v) > 0
	default:
		err = ErrCantConvert(name, v, "bool")
	}
	return
}

func intFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
	switch v := args[ParamValue].(type) {
	case int64:
		result = v
	case float64:
		result = int64(math.Trunc(v))
	case bool:
		if v {
			result = int64(1)
		} else {
			result = int64(0)
		}
	case string:
		var i int
		if i, err = strconv.Atoi(v); err == nil {
			result = int64(i)
		}
	default:
		err = ErrCantConvert(name, v, "int")
	}
	return
}

func decFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
	switch v := args[ParamValue].(type) {
	case int64:
		result = float64(v)
	case float64:
		result = v
	case bool:
		if v {
			result = float64(1)
		} else {
			result = float64(0)
		}
	case string:
		var f float64
		if f, err = strconv.ParseFloat(v, 64); err == nil {
			result = f
		}
	case *FractionType:
		result = v.toFloat()
	default:
		err = ErrCantConvert(name, v, "float")
	}
	return
}

func stringFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
	switch v := args[ParamValue].(type) {
	case int64:
		result = strconv.FormatInt(v, 10)
	case float64:
		result = strconv.FormatFloat(v, 'g', -1, 64)
	case bool:
		if v {
			result = "true"
		} else {
			result = "false"
		}
	case string:
		result = v
	case *FractionType:
		result = v.ToString(0)
	case Formatter:
		result = v.ToString(0)
	case fmt.Stringer:
		result = v.String()
	default:
		err = ErrCantConvert(name, v, "string")
	}
	return
}

func fractFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
	switch v := args[ParamValue].(type) {
	case int64:
		var den int64 = 1

		var ok bool
		if den, ok = args[ParamDenominator].(int64); !ok {
			err = ErrExpectedGot(name, "integer", args[ParamDenominator])
		} else if den == 0 {
			err = ErrFuncDivisionByZero(name)
		}

		if err == nil {
			result = newFraction(v, den)
		}
	case float64:
		result, err = float64ToFraction(v)
	case bool:
		if v {
			result = newFraction(1, 1)
		} else {
			result = newFraction(0, 1)
		}
	case string:
		result, err = makeGeneratingFraction(v)
	case *FractionType:
		result = v
	default:
		err = ErrCantConvert(name, v, "float")
	}
	return
}

// func iteratorFunc(ctx ExprContext, name string, args []any) (result any, err error) {
// 	return
// }

func ImportBuiltinsFuncs(ctx ExprContext) {
	anyParams := []ExprFuncParam{
		NewFuncParam(ParamValue),
	}

	ctx.RegisterFunc("isNil", NewGolangFunctor(isNilFunc), TypeBoolean, anyParams)
	ctx.RegisterFunc("isInt", NewGolangFunctor(isIntFunc), TypeBoolean, anyParams)
	ctx.RegisterFunc("isFloat", NewGolangFunctor(isFloatFunc), TypeBoolean, anyParams)
	ctx.RegisterFunc("isBool", NewGolangFunctor(isBoolFunc), TypeBoolean, anyParams)
	ctx.RegisterFunc("isString", NewGolangFunctor(isStringFunc), TypeBoolean, anyParams)
	ctx.RegisterFunc("isFract", NewGolangFunctor(isFractionFunc), TypeBoolean, anyParams)
	ctx.RegisterFunc("isRational", NewGolangFunctor(isRationalFunc), TypeBoolean, anyParams)
	ctx.RegisterFunc("isList", NewGolangFunctor(isListFunc), TypeBoolean, anyParams)
	ctx.RegisterFunc("isDict", NewGolangFunctor(isDictionaryFunc), TypeBoolean, anyParams)

	ctx.RegisterFunc("bool", NewGolangFunctor(boolFunc), TypeBoolean, anyParams)
	ctx.RegisterFunc("int", NewGolangFunctor(intFunc), TypeInt, anyParams)
	ctx.RegisterFunc("dec", NewGolangFunctor(decFunc), TypeFloat, anyParams)
	ctx.RegisterFunc("string", NewGolangFunctor(stringFunc), TypeString, anyParams)
	ctx.RegisterFunc("fract", NewGolangFunctor(fractFunc), TypeFraction, []ExprFuncParam{
		NewFuncParam(ParamValue),
		NewFuncParamFlagDef(ParamDenominator, PfDefault, int64(1)),
	})
}

func init() {
	RegisterBuiltinModule("base", ImportBuiltinsFuncs, "Base expression tools like isNil(), int(), etc.")
}