// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Tetragon

// CEL -> BPF code generation
// Heavily based on an earlier implementation by Yutaro Hayakawa <yutaro.hayakawa@isovalent.com>

package celbpf

import (
	"errors"
	"fmt"

	gt "github.com/cilium/tetragon/pkg/generictypes"

	cgDecls "github.com/google/cel-go/common/decls"
	cgOverloads "github.com/google/cel-go/common/overloads"
	cgTypes "github.com/google/cel-go/common/types"
)

var (
	s64Ty         = cgTypes.IntType
	u64Ty         = cgTypes.UintType
	s32Ty         = cgTypes.NewOpaqueType("s32")
	u32Ty         = cgTypes.NewOpaqueType("u32")
	unsupportedTy = cgTypes.NewOpaqueType("unsupported")

	addS32 = "add_s32"
	addU32 = "add_u32"
	subS32 = "sub_s32"
	subU32 = "sub_u32"

	ltS32 = "lt_s32"
	ltU32 = "lt_u32"
	lqS32 = "lq_s32"
	lqU32 = "lq_u32"
	gtS32 = "gt_s32"
	gtU32 = "gt_u32"
	gqS32 = "gq_s32"
	gqU32 = "gq_u32"
)

type intType struct {
	ty          *cgTypes.Type
	overloadAdd string
	overloadSub string
}

var intTypes = []intType{
	{s64Ty, cgOverloads.AddInt64, cgOverloads.SubtractInt64},
	{s32Ty, addS32, subS32},
	{u64Ty, cgOverloads.AddUint64, cgOverloads.SubtractUint64},
	{u32Ty, addU32, subU32},
}

func addOperatorFunctionOpts() []cgDecls.FunctionOpt {
	ret := make([]cgDecls.FunctionOpt, 0, len(intTypes))
	for _, ity := range intTypes {
		ret = append(ret, cgDecls.Overload(
			ity.overloadAdd,
			[]*cgTypes.Type{ity.ty, ity.ty}, ity.ty,
		))
	}
	return ret
}

func subOperatorFunctionOpts() []cgDecls.FunctionOpt {
	ret := make([]cgDecls.FunctionOpt, 0, len(intTypes))
	for _, ity := range intTypes {
		ret = append(ret, cgDecls.Overload(
			ity.overloadSub,
			[]*cgTypes.Type{ity.ty, ity.ty}, ity.ty,
		))
	}
	return ret
}

func typeFromGenTy(genTy int) (*cgTypes.Type, error) {
	switch genTy {
	case gt.GenericS64Type:
		return s64Ty, nil
	case gt.GenericS32Type, gt.GenericIntType:
		return s32Ty, nil
	case gt.GenericU64Type:
		return u64Ty, nil
	case gt.GenericU32Type:
		return u32Ty, nil
	case gt.GenericInvalidType:
		return nil, errors.New("cannot convert invalid generic type")
	}

	return nil, fmt.Errorf("unhandled generic type: %d (%s)", genTy, gt.GenericTypeString(genTy))
}
