package merkletrie_test

import (
	"fmt"
	"io"
	"strings"

	"github.com/go-git/go-git/v5/utils/merkletrie"
	"github.com/go-git/go-git/v5/utils/merkletrie/internal/fsnoder"
	"github.com/go-git/go-git/v5/utils/merkletrie/noder"

	. "gopkg.in/check.v1"
)

type IterSuite struct{}

var _ = Suite(&IterSuite{})

// A test is a list of operations we want to perform on an iterator and
// their expected results.
//
// The operations are expressed as a sequence of `n` and `s`,
// representing the amount of next and step operations we want to call
// on the iterator and their order.  For example, an operations value of
// "nns" means: call a `n`ext, then another `n`ext and finish with a
// `s`tep.
//
// The expected is the full path of the noders returned by the
// operations, separated by spaces.
//
// For instance:
//
//     t := test{
//         operations: "ns",
//         expected:   "a a/b"
//     }
//
// means:
//
// - the first iterator operation has to be Next, and it must return a
// node called "a" with no ancestors.
//
// - the second operation has to be Step, and it must return a node
// called "b" with a single ancestor called "a".
type test struct {
	operations string
	expected   string
}

// Runs a test on the provided iterator, checking that the names of the
// returned values are correct.  If not, the treeDescription value is
// printed along with information about mismatch.
func (t test) run(c *C, iter *merkletrie.Iter,
	treeDescription string, testNumber int) {

	expectedChunks := strings.Split(t.expected, " ")
	if t.expected == "" {
		expectedChunks = []string{}
	}

	if len(t.operations) < len(expectedChunks) {
		c.Fatalf("malformed test %d: not enough operations", testNumber)
		return
	}

	var obtained noder.Path
	var err error
	for i, b := range t.operations {
		comment := Commentf("\ntree: %q\ntest #%d (%q)\noperation #%d (%q)",
			treeDescription, testNumber, t.operations, i, t.operations[i])

		switch t.operations[i] {
		case 'n':
			obtained, err = iter.Next()
			if err != io.EOF {
				c.Assert(err, IsNil)
			}
		case 's':
			obtained, err = iter.Step()
			if err != io.EOF {
				c.Assert(err, IsNil)
			}
		default:
			c.Fatalf("unknown operation at test %d, operation %d (%c)\n",
				testNumber, i, b)
		}
		if i >= len(expectedChunks) {
			c.Assert(err, Equals, io.EOF, comment)
			continue
		}

		c.Assert(err, IsNil, comment)
		c.Assert(obtained.String(), Equals, expectedChunks[i], comment)
	}
}

// A testsCollection value represents a tree and a collection of tests
// we want to perform on iterators of that tree.
//
// Example:
//
//            .
//            |
//        ---------
//        |   |   |
//        a   b   c
//            |
//            z
//
//    var foo testCollection = {
// 	      tree: "(a<> b(z<>) c<>)"
//     	  tests: []test{
//            {operations: "nns", expected: "a b b/z"},
//            {operations: "nnn", expected: "a b c"},
//		  },
//    }
//
// A new iterator will be build for each test.
type testsCollection struct {
	tree  string // a fsnoder description of a tree.
	tests []test // the collection of tests we want to run
}

// Executes all the tests in a testsCollection.
func (tc testsCollection) run(c *C) {
	root, err := fsnoder.New(tc.tree)
	c.Assert(err, IsNil)

	for i, t := range tc.tests {
		iter, err := merkletrie.NewIter(root)
		c.Assert(err, IsNil)
		t.run(c, iter, root.String(), i)
	}
}

func (s *IterSuite) TestEmptyNamedDir(c *C) {
	tc := testsCollection{
		tree: "A()",
		tests: []test{
			{operations: "n", expected: ""},
			{operations: "nn", expected: ""},
			{operations: "nnn", expected: ""},
			{operations: "nnns", expected: ""},
			{operations: "nnnssnsnns", expected: ""},
			{operations: "s", expected: ""},
			{operations: "ss", expected: ""},
			{operations: "sss", expected: ""},
			{operations: "sssn", expected: ""},
			{operations: "sssnnsnssn", expected: ""},
		},
	}
	tc.run(c)
}

func (s *IterSuite) TestEmptyUnnamedDir(c *C) {
	tc := testsCollection{
		tree: "()",
		tests: []test{
			{operations: "n", expected: ""},
			{operations: "nn", expected: ""},
			{operations: "nnn", expected: ""},
			{operations: "nnns", expected: ""},
			{operations: "nnnssnsnns", expected: ""},
			{operations: "s", expected: ""},
			{operations: "ss", expected: ""},
			{operations: "sss", expected: ""},
			{operations: "sssn", expected: ""},
			{operations: "sssnnsnssn", expected: ""},
		},
	}
	tc.run(c)
}

func (s *IterSuite) TestOneFile(c *C) {
	tc := testsCollection{
		tree: "(a<>)",
		tests: []test{
			{operations: "n", expected: "a"},
			{operations: "nn", expected: "a"},
			{operations: "nnn", expected: "a"},
			{operations: "nnns", expected: "a"},
			{operations: "nnnssnsnns", expected: "a"},
			{operations: "s", expected: "a"},
			{operations: "ss", expected: "a"},
			{operations: "sss", expected: "a"},
			{operations: "sssn", expected: "a"},
			{operations: "sssnnsnssn", expected: "a"},
		},
	}
	tc.run(c)
}

//     root
//      / \
//     a   b
func (s *IterSuite) TestTwoFiles(c *C) {
	tc := testsCollection{
		tree: "(a<> b<>)",
		tests: []test{
			{operations: "nnn", expected: "a b"},
			{operations: "nns", expected: "a b"},
			{operations: "nsn", expected: "a b"},
			{operations: "nss", expected: "a b"},
			{operations: "snn", expected: "a b"},
			{operations: "sns", expected: "a b"},
			{operations: "ssn", expected: "a b"},
			{operations: "sss", expected: "a b"},
		},
	}
	tc.run(c)
}

//     root
//      |
//      a
//      |
//      b
func (s *IterSuite) TestDirWithFile(c *C) {
	tc := testsCollection{
		tree: "(a(b<>))",
		tests: []test{
			{operations: "nnn", expected: "a"},
			{operations: "nns", expected: "a"},
			{operations: "nsn", expected: "a a/b"},
			{operations: "nss", expected: "a a/b"},
			{operations: "snn", expected: "a"},
			{operations: "sns", expected: "a"},
			{operations: "ssn", expected: "a a/b"},
			{operations: "sss", expected: "a a/b"},
		},
	}
	tc.run(c)
}

//     root
//      /|\
//     c a b
func (s *IterSuite) TestThreeSiblings(c *C) {
	tc := testsCollection{
		tree: "(c<> a<> b<>)",
		tests: []test{
			{operations: "nnnn", expected: "a b c"},
			{operations: "nnns", expected: "a b c"},
			{operations: "nnsn", expected: "a b c"},
			{operations: "nnss", expected: "a b c"},
			{operations: "nsnn", expected: "a b c"},
			{operations: "nsns", expected: "a b c"},
			{operations: "nssn", expected: "a b c"},
			{operations: "nsss", expected: "a b c"},
			{operations: "snnn", expected: "a b c"},
			{operations: "snns", expected: "a b c"},
			{operations: "snsn", expected: "a b c"},
			{operations: "snss", expected: "a b c"},
			{operations: "ssnn", expected: "a b c"},
			{operations: "ssns", expected: "a b c"},
			{operations: "sssn", expected: "a b c"},
			{operations: "ssss", expected: "a b c"},
		},
	}
	tc.run(c)
}

//     root
//      |
//      b
//      |
//      c
//      |
//      a
func (s *IterSuite) TestThreeVertical(c *C) {
	tc := testsCollection{
		tree: "(b(c(a())))",
		tests: []test{
			{operations: "nnnn", expected: "b"},
			{operations: "nnns", expected: "b"},
			{operations: "nnsn", expected: "b"},
			{operations: "nnss", expected: "b"},
			{operations: "nsnn", expected: "b b/c"},
			{operations: "nsns", expected: "b b/c"},
			{operations: "nssn", expected: "b b/c b/c/a"},
			{operations: "nsss", expected: "b b/c b/c/a"},
			{operations: "snnn", expected: "b"},
			{operations: "snns", expected: "b"},
			{operations: "snsn", expected: "b"},
			{operations: "snss", expected: "b"},
			{operations: "ssnn", expected: "b b/c"},
			{operations: "ssns", expected: "b b/c"},
			{operations: "sssn", expected: "b b/c b/c/a"},
			{operations: "ssss", expected: "b b/c b/c/a"},
		},
	}
	tc.run(c)
}

//     root
//      / \
//     c   a
//     |
//     b
func (s *IterSuite) TestThreeMix1(c *C) {
	tc := testsCollection{
		tree: "(c(b<>) a<>)",
		tests: []test{
			{operations: "nnnn", expected: "a c"},
			{operations: "nnns", expected: "a c"},
			{operations: "nnsn", expected: "a c c/b"},
			{operations: "nnss", expected: "a c c/b"},
			{operations: "nsnn", expected: "a c"},
			{operations: "nsns", expected: "a c"},
			{operations: "nssn", expected: "a c c/b"},
			{operations: "nsss", expected: "a c c/b"},
			{operations: "snnn", expected: "a c"},
			{operations: "snns", expected: "a c"},
			{operations: "snsn", expected: "a c c/b"},
			{operations: "snss", expected: "a c c/b"},
			{operations: "ssnn", expected: "a c"},
			{operations: "ssns", expected: "a c"},
			{operations: "sssn", expected: "a c c/b"},
			{operations: "ssss", expected: "a c c/b"},
		},
	}
	tc.run(c)
}

//     root
//      / \
//     b   a
//         |
//         c
func (s *IterSuite) TestThreeMix2(c *C) {
	tc := testsCollection{
		tree: "(b() a(c<>))",
		tests: []test{
			{operations: "nnnn", expected: "a b"},
			{operations: "nnns", expected: "a b"},
			{operations: "nnsn", expected: "a b"},
			{operations: "nnss", expected: "a b"},
			{operations: "nsnn", expected: "a a/c b"},
			{operations: "nsns", expected: "a a/c b"},
			{operations: "nssn", expected: "a a/c b"},
			{operations: "nsss", expected: "a a/c b"},
			{operations: "snnn", expected: "a b"},
			{operations: "snns", expected: "a b"},
			{operations: "snsn", expected: "a b"},
			{operations: "snss", expected: "a b"},
			{operations: "ssnn", expected: "a a/c b"},
			{operations: "ssns", expected: "a a/c b"},
			{operations: "sssn", expected: "a a/c b"},
			{operations: "ssss", expected: "a a/c b"},
		},
	}
	tc.run(c)
}

//      root
//      / | \
//     /  |  ----
//    f   d      h --------
//   /\         /  \      |
//  e   a      j   b/      g
//  |  / \     |
//  l  n  k    icm
//     |
//     o
//     |
//     p/
func (s *IterSuite) TestCrazy(c *C) {
	tc := testsCollection{
		tree: "(f(e(l<>) a(n(o(p())) k<>)) d<> h(j(i<> c<> m<>) b() g<>))",
		tests: []test{
			{operations: "nnnnn", expected: "d f h"},
			{operations: "nnnns", expected: "d f h"},
			{operations: "nnnsn", expected: "d f h h/b h/g"},
			{operations: "nnnss", expected: "d f h h/b h/g"},
			{operations: "nnsnn", expected: "d f f/a f/e h"},
			{operations: "nnsns", expected: "d f f/a f/e f/e/l"},
			{operations: "nnssn", expected: "d f f/a f/a/k f/a/n"},
			{operations: "nnsss", expected: "d f f/a f/a/k f/a/n"},
			{operations: "nsnnn", expected: "d f h"},
			{operations: "nsnns", expected: "d f h"},
			{operations: "nsnsn", expected: "d f h h/b h/g"},
			{operations: "nsnss", expected: "d f h h/b h/g"},
			{operations: "nssnn", expected: "d f f/a f/e h"},
		},
	}
	tc.run(c)
}

//      .
//      |
//      a
//      |
//      b
//     / \
//    z   h
//   / \
//  d   e
//      |
//      f
func (s *IterSuite) TestNewIterFromPath(c *C) {
	tree, err := fsnoder.New("(a(b(z(d<> e(f<>)) h<>)))")
	c.Assert(err, IsNil)

	z := find(c, tree, "z")

	iter, err := merkletrie.NewIterFromPath(z)
	c.Assert(err, IsNil)

	n, err := iter.Next()
	c.Assert(err, IsNil)
	c.Assert(n.String(), Equals, "a/b/z/d")

	n, err = iter.Next()
	c.Assert(err, IsNil)
	c.Assert(n.String(), Equals, "a/b/z/e")

	n, err = iter.Step()
	c.Assert(err, IsNil)
	c.Assert(n.String(), Equals, "a/b/z/e/f")

	_, err = iter.Step()
	c.Assert(err, Equals, io.EOF)
}

func find(c *C, tree noder.Noder, name string) noder.Path {
	iter, err := merkletrie.NewIter(tree)
	c.Assert(err, IsNil)

	for {
		current, err := iter.Step()
		if err != io.EOF {
			c.Assert(err, IsNil)
		} else {
			c.Fatalf("node %s not found in tree %s", name, tree)
		}

		if current.Name() == name {
			return current
		}
	}
}

type errorNoder struct{ noder.Noder }

func (e *errorNoder) Children() ([]noder.Noder, error) {
	return nil, fmt.Errorf("mock error")
}

func (s *IterSuite) TestNewIterNil(c *C) {
	i, err := merkletrie.NewIter(nil)
	c.Assert(err, IsNil)
	_, err = i.Next()
	c.Assert(err, Equals, io.EOF)
}

func (s *IterSuite) TestNewIterFailsOnChildrenErrors(c *C) {
	_, err := merkletrie.NewIter(&errorNoder{})
	c.Assert(err, ErrorMatches, "mock error")
}
