// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT

package path

import (
	"fmt"
	"testing"

	"code.forgejo.org/f3/gof3/v3/f3"
	"code.forgejo.org/f3/gof3/v3/id"
	"code.forgejo.org/f3/gof3/v3/kind"

	"github.com/stretchr/testify/assert"
)

type PathElementTest struct {
	id     id.NodeID
	mapped id.NodeID
	kind   kind.Kind
	name   string
}

func NewPathElementTest() PathElement {
	return &PathElementTest{
		id:     id.NilID,
		mapped: id.NilID,
	}
}

func (o PathElementTest) GetID() id.NodeID   { return o.id }
func (o *PathElementTest) SetID(i id.NodeID) { o.id = i }

func (o PathElementTest) GetMappedID() id.NodeID        { return o.mapped }
func (o *PathElementTest) SetMappedID(mapped id.NodeID) { o.mapped = mapped }

func (o PathElementTest) GetKind() kind.Kind      { return o.kind }
func (o *PathElementTest) SetKind(kind kind.Kind) { o.kind = kind }

type PathF3Test struct {
	f3.Common
	name string
}

func (o *PathF3Test) GetName() string {
	if o.name != "" {
		return o.name
	}
	return o.Common.GetName()
}

func (o PathElementTest) ToFormat() f3.Interface {
	return &PathF3Test{
		Common: f3.NewCommon(o.GetID().String()),
		name:   o.name,
	}
}

func TestNewPathFromString(t *testing.T) {
	for _, pathString := range []string{"", ".", "A/.."} {
		path := NewPathFromString(NewPathElementTest, pathString)
		assert.False(t, path.Empty(), path.String())
		assert.Len(t, path, 1, path.String())
		assert.EqualValues(t, ".", path.First().GetID())
	}
	for _, pathString := range []string{"/", "/.", "/..", "/A/.."} {
		path := NewPathFromString(NewPathElementTest, pathString)
		assert.False(t, path.Empty(), path.String())
		assert.Len(t, path, 1, path.String())
		assert.EqualValues(t, kind.KindRoot, path.First().GetKind(), path.String())
		assert.EqualValues(t, "", path.First().GetID())
	}
	for _, pathString := range []string{"A", "A/.", "B/../A"} {
		path := NewPathFromString(NewPathElementTest, pathString)
		assert.False(t, path.Empty(), path.String())
		assert.Len(t, path, 1, path.String())
		assert.NotEqualValues(t, kind.KindRoot, path.First().GetKind(), path.String())
		assert.EqualValues(t, "A", path.First().GetID())
	}
	{
		pathString := "/A"
		path := NewPathFromString(NewPathElementTest, pathString)
		assert.False(t, path.Empty(), path.String())
		assert.Len(t, path, 2, path.String())
		assert.EqualValues(t, kind.KindRoot, path.First().GetKind(), path.String())
		notRoot := path.RemoveFirst()
		assert.NotEqualValues(t, kind.KindRoot, notRoot.First().GetKind(), path.String())
		assert.EqualValues(t, pathString, path.String(), path.String())
	}
	{
		pathString := "A/B"
		path := NewPathFromString(NewPathElementTest, pathString)
		assert.False(t, path.Empty(), path.String())
		assert.Len(t, path, 2, path.String())
		assert.NotEqualValues(t, kind.KindRoot, path.First().GetKind(), path.String())
		notRoot := path.RemoveFirst()
		assert.NotEqualValues(t, kind.KindRoot, notRoot.First().GetKind(), path.String())
		assert.EqualValues(t, pathString, path.String(), path.String())
	}
	{
		pathString := "../B"
		path := NewPathFromString(NewPathElementTest, pathString)
		assert.False(t, path.Empty(), path.String())
		assert.Len(t, path, 2, path.String())
		assert.NotEqualValues(t, kind.KindRoot, path.First().GetKind(), path.String())
		notRoot := path.RemoveFirst()
		assert.NotEqualValues(t, kind.KindRoot, notRoot.First().GetKind(), path.String())
		assert.EqualValues(t, pathString, path.String(), path.String())
	}
}

func TestPathAbsoluteString(t *testing.T) {
	assert.Equal(t, "/c", PathAbsoluteString("/a/b", "/c/d/.."))
	assert.Equal(t, "/a/b/c", PathAbsoluteString("/a/b", "c"))
	assert.Equal(t, "/a/b/c", PathAbsoluteString("/a/./b", "c/d/.."))
}

func TestPathAbsolute(t *testing.T) {
	assert.Equal(t, "/a/b/c", PathAbsolute(NewPathElementTest, "/a/b", "c").String())
}

func TestPathRelativeString(t *testing.T) {
	assert.Equal(t, "../c", PathRelativeString("/a/b/d", "/a/b/c"))
	assert.Panics(t, func() { PathRelativeString("/a/b/d", "") })
}

func TestPathString(t *testing.T) {
	path := NewPathFromString(NewPathElementTest, "/a/b/c")
	assert.Equal(t, "/a/b/c", path.PathString().Join())
	last, path := path.Pop()
	path = path.Append(NewPathElementTest())
	path = path.Append(last)
	assert.Equal(t, "/a/b/nothing/c", path.PathString().Join())
}

func TestPathReadable(t *testing.T) {
	path := NewPathFromString(NewPathElementTest, "/a/b/c")
	name := "N{AM}E"
	path.Last().(*PathElementTest).name = name
	assert.Equal(t, name, path.Last().ToFormat().GetName())
	expected := fmt.Sprintf("/a/b/{%s/c}", replacer.Replace(name))
	assert.Equal(t, expected, path.ReadablePathString().Join())
	assert.Equal(t, expected, path.ReadableString())

	path = NewPathFromString(NewPathElementTest, "")
	assert.Equal(t, "", path.ReadableString())
}

func TestPathMappedString(t *testing.T) {
	path := NewPathFromString(NewPathElementTest, "/a/b")
	for _, node := range path.All() {
		node.SetMappedID(id.NewNodeID(node.GetID().String() + "M"))
	}
	assert.Equal(t, "M/aM/bM", path.PathMappedString().Join())
}

func TestPathMethods(t *testing.T) {
	path := NewPathFromString(NewPathElementTest, "/a/b/c")
	assert.Equal(t, "/a/b/c", path.String())
	assert.Equal(t, 4, path.Length())
	assert.Len(t, path.All(), 4)
	assert.False(t, path.Empty())

	first, path := path.PopFirst()
	assert.Equal(t, "", first.GetID().String())
	assert.Equal(t, "a/b/c", path.String())

	assert.Equal(t, "a", path.First().GetID().String())

	assert.Equal(t, "c", path.Last().GetID().String())

	firstRemoved := path.RemoveFirst()
	assert.Equal(t, "b/c", firstRemoved.String())

	last, lastRemoved := path.Pop()
	assert.Equal(t, "c", last.GetID().String())
	assert.Equal(t, "a/b", lastRemoved.String())

	lastRemoved = path.RemoveLast()
	assert.Equal(t, "a/b", lastRemoved.String())
}
