package index

import (
	"fmt"
	"math/rand"
	"runtime"
	"testing"
	"time"
)

func randf(min, max float64) float64 {
	return rand.Float64()*(max-min) + min
}

func randPoint() (lat float64, lon float64) {
	// intentionally go out of range.
	return randf(-100, 100), randf(-190, 190)
}

func randRect() (swLat, swLon, neLat, neLon float64) {
	swLat, swLon = randPoint()
	// intentionally go out of range even more.
	neLat = randf(swLat-10, swLat+10)
	neLon = randf(swLon-10, swLon+10)
	return
}

func wp(swLat, swLon, neLat, neLon float64) *FlexItem {
	return &FlexItem{
		MinX: swLon,
		MinY: swLat,
		MaxX: neLon,
		MaxY: neLat,
	}
}

func TestRandomInserts(t *testing.T) {
	rand.Seed(time.Now().UnixNano())
	l := 200000
	tr := New()
	start := time.Now()
	i := 0
	for ; i < l/2; i++ {
		swLat, swLon := randPoint()
		tr.Insert(wp(swLat, swLon, swLat, swLon))
	}
	inspdur := time.Now().Sub(start)

	start = time.Now()
	for ; i < l; i++ {
		swLat, swLon, neLat, neLon := randRect()
		tr.Insert(wp(swLat, swLon, neLat, neLon))
	}
	insrdur := time.Now().Sub(start)
	count := 0

	count = tr.Count()
	if count != l {
		t.Fatalf("count == %d, expect %d", count, l)
	}
	count = 0
	items := make([]Item, 0, l)
	tr.Search(-90, -180, 90, 180, 0, 0, func(item interface{}) bool {
		count++
		items = append(items, item.(Item))
		return true
	})
	if count != l {
		t.Fatalf("count == %d, expect %d", count, l)
	}
	start = time.Now()
	count1 := 0
	tr.Search(33, -115, 34, -114, 0, 0, func(item interface{}) bool {
		count1++
		return true
	})
	searchdur1 := time.Now().Sub(start)

	start = time.Now()
	count2 := 0

	tr.Search(33-180, -115-360, 34-180, -114-360, 0, 0, func(item interface{}) bool {
		count2++
		return true
	})
	searchdur2 := time.Now().Sub(start)

	start = time.Now()
	count3 := 0
	tr.Search(-10, 170, 20, 200, 0, 0, func(item interface{}) bool {
		count3++
		return true
	})
	searchdur3 := time.Now().Sub(start)

	fmt.Printf("Randomly inserted %d points in %s.\n", l/2, inspdur.String())
	fmt.Printf("Randomly inserted %d rects in %s.\n", l/2, insrdur.String())
	fmt.Printf("Searched %d items in %s.\n", count1, searchdur1.String())
	fmt.Printf("Searched %d items in %s.\n", count2, searchdur2.String())
	fmt.Printf("Searched %d items in %s.\n", count3, searchdur3.String())

	tr.Search(-10, 170, 20, 200, 0, 0, func(item interface{}) bool {
		lat1, lon1, _, lat2, lon2, _ := item.(Item).Rect()
		if lat1 == lat2 && lon1 == lon2 {
			return false
		}
		return true
	})

	tr.Search(-10, 170, 20, 200, 0, 0, func(item interface{}) bool {
		lat1, lon1, _, lat2, lon2, _ := item.(Item).Rect()
		if lat1 != lat2 || lon1 != lon2 {
			return false
		}
		return true
	})

	// Remove all of the elements
	for _, item := range items {
		tr.Remove(item)
	}

	count = tr.Count()
	if count != 0 {
		t.Fatalf("count == %d, expect %d", count, 0)
	}

	tr.RemoveAll()
	/*	if tr.getQTreeItem(nil) != nil {
			t.Fatal("getQTreeItem(nil) should return nil")
		}
	*/
	if tr.getRTreeItem(nil) != nil {
		t.Fatal("getRTreeItem(nil) should return nil")
	}
}

func TestMemory(t *testing.T) {
	rand.Seed(0)
	l := 100000
	tr := New()
	for i := 0; i < l; i++ {
		swLat, swLon, neLat, neLon := randRect()
		if rand.Int()%2 == 0 { // one in three chance that the rect is actually a point.
			neLat, neLon = swLat, swLon
		}
		tr.Insert(wp(swLat, swLon, neLat, neLon))
	}
	runtime.GC()
	var m runtime.MemStats
	runtime.ReadMemStats(&m)
	const PtrSize = 32 << uintptr(uint64(^uintptr(0))>>63)
	fmt.Printf("Memory consumption is %d bytes/object. Pointers are %d bytes.\n", int(m.HeapAlloc)/tr.Count(), PtrSize/8)
}

func TestInsertVarious(t *testing.T) {
	var count int
	tr := New()
	item := wp(33, -115, 33, -115)
	tr.Insert(item)
	count = tr.Count()
	if count != 1 {
		t.Fatalf("count = %d, expect 1", count)
	}
	tr.Remove(item)
	count = tr.Count()
	if count != 0 {
		t.Fatalf("count = %d, expect 0", count)
	}
	tr.Insert(item)
	count = tr.Count()
	if count != 1 {
		t.Fatalf("count = %d, expect 1", count)
	}
	found := false
	tr.Search(-90, -180, 90, 180, 0, 0, func(item2 interface{}) bool {
		if item2.(Item) == item {
			found = true
		}
		return true
	})
	if !found {
		t.Fatal("did not find item")
	}
}

func BenchmarkInsertRect(b *testing.B) {
	rand.Seed(time.Now().UnixNano())
	tr := New()
	for i := 0; i < b.N; i++ {
		swLat, swLon, neLat, neLon := randRect()
		tr.Insert(wp(swLat, swLon, neLat, neLon))
	}
}

func BenchmarkInsertPoint(b *testing.B) {
	rand.Seed(time.Now().UnixNano())
	tr := New()
	for i := 0; i < b.N; i++ {
		swLat, swLon, _, _ := randRect()
		tr.Insert(wp(swLat, swLon, swLat, swLon))
	}
}

func BenchmarkInsertEither(b *testing.B) {
	rand.Seed(time.Now().UnixNano())
	tr := New()
	for i := 0; i < b.N; i++ {
		swLat, swLon, neLat, neLon := randRect()
		if rand.Int()%3 == 0 { // one in three chance that the rect is actually a point.
			neLat, neLon = swLat, swLon
		}
		tr.Insert(wp(swLat, swLon, neLat, neLon))
	}
}

// func BenchmarkSearchRect(b *testing.B) {
// 	rand.Seed(time.Now().UnixNano())
// 	tr := New()
// 	for i := 0; i < 100000; i++ {
// 		swLat, swLon, neLat, neLon := randRect()
// 		tr.Insert(swLat, swLon, neLat, neLon)
// 	}
// 	b.ResetTimer()
// 	count := 0
// 	//for i := 0; i < b.N; i++ {
// 	tr.Search(0, -180, 90, 180, func(id int) bool {
// 		count++
// 		return true
// 	})
// 	//}
// 	println(count)
// }