package git

import (
	"os"
	"runtime"
	"sort"
	"testing"
	"time"
)

func TestRefModification(t *testing.T) {
	repo := createTestRepo(t)
	defer os.RemoveAll(repo.Workdir())

	commitId, treeId := seedTestRepo(t, repo)

	loc, err := time.LoadLocation("Europe/Berlin")
	checkFatal(t, err)
	sig := &Signature{
		Name:  "Rand Om Hacker",
		Email: "random@hacker.com",
		When:  time.Date(2013, 03, 06, 14, 30, 0, 0, loc),
	}
	_, err = repo.CreateReference("refs/tags/tree", treeId, true, sig, "testTreeTag")
	checkFatal(t, err)

	tag, err := repo.LookupReference("refs/tags/tree")
	checkFatal(t, err)
	checkRefType(t, tag, ReferenceOid)

	ref, err := repo.LookupReference("HEAD")
	checkFatal(t, err)
	checkRefType(t, ref, ReferenceSymbolic)

	if target := ref.Target(); target != nil {
		t.Fatalf("Expected nil *Oid, got %v", target)
	}

	ref, err = ref.Resolve()
	checkFatal(t, err)
	checkRefType(t, ref, ReferenceOid)

	if target := ref.Target(); target == nil {
		t.Fatalf("Expected valid target got nil")
	}

	if target := ref.SymbolicTarget(); target != "" {
		t.Fatalf("Expected empty string, got %v", target)
	}

	if commitId.String() != ref.Target().String() {
		t.Fatalf("Wrong ref target")
	}

	_, err = tag.Rename("refs/tags/renamed", false, nil, "")
	checkFatal(t, err)
	tag, err = repo.LookupReference("refs/tags/renamed")
	checkFatal(t, err)
	checkRefType(t, ref, ReferenceOid)

}

func TestReferenceIterator(t *testing.T) {
	repo := createTestRepo(t)
	defer os.RemoveAll(repo.Workdir())

	loc, err := time.LoadLocation("Europe/Berlin")
	checkFatal(t, err)
	sig := &Signature{
		Name:  "Rand Om Hacker",
		Email: "random@hacker.com",
		When:  time.Date(2013, 03, 06, 14, 30, 0, 0, loc),
	}

	idx, err := repo.Index()
	checkFatal(t, err)
	err = idx.AddByPath("README")
	checkFatal(t, err)
	treeId, err := idx.WriteTree()
	checkFatal(t, err)

	message := "This is a commit\n"
	tree, err := repo.LookupTree(treeId)
	checkFatal(t, err)
	commitId, err := repo.CreateCommit("HEAD", sig, sig, message, tree)
	checkFatal(t, err)

	_, err = repo.CreateReference("refs/heads/one", commitId, true, sig, "headOne")
	checkFatal(t, err)

	_, err = repo.CreateReference("refs/heads/two", commitId, true, sig, "headTwo")
	checkFatal(t, err)

	_, err = repo.CreateReference("refs/heads/three", commitId, true, sig, "headThree")
	checkFatal(t, err)

	iter, err := repo.NewReferenceIterator()
	checkFatal(t, err)

	var list []string
	expected := []string{
		"refs/heads/master",
		"refs/heads/one",
		"refs/heads/three",
		"refs/heads/two",
	}

	// test some manual iteration
	nameIter := iter.Names()
	name, err := nameIter.Next()
	for err == nil {
		list = append(list, name)
		name, err = nameIter.Next()
	}
	if !IsErrorCode(err, ErrIterOver) {
		t.Fatal("Iteration not over")
	}

	sort.Strings(list)
	compareStringList(t, expected, list)

	// test the iterator for full refs, rather than just names
	iter, err = repo.NewReferenceIterator()
	checkFatal(t, err)
	count := 0
	_, err = iter.Next()
	for err == nil {
		count++
		_, err = iter.Next()
	}
	if !IsErrorCode(err, ErrIterOver) {
		t.Fatal("Iteration not over")
	}

	if count != 4 {
		t.Fatalf("Wrong number of references returned %v", count)
	}

}

func TestReferenceOwner(t *testing.T) {
	repo := createTestRepo(t)
	defer os.RemoveAll(repo.Workdir())
	commitId, _ := seedTestRepo(t, repo)

	ref, err := repo.CreateReference("refs/heads/foo", commitId, true, nil, "")
	checkFatal(t, err)

	owner := ref.Owner()
	if owner == nil {
		t.Fatal("nil owner")
	}

	if owner.ptr != repo.ptr {
		t.Fatalf("bad ptr, expected %v have %v\n", repo.ptr, owner.ptr)
	}
}

func TestUtil(t *testing.T) {
	repo := createTestRepo(t)
	defer os.RemoveAll(repo.Workdir())

	commitId, _ := seedTestRepo(t, repo)

	ref, err := repo.CreateReference("refs/heads/foo", commitId, true, nil, "")
	checkFatal(t, err)

	ref2, err := repo.DwimReference("foo")
	checkFatal(t, err)

	if ref.Cmp(ref2) != 0 {
		t.Fatalf("foo didn't dwim to the right thing")
	}

	if ref.Shorthand() != "foo" {
		t.Fatalf("refs/heads/foo has no foo shorthand")
	}

	hasLog, err := repo.HasLog("refs/heads/foo")
	checkFatal(t, err)
	if !hasLog {
		t.Fatalf("branches have logs by default")
	}
}

func compareStringList(t *testing.T, expected, actual []string) {
	for i, v := range expected {
		if actual[i] != v {
			t.Fatalf("Bad list")
		}
	}
}

func checkRefType(t *testing.T, ref *Reference, kind ReferenceType) {
	if ref.Type() == kind {
		return
	}

	// The failure happens at wherever we were called, not here
	_, file, line, ok := runtime.Caller(1)
	if !ok {
		t.Fatal()
	}

	t.Fatalf("Wrong ref type at %v:%v; have %v, expected %v", file, line, ref.Type(), kind)
}