package {{.PackageName}}
{{if .IsScalar -}}
// #cgo CFLAGS: -I./include/
// #include "scalar_field.h"
import "C"
{{- end}}
import (
	"encoding/binary"
	"fmt"
	{{- if .IsScalar}}
	"unsafe"
	"github.com/ingonyama-zk/icicle/v2/wrappers/golang/core"
	cr "github.com/ingonyama-zk/icicle/v2/wrappers/golang/cuda_runtime"
	{{- end}}
)

const (
	{{toConst .FieldPrefix}}LIMBS int = {{.NUM_LIMBS}}
)

type {{.FieldPrefix}}Field struct {
	limbs [{{toConst .FieldPrefix}}LIMBS]uint32
}

func (f {{.FieldPrefix}}Field) Len() int {
	return int({{toConst .FieldPrefix}}LIMBS)
}

func (f {{.FieldPrefix}}Field) Size() int {
	return int({{toConst .FieldPrefix}}LIMBS * 4)
}

func (f {{.FieldPrefix}}Field) GetLimbs() []uint32 {
	return f.limbs[:]
}

func (f {{.FieldPrefix}}Field) AsPointer() *uint32 {
	return &f.limbs[0]
}

func (f *{{.FieldPrefix}}Field) FromUint32(v uint32) {{.FieldPrefix}}Field {
	f.limbs[0] = v
	return *f
}

func (f *{{.FieldPrefix}}Field) FromLimbs(limbs []uint32) {{.FieldPrefix}}Field {
	if len(limbs) != f.Len() {
		panic("Called FromLimbs with limbs of different length than field")
	}
	for i := range f.limbs {
		f.limbs[i] = limbs[i]
	}

	return *f
}

func (f *{{.FieldPrefix}}Field) Zero() {{.FieldPrefix}}Field {
	for i := range f.limbs {
		f.limbs[i] = 0
	}

	return *f
}

func (f *{{.FieldPrefix}}Field) One() {{.FieldPrefix}}Field {
	for i := range f.limbs {
		f.limbs[i] = 0
	}
	f.limbs[0] = 1

	return *f
}

func (f *{{.FieldPrefix}}Field) FromBytesLittleEndian(bytes []byte) {{.FieldPrefix}}Field {
	if len(bytes)/4 != f.Len() {
		panic(fmt.Sprintf("Called FromBytesLittleEndian with incorrect bytes length; expected %d - got %d", f.Len()*4, len(bytes)))
	}

	for i := range f.limbs {
		f.limbs[i] = binary.LittleEndian.Uint32(bytes[i*4 : i*4+4])
	}

	return *f
}

func (f {{.FieldPrefix}}Field) ToBytesLittleEndian() []byte {
	bytes := make([]byte, f.Len()*4)
	for i, v := range f.limbs {
		binary.LittleEndian.PutUint32(bytes[i*4:], v)
	}

	return bytes
}
{{if .IsScalar}}
func GenerateScalars(size int) core.HostSlice[{{.FieldPrefix}}Field] {
	scalarSlice := make(core.HostSlice[{{.FieldPrefix}}Field], size)

	cScalars := (*C.scalar_t)(unsafe.Pointer(&scalarSlice[0]))
	cSize := (C.int)(size)
	C.{{.Field}}_generate_scalars(cScalars, cSize)

	return scalarSlice
}

func convertScalarsMontgomery(scalars *core.DeviceSlice, isInto bool) cr.CudaError {
	cValues := (*C.scalar_t)(scalars.AsUnsafePointer())
	cSize := (C.size_t)(scalars.Len())
	cIsInto := (C._Bool)(isInto)
	defaultCtx, _ := cr.GetDefaultDeviceContext()
	cCtx := (*C.DeviceContext)(unsafe.Pointer(&defaultCtx))
	__ret := C.{{.Field}}_scalar_convert_montgomery(cValues, cSize, cIsInto, cCtx)
	err := (cr.CudaError)(__ret)
	return err
}

func ToMontgomery(scalars *core.DeviceSlice) cr.CudaError {
	scalars.CheckDevice()
	return convertScalarsMontgomery(scalars, true)
}

func FromMontgomery(scalars *core.DeviceSlice) cr.CudaError {
	scalars.CheckDevice()
	return convertScalarsMontgomery(scalars, false)
}{{end}}
