// SPDX-FileCopyrightText: 2022 Free Mobile
// SPDX-License-Identifier: AGPL-3.0-only

package intern

import (
	"testing"

	"akvorado/common/helpers"

	"github.com/google/go-cmp/cmp/cmpopts"
)

type likeInt int

func (i likeInt) Equal(j likeInt) bool { return i == j }
func (i likeInt) Hash() uint64         { return uint64(i) % 10 }

func TestPut(t *testing.T) {
	p := NewPool[likeInt]()

	a := p.Put(likeInt(10))
	b := p.Put(likeInt(10))
	c := p.Put(likeInt(11))
	d := p.Put(likeInt(12))

	if a != b {
		t.Error("got two references for Put(10)")
	}
	if a == c || a == d || c == d {
		t.Error("got same reference for Put(10)/Put(11)/Put(12)")
	}
	if p.Get(a) != likeInt(10) {
		t.Errorf("Get(Put(10)) == %d != 10", p.Get(a))
	}
	if p.Get(c) != likeInt(11) {
		t.Errorf("Get(Put(11)) == %d != 10", p.Get(c))
	}
	if p.Get(d) != likeInt(12) {
		t.Errorf("Get(Put(12)) == %d != 10", p.Get(d))
	}
}

func TestRef(t *testing.T) {
	p := NewPool[likeInt]()
	a := p.Put(likeInt(10))
	b, bOK := p.Ref(likeInt(10))
	c, cOK := p.Ref(likeInt(20))
	if a != b {
		t.Error("got two references for Put/Ref(10)")
	}
	if !bOK {
		t.Error("didn't get a ref for Ref(10)")
	}
	if cOK {
		t.Error("got a ref for Ref(20)")
	}
	if c != 0 {
		t.Error("got a value for Ref(20)")
	}
}

func TestPutCollision(t *testing.T) {
	p := NewPool[likeInt]()

	a := p.Put(likeInt(10))
	b := p.Put(likeInt(20))
	c := p.Put(likeInt(11))
	d := p.Put(likeInt(21))
	if a == b || a == c || a == d || b == c || b == d || c == d {
		t.Error("got same reference for two different values")
	}
}

func TestTake(t *testing.T) {
	p := NewPool[likeInt]()
	diffOpt := cmpopts.EquateComparable(internValue[likeInt]{})

	val1 := likeInt(10)
	ref1 := p.Put(val1)
	val2 := likeInt(10)
	ref2 := p.Put(val2)
	val3 := likeInt(12)
	ref3 := p.Put(val3)
	val4 := likeInt(22) // collision
	ref4 := p.Put(val4)
	val5 := likeInt(32)
	ref5 := p.Put(val5)

	expectedValues := []internValue[likeInt]{
		{},
		{value: 10, refCount: 2},
		{value: 12, refCount: 1, next: 3},
		{value: 22, refCount: 1, previous: 2, next: 4},
		{value: 32, refCount: 1, previous: 3},
	}
	if diff := helpers.Diff(p.values, expectedValues, diffOpt); diff != "" {
		t.Fatalf("p.values (-got, +want):\n%s", diff)
	}

	p.Take(ref4)

	expectedValues = []internValue[likeInt]{
		{},
		{value: 10, refCount: 2},
		{value: 12, refCount: 1, next: 4},
		{value: 22, refCount: 0, previous: 2, next: 4}, // free
		{value: 32, refCount: 1, previous: 2},
	}
	if diff := helpers.Diff(p.values, expectedValues, diffOpt); diff != "" {
		t.Fatalf("p.values (-got, +want):\n%s", diff)
	}

	ref6 := p.Put(likeInt(42))
	if ref6 != ref4 {
		t.Fatal("p.Put() did not reuse free slot")
	}

	expectedValues = []internValue[likeInt]{
		{},
		{value: 10, refCount: 2},
		{value: 12, refCount: 1, next: 4},
		{value: 42, refCount: 1, previous: 4},
		{value: 32, refCount: 1, previous: 2, next: 3},
	}
	if diff := helpers.Diff(p.values, expectedValues, diffOpt); diff != "" {
		t.Fatalf("p.values (-got, +want):\n%s", diff)
	}

	p.Take(ref3)

	expectedValues = []internValue[likeInt]{
		{},
		{value: 10, refCount: 2},
		{value: 12, refCount: 0, next: 4}, // free
		{value: 42, refCount: 1, previous: 4},
		{value: 32, refCount: 1, next: 3},
	}
	if diff := helpers.Diff(p.values, expectedValues, diffOpt); diff != "" {
		t.Fatalf("p.values (-got, +want):\n%s", diff)
	}

	p.Take(ref5)

	expectedValues = []internValue[likeInt]{
		{},
		{value: 10, refCount: 2},
		{value: 12, refCount: 0, next: 4}, // free
		{value: 42, refCount: 1},
		{value: 32, refCount: 0, next: 3}, // free
	}
	if diff := helpers.Diff(p.values, expectedValues, diffOpt); diff != "" {
		t.Fatalf("p.values (-got, +want):\n%s", diff)
	}

	p.Take(ref6)

	expectedValues = []internValue[likeInt]{
		{},
		{value: 10, refCount: 2},
		{value: 12, refCount: 0, next: 4}, // free
		{value: 42, refCount: 0},          // free
		{value: 32, refCount: 0, next: 3}, // free
	}
	if diff := helpers.Diff(p.values, expectedValues, diffOpt); diff != "" {
		t.Fatalf("p.values (-got, +want):\n%s", diff)
	}

	p.Take(ref1)
	p.Take(ref2)
	diff := p.Len()
	if diff != 0 {
		t.Fatalf("Take() didn't free everything (%d remaining)", diff)
	}
}
