// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package runtime_test

import (
	"internal/runtime/sys"
	"runtime"
	"testing"
	"unsafe"
)

// The tests in this file are identical to list_test.go, but for the
// manually-managed variants.

type listedValManual struct {
	val int

	aNode runtime.ListNodeManual
	bNode runtime.ListNodeManual
}

// ListHeadManual is intended to be used with objects where the lifetime of the
// object is managed explicitly, so it is OK to store as uintptr.
//
// This means that our test values must outlive the test, and must not live on
// the stack (which may move).
var allListedValManual []*listedValManual

func newListedValManual(v int) *listedValManual {
	lv := &listedValManual{
		val: v,
	}
	allListedValManual = append(allListedValManual, lv)
	return lv
}

func TestListManualPush(t *testing.T) {
	var headA runtime.ListHeadManual
	headA.Init(unsafe.Offsetof(listedValManual{}.aNode))

	one := newListedValManual(1)
	headA.Push(unsafe.Pointer(one))

	two := newListedValManual(2)
	headA.Push(unsafe.Pointer(two))

	three := newListedValManual(3)
	headA.Push(unsafe.Pointer(three))

	p := headA.Pop()
	v := (*listedValManual)(p)
	if v == nil {
		t.Fatalf("pop got nil want 3")
	}
	if v.val != 3 {
		t.Errorf("pop got %d want 3", v.val)
	}

	p = headA.Pop()
	v = (*listedValManual)(p)
	if v == nil {
		t.Fatalf("pop got nil want 2")
	}
	if v.val != 2 {
		t.Errorf("pop got %d want 2", v.val)
	}

	p = headA.Pop()
	v = (*listedValManual)(p)
	if v == nil {
		t.Fatalf("pop got nil want 1")
	}
	if v.val != 1 {
		t.Errorf("pop got %d want 1", v.val)
	}

	p = headA.Pop()
	v = (*listedValManual)(p)
	if v != nil {
		t.Fatalf("pop got %+v want nil", v)
	}

	runtime.KeepAlive(one)
	runtime.KeepAlive(two)
	runtime.KeepAlive(three)
}

func wantValManual(t *testing.T, v *listedValManual, i int) {
	t.Helper()
	if v == nil {
		t.Fatalf("listedVal got nil want %d", i)
	}
	if v.val != i {
		t.Errorf("pop got %d want %d", v.val, i)
	}
}

func TestListManualRemoveHead(t *testing.T) {
	var headA runtime.ListHeadManual
	headA.Init(unsafe.Offsetof(listedValManual{}.aNode))

	one := newListedValManual(1)
	headA.Push(unsafe.Pointer(one))

	two := newListedValManual(2)
	headA.Push(unsafe.Pointer(two))

	three := newListedValManual(3)
	headA.Push(unsafe.Pointer(three))

	headA.Remove(unsafe.Pointer(three))

	p := headA.Pop()
	v := (*listedValManual)(p)
	wantValManual(t, v, 2)

	p = headA.Pop()
	v = (*listedValManual)(p)
	wantValManual(t, v, 1)

	p = headA.Pop()
	v = (*listedValManual)(p)
	if v != nil {
		t.Fatalf("pop got %+v want nil", v)
	}

	runtime.KeepAlive(one)
	runtime.KeepAlive(two)
	runtime.KeepAlive(three)
}

func TestListManualRemoveMiddle(t *testing.T) {
	var headA runtime.ListHeadManual
	headA.Init(unsafe.Offsetof(listedValManual{}.aNode))

	one := newListedValManual(1)
	headA.Push(unsafe.Pointer(one))

	two := newListedValManual(2)
	headA.Push(unsafe.Pointer(two))

	three := newListedValManual(3)
	headA.Push(unsafe.Pointer(three))

	headA.Remove(unsafe.Pointer(two))

	p := headA.Pop()
	v := (*listedValManual)(p)
	wantValManual(t, v, 3)

	p = headA.Pop()
	v = (*listedValManual)(p)
	wantValManual(t, v, 1)

	p = headA.Pop()
	v = (*listedValManual)(p)
	if v != nil {
		t.Fatalf("pop got %+v want nil", v)
	}

	runtime.KeepAlive(one)
	runtime.KeepAlive(two)
	runtime.KeepAlive(three)
}

func TestListManualRemoveTail(t *testing.T) {
	var headA runtime.ListHeadManual
	headA.Init(unsafe.Offsetof(listedValManual{}.aNode))

	one := newListedValManual(1)
	headA.Push(unsafe.Pointer(one))

	two := newListedValManual(2)
	headA.Push(unsafe.Pointer(two))

	three := newListedValManual(3)
	headA.Push(unsafe.Pointer(three))

	headA.Remove(unsafe.Pointer(one))

	p := headA.Pop()
	v := (*listedValManual)(p)
	wantValManual(t, v, 3)

	p = headA.Pop()
	v = (*listedValManual)(p)
	wantValManual(t, v, 2)

	p = headA.Pop()
	v = (*listedValManual)(p)
	if v != nil {
		t.Fatalf("pop got %+v want nil", v)
	}

	runtime.KeepAlive(one)
	runtime.KeepAlive(two)
	runtime.KeepAlive(three)
}

func TestListManualRemoveAll(t *testing.T) {
	var headA runtime.ListHeadManual
	headA.Init(unsafe.Offsetof(listedValManual{}.aNode))

	one := newListedValManual(1)
	headA.Push(unsafe.Pointer(one))

	two := newListedValManual(2)
	headA.Push(unsafe.Pointer(two))

	three := newListedValManual(3)
	headA.Push(unsafe.Pointer(three))

	headA.Remove(unsafe.Pointer(one))
	headA.Remove(unsafe.Pointer(two))
	headA.Remove(unsafe.Pointer(three))

	p := headA.Pop()
	v := (*listedValManual)(p)
	if v != nil {
		t.Fatalf("pop got %+v want nil", v)
	}

	runtime.KeepAlive(one)
	runtime.KeepAlive(two)
	runtime.KeepAlive(three)
}

// The tests below are identical, but used with a sys.NotInHeap type.

type listedValNIH struct {
	_ sys.NotInHeap
	listedValManual
}

func newListedValNIH(v int) *listedValNIH {
	l := (*listedValNIH)(runtime.PersistentAlloc(unsafe.Sizeof(listedValNIH{}), unsafe.Alignof(listedValNIH{})))
	l.val = v
	return l
}

func newListHeadNIH() *runtime.ListHeadManual {
	return (*runtime.ListHeadManual)(runtime.PersistentAlloc(unsafe.Sizeof(runtime.ListHeadManual{}), unsafe.Alignof(runtime.ListHeadManual{})))
}

func TestListNIHPush(t *testing.T) {
	headA := newListHeadNIH()
	headA.Init(unsafe.Offsetof(listedValNIH{}.aNode))

	one := newListedValNIH(1)
	headA.Push(unsafe.Pointer(one))

	two := newListedValNIH(2)
	headA.Push(unsafe.Pointer(two))

	three := newListedValNIH(3)
	headA.Push(unsafe.Pointer(three))

	p := headA.Pop()
	v := (*listedValNIH)(p)
	if v == nil {
		t.Fatalf("pop got nil want 3")
	}
	if v.val != 3 {
		t.Errorf("pop got %d want 3", v.val)
	}

	p = headA.Pop()
	v = (*listedValNIH)(p)
	if v == nil {
		t.Fatalf("pop got nil want 2")
	}
	if v.val != 2 {
		t.Errorf("pop got %d want 2", v.val)
	}

	p = headA.Pop()
	v = (*listedValNIH)(p)
	if v == nil {
		t.Fatalf("pop got nil want 1")
	}
	if v.val != 1 {
		t.Errorf("pop got %d want 1", v.val)
	}

	p = headA.Pop()
	v = (*listedValNIH)(p)
	if v != nil {
		t.Fatalf("pop got %+v want nil", v)
	}
}

func wantValNIH(t *testing.T, v *listedValNIH, i int) {
	t.Helper()
	if v == nil {
		t.Fatalf("listedVal got nil want %d", i)
	}
	if v.val != i {
		t.Errorf("pop got %d want %d", v.val, i)
	}
}

func TestListNIHRemoveHead(t *testing.T) {
	headA := newListHeadNIH()
	headA.Init(unsafe.Offsetof(listedValNIH{}.aNode))

	one := newListedValNIH(1)
	headA.Push(unsafe.Pointer(one))

	two := newListedValNIH(2)
	headA.Push(unsafe.Pointer(two))

	three := newListedValNIH(3)
	headA.Push(unsafe.Pointer(three))

	headA.Remove(unsafe.Pointer(three))

	p := headA.Pop()
	v := (*listedValNIH)(p)
	wantValNIH(t, v, 2)

	p = headA.Pop()
	v = (*listedValNIH)(p)
	wantValNIH(t, v, 1)

	p = headA.Pop()
	v = (*listedValNIH)(p)
	if v != nil {
		t.Fatalf("pop got %+v want nil", v)
	}
}

func TestListNIHRemoveMiddle(t *testing.T) {
	headA := newListHeadNIH()
	headA.Init(unsafe.Offsetof(listedValNIH{}.aNode))

	one := newListedValNIH(1)
	headA.Push(unsafe.Pointer(one))

	two := newListedValNIH(2)
	headA.Push(unsafe.Pointer(two))

	three := newListedValNIH(3)
	headA.Push(unsafe.Pointer(three))

	headA.Remove(unsafe.Pointer(two))

	p := headA.Pop()
	v := (*listedValNIH)(p)
	wantValNIH(t, v, 3)

	p = headA.Pop()
	v = (*listedValNIH)(p)
	wantValNIH(t, v, 1)

	p = headA.Pop()
	v = (*listedValNIH)(p)
	if v != nil {
		t.Fatalf("pop got %+v want nil", v)
	}
}

func TestListNIHRemoveTail(t *testing.T) {
	headA := newListHeadNIH()
	headA.Init(unsafe.Offsetof(listedValNIH{}.aNode))

	one := newListedValNIH(1)
	headA.Push(unsafe.Pointer(one))

	two := newListedValNIH(2)
	headA.Push(unsafe.Pointer(two))

	three := newListedValNIH(3)
	headA.Push(unsafe.Pointer(three))

	headA.Remove(unsafe.Pointer(one))

	p := headA.Pop()
	v := (*listedValNIH)(p)
	wantValNIH(t, v, 3)

	p = headA.Pop()
	v = (*listedValNIH)(p)
	wantValNIH(t, v, 2)

	p = headA.Pop()
	v = (*listedValNIH)(p)
	if v != nil {
		t.Fatalf("pop got %+v want nil", v)
	}
}

func TestListNIHRemoveAll(t *testing.T) {
	headA := newListHeadNIH()
	headA.Init(unsafe.Offsetof(listedValNIH{}.aNode))

	one := newListedValNIH(1)
	headA.Push(unsafe.Pointer(one))

	two := newListedValNIH(2)
	headA.Push(unsafe.Pointer(two))

	three := newListedValNIH(3)
	headA.Push(unsafe.Pointer(three))

	headA.Remove(unsafe.Pointer(one))
	headA.Remove(unsafe.Pointer(two))
	headA.Remove(unsafe.Pointer(three))

	p := headA.Pop()
	v := (*listedValNIH)(p)
	if v != nil {
		t.Fatalf("pop got %+v want nil", v)
	}
}
