package registry_test

import (
	"flow/internal/assert"
	"flow/registry"
	"testing"
)

func TestRegistry(t *testing.T) {
	a := assert.A(t)

	r := registry.New()
	r.Register("vecadd", dummy1)

	e, err := r.Entry("vecadd")
	a.Eq(err, nil, "fetching entry")

	d := e.Description

	a.Eq(len(d.Inputs), 2, "should have 2 outputs")
	a.Eq(d.Output.Type, "[]float32", "output type")

	t.Log(d)

}

func TestEntry(t *testing.T) {
	a := assert.A(t)
	r := registry.New()
	e, err := r.Entry("bogus")

	a.NotEq(err, nil, "should get an error")
	a.Eq(e, nil, "entry should be nil")

}

func TestRegister(t *testing.T) {
	a := assert.A(t)
	r := registry.New()
	e := r.Register("func", func(a, b int) int { return 0 })
	a.Eq(e.Err(), nil, "should register a function")
}
func TestRegisterDuplicate(t *testing.T) {
	a := assert.A(t)
	r := registry.New()
	r.Register("func", func(a, b int) int { return 0 })
	e := r.Register("func", func(b int) int { return 0 })

	a.Eq(e.Err(), nil, "should allow duplicate")
}
func TestRegisterInvalidOutput(t *testing.T) {
	a := assert.A(t)
	r := registry.New()
	e := r.Register("func", func(a int) {})
	a.Eq(e.Err(), registry.ErrOutput, "should give output error")
}

func TestRegisterInvalidInput(t *testing.T) {
	a := assert.A(t)
	r := registry.New()
	e := r.Register("func", func() int { return 0 })
	a.Eq(e.Err(), nil, "should register a func without params")
}

func TestRegistryGet(t *testing.T) {
	a := assert.A(t)
	r := registry.New()
	e := r.Register("func", func() int { return 0 })
	a.Eq(e.Err(), nil, "should register func")

	fn, err := r.Get("func")
	a.Eq(err, nil, "should fetch a function")
	a.NotEq(fn, nil, "fn should not be nil")
}

func TestRegistryGetEmpty(t *testing.T) {
	a := assert.A(t)
	r := registry.New()
	fn, err := r.Get("notfoundfunc")
	a.Eq(err, registry.ErrNotFound, "should fail fetching a unregistered func")
	a.Eq(fn, nil, "should return a <nil> func")
}
func TestRegistryGetConstructor(t *testing.T) {
	a := assert.A(t)
	r := registry.New()
	e := r.Register("func", func() func() int {
		return func() int {
			return 0
		}
	})
	a.Eq(e.Err(), nil, "should register the constructor func")

	ifn, err := r.Get("func")
	a.Eq(err, nil, "get should not error")

	fn, ok := ifn.(func() int)
	a.Eq(ok, true, "function should be the constructor type")

	ret := fn()
	a.Eq(ret, 0, "function should return 0")
}

func TestRegistryGetConstructorParam(t *testing.T) {
	a := assert.A(t)

	r := registry.New()
	e := r.Register("func2", func(a, b int) func() int {
		return func() int {
			return a + b
		}
	})
	a.Eq(e.Err(), nil)
	ifn, err := r.Get("func2", 1, 1)
	a.Eq(err, nil, "should not fail passing params to constructor")
	a.NotEq(ifn, nil, "should return a function")

	fn, ok := ifn.(func() int)
	a.Eq(ok, true, "function should match the type")

	ret := fn()
	a.Eq(ret, 2, "function should execute")

}

func TestDescriptions(t *testing.T) {
	a := assert.A(t)

	r := registry.New()
	r.Register("vecadd", dummy1)
	r.Register("vecstr", dummy2)

	d, err := r.Descriptions()
	a.Eq(err, nil, "should fetch Descriptions")
	a.Eq(len(d), 2, "should contain 2 descriptions")

	t.Log(d)
}
func TestClone(t *testing.T) {
	a := assert.A(t)
	r := registry.New()
	r.Register("vecadd", dummy1)

	d, err := r.Descriptions()
	a.Eq(err, nil, "should not error fetching description")

	a.Eq(len(d), 1, "should contain 1 descriptions")

	r2 := r.Clone()
	r2.Register("vecmul", dummy2)
	a.Eq(len(d), 1, "should contain 1 descriptions")

	d2, err := r2.Descriptions()
	a.Eq(err, nil, "should not error fetching descriptions")

	a.Eq(len(d2), 2, "should contain 2 descriptions")
	_, ok := d2["vecmul"]
	a.Eq(ok, true, "should be equal")
}

func TestAddEntry(t *testing.T) {
	a := assert.A(t)
	r := registry.New()

	g := r.Add(r.Register("vecadd", dummy1))
	a.Eq(len(g), 0, "should contain 1 entry")

	r.Add(dummy2)

	d, err := r.Descriptions()
	a.Eq(err, nil, "should not error fetching descriptions")
	a.Eq(len(d), 2, "should contain 2 descriptions")

}

func dummy1([]float32, []float32) []float32 {
	return []float32{1, 3, 3, 7}
}
func dummy2([]float32) string {
	return ""
}
