package storer

import (
	"fmt"
	"testing"

	"github.com/go-git/go-git/v5/plumbing"

	. "gopkg.in/check.v1"
)

func Test(t *testing.T) { TestingT(t) }

type ObjectSuite struct {
	Objects []plumbing.EncodedObject
	Hash    []plumbing.Hash
}

var _ = Suite(&ObjectSuite{})

func (s *ObjectSuite) SetUpSuite(c *C) {
	s.Objects = []plumbing.EncodedObject{
		s.buildObject([]byte("foo")),
		s.buildObject([]byte("bar")),
	}

	for _, o := range s.Objects {
		s.Hash = append(s.Hash, o.Hash())
	}
}

func (s *ObjectSuite) TestMultiObjectIterNext(c *C) {
	expected := []plumbing.EncodedObject{
		&plumbing.MemoryObject{},
		&plumbing.MemoryObject{},
		&plumbing.MemoryObject{},
		&plumbing.MemoryObject{},
		&plumbing.MemoryObject{},
		&plumbing.MemoryObject{},
	}

	iter := NewMultiEncodedObjectIter([]EncodedObjectIter{
		NewEncodedObjectSliceIter(expected[0:2]),
		NewEncodedObjectSliceIter(expected[2:4]),
		NewEncodedObjectSliceIter(expected[4:5]),
	})

	var i int
	iter.ForEach(func(o plumbing.EncodedObject) error {
		c.Assert(o, Equals, expected[i])
		i++
		return nil
	})

	iter.Close()
}

func (s *ObjectSuite) buildObject(content []byte) plumbing.EncodedObject {
	o := &plumbing.MemoryObject{}
	o.Write(content)

	return o
}

func (s *ObjectSuite) TestObjectLookupIter(c *C) {
	var count int

	storage := &MockObjectStorage{s.Objects}
	i := NewEncodedObjectLookupIter(storage, plumbing.CommitObject, s.Hash)
	err := i.ForEach(func(o plumbing.EncodedObject) error {
		c.Assert(o, NotNil)
		c.Assert(o.Hash().String(), Equals, s.Hash[count].String())
		count++
		return nil
	})

	c.Assert(err, IsNil)
	i.Close()
}

func (s *ObjectSuite) TestObjectSliceIter(c *C) {
	var count int

	i := NewEncodedObjectSliceIter(s.Objects)
	err := i.ForEach(func(o plumbing.EncodedObject) error {
		c.Assert(o, NotNil)
		c.Assert(o.Hash().String(), Equals, s.Hash[count].String())
		count++
		return nil
	})

	c.Assert(count, Equals, 2)
	c.Assert(err, IsNil)
	c.Assert(i.series, HasLen, 0)
}

func (s *ObjectSuite) TestObjectSliceIterStop(c *C) {
	i := NewEncodedObjectSliceIter(s.Objects)

	var count = 0
	err := i.ForEach(func(o plumbing.EncodedObject) error {
		c.Assert(o, NotNil)
		c.Assert(o.Hash().String(), Equals, s.Hash[count].String())
		count++
		return ErrStop
	})

	c.Assert(count, Equals, 1)
	c.Assert(err, IsNil)
}

func (s *ObjectSuite) TestObjectSliceIterError(c *C) {
	i := NewEncodedObjectSliceIter([]plumbing.EncodedObject{
		s.buildObject([]byte("foo")),
	})

	err := i.ForEach(func(plumbing.EncodedObject) error {
		return fmt.Errorf("a random error")
	})

	c.Assert(err, NotNil)
}

type MockObjectStorage struct {
	db []plumbing.EncodedObject
}

func (o *MockObjectStorage) NewEncodedObject() plumbing.EncodedObject {
	return nil
}

func (o *MockObjectStorage) SetEncodedObject(obj plumbing.EncodedObject) (plumbing.Hash, error) {
	return plumbing.ZeroHash, nil
}

func (o *MockObjectStorage) HasEncodedObject(h plumbing.Hash) error {
	for _, o := range o.db {
		if o.Hash() == h {
			return nil
		}
	}
	return plumbing.ErrObjectNotFound
}

func (o *MockObjectStorage) EncodedObjectSize(h plumbing.Hash) (
	size int64, err error) {
	for _, o := range o.db {
		if o.Hash() == h {
			return o.Size(), nil
		}
	}
	return 0, plumbing.ErrObjectNotFound
}

func (o *MockObjectStorage) EncodedObject(t plumbing.ObjectType, h plumbing.Hash) (plumbing.EncodedObject, error) {
	for _, o := range o.db {
		if o.Hash() == h {
			return o, nil
		}
	}
	return nil, plumbing.ErrObjectNotFound
}

func (o *MockObjectStorage) IterEncodedObjects(t plumbing.ObjectType) (EncodedObjectIter, error) {
	return nil, nil
}

func (o *MockObjectStorage) Begin() Transaction {
	return nil
}

func (o *MockObjectStorage) AddAlternate(remote string) error {
	return nil
}
