// Copyright 2020 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package s2

import (
	"reflect"
	"sort"
	"testing"
)

func cellIndexQuadraticValidate(t *testing.T, desc string, index *CellIndex, contents []cellIndexNode) {
	// Verifies that the index computes the correct set of (cell_id, label) pairs
	// for every possible leaf cell.  The running time of this function is
	// quadratic in the size of the index.
	index.Build()
	verifyCellIndexCellIterator(t, desc, index)
	verifyCellIndexRangeIterators(t, desc, index)
	verifyCellIndexContents(t, desc, index)
}

// less reports whether this node is less than the other.
func (c cellIndexNode) less(other cellIndexNode) bool {
	if c.cellID != other.cellID {
		return c.cellID < other.cellID
	}
	if c.label != other.label {
		return c.label < other.label
	}
	return c.parent < other.parent
}

func cellIndexNodesEqual(a, b []cellIndexNode) bool {
	sort.Slice(a, func(i, j int) bool {
		return a[i].less(a[j])
	})
	sort.Slice(b, func(i, j int) bool {
		return b[i].less(b[j])
	})
	return reflect.DeepEqual(a, b)

}

// copyCellIndexNodes creates a copy of the nodes so that sorting and other tests
// don't alter the instance in a given CellIndex.
func copyCellIndexNodes(in []cellIndexNode) []cellIndexNode {
	out := make([]cellIndexNode, len(in))
	copy(out, in)
	return out
}

func verifyCellIndexCellIterator(t *testing.T, desc string, index *CellIndex) {
	// TODO(roberts): Once the plain iterator is implemented, add this check.
	/*
		var actual []cellIndexNode
		iter := NewCellIndexIterator(index)
		for iter.Begin(); !iter.Done(); iter.Next() {
			actual = append(actual, cellIndexNode{iter.StartID(), iter.Label())
		}

		want := copyCellIndexNodes(index.cellTree)
		if !cellIndexNodesEqual(actual, want) {
			t.Errorf("%s: cellIndexNodes not equal but should be.  %v != %v", desc, actual, want)
		}
	*/
}

func verifyCellIndexRangeIterators(t *testing.T, desc string, index *CellIndex) {
	// tests Finish(), which is not otherwise tested below.
	it := NewCellIndexRangeIterator(index)
	it.Begin()
	it.Finish()
	if !it.Done() {
		t.Errorf("%s: positioning iterator to finished should be done, but was not", desc)
	}

	// And also for non-empty ranges.
	nonEmpty := NewCellIndexNonEmptyRangeIterator(index)
	nonEmpty.Begin()
	nonEmpty.Finish()
	if !nonEmpty.Done() {
		t.Errorf("%s: positioning non-empty iterator to finished should be done, but was not", desc)
	}

	// Iterate through all the ranges in the index.  We simultaneously iterate
	// through the non-empty ranges and check that the correct ranges are found.
	prevStart := CellID(0)
	nonEmptyPrevStart := CellID(0)

	it.Begin()
	nonEmpty.Begin()
	for ; !it.Done(); it.Next() {
		// Check that seeking in the current range takes us to this range.
		it2 := NewCellIndexRangeIterator(index)
		start := it.StartID()
		it2.Seek(it.StartID())
		if start != it2.StartID() {
			t.Errorf("%s: id: %v. id2 start: %v\nit: %+v\nit2: %+v", desc, start, it2.StartID(), it, it2)
		}
		it2.Seek(it.LimitID().Prev())
		if start != it2.StartID() {
			t.Errorf("%s: it2.Seek(%v) = %v, want %v", desc, it.LimitID().Prev(), it2.StartID(), start)
		}

		// And also for non-empty ranges.
		nonEmpty2 := NewCellIndexNonEmptyRangeIterator(index)
		nonEmptyStart := nonEmpty.StartID()
		nonEmpty2.Seek(it.StartID())
		if nonEmptyStart != nonEmpty2.StartID() {
			t.Errorf("%s: nonEmpty2.StartID() = %v, want %v", desc, nonEmpty2.StartID(), nonEmptyStart)
		}
		nonEmpty2.Seek(it.LimitID().Prev())
		if nonEmptyStart != nonEmpty2.StartID() {
			t.Errorf("%s: nonEmpty2.StartID() = %v, want %v", desc, nonEmpty2.StartID(), nonEmptyStart)
		}

		// Test Prev() and Next().
		if it2.Prev() {
			if prevStart != it2.StartID() {
				t.Errorf("%s: it2.StartID() = %v, want %v", desc, it2.StartID(), prevStart)
			}
			it2.Next()
			if start != it2.StartID() {
				t.Errorf("%s: it2.StartID() = %v, want %v", desc, it2.StartID(), start)
			}
		} else {
			if start != it2.StartID() {
				t.Errorf("%s: it2.StartID() = %v, want %v", desc, it2.StartID(), start)
			}
			if prevStart != 0 {
				t.Errorf("%s: prevStart = %v, want %v", desc, prevStart, 0)
			}
		}

		// And also for non-empty ranges.
		if nonEmpty2.Prev() {
			if nonEmptyPrevStart != nonEmpty2.StartID() {
				t.Errorf("%s: nonEmpty2.StartID() = %v, want %v", desc, nonEmpty2.StartID(), nonEmptyPrevStart)
			}
			nonEmpty2.Next()
			if nonEmptyStart != nonEmpty2.StartID() {
				t.Errorf("%s: nonEmpty2.StartID() = %v, want %v", desc, nonEmpty2.StartID(), nonEmptyStart)
			}
		} else {
			if nonEmptyStart != nonEmpty2.StartID() {
				t.Errorf("%s: nonEmpty2.StartID() = %v, want %v", desc, nonEmpty2.StartID(), nonEmptyStart)
			}
			if nonEmptyPrevStart != 0 {
				t.Errorf("%s: nonEmptyPrevStart = %v, want 0", desc, nonEmptyPrevStart)
			}
		}

		// Keep the non-empty iterator synchronized with the regular one.
		if !it.IsEmpty() {
			if it.StartID() != nonEmpty.StartID() {
				t.Errorf("%s: it.StartID = %v, want %v", desc, it.StartID(), nonEmpty.StartID())
			}
			if it.LimitID() != nonEmpty.LimitID() {
				t.Errorf("%s: it.LimitID = %v, want %v", desc, it.LimitID(), nonEmpty.LimitID())
			}
			if nonEmpty.Done() {
				t.Errorf("%s: nonEmpty iterator should not be done but was", desc)
			}
			nonEmptyPrevStart = nonEmptyStart
			nonEmpty.Next()
		}
		prevStart = start
	}

	// Verify that the NonEmptyRangeIterator is also finished.
	if !nonEmpty.Done() {
		t.Errorf("%s: non empty iterator should have also finished", desc)
	}
}

// verifies that RangeIterator and ContentsIterator can be used to determine
// the exact set of (s2cell_id, label) pairs that contain any leaf cell.
func verifyCellIndexContents(t *testing.T, desc string, index *CellIndex) {
	// "minCellID" is the first CellID that has not been validated yet.
	minCellID := CellIDFromFace(0).ChildBeginAtLevel(MaxLevel)
	r := NewCellIndexRangeIterator(index)
	for r.Begin(); !r.Done(); r.Next() {
		if minCellID != r.StartID() {
			t.Errorf("%s: minCellID should match the previous ending cellID. got %v, want %v", desc, r.StartID(), minCellID)
		}
		if minCellID >= r.LimitID() {
			t.Errorf("%s: minCellID should be >= the end of the current range. got %v, want %v", desc, r.LimitID(), minCellID)
		}
		if !r.LimitID().IsLeaf() {
			t.Errorf("%s: ending range cell ID should not be a leaf, but was", desc)
		}

		minCellID = r.LimitID()

		// Build a list of expected (CellID, label) for this range.
		var expected []cellIndexNode
		for _, x := range index.cellTree {
			// The cell contains the entire range.
			if x.cellID.RangeMin() <= r.StartID() &&
				x.cellID.RangeMax().Next() >= r.LimitID() {
				expected = append(expected, x)
			} else {
				// Verify that the cell does not intersect the range.
				if x.cellID.RangeMin() <= r.LimitID().Prev() &&
					x.cellID.RangeMax() >= r.StartID() {
					t.Errorf("%s: CellID does not intersect the current range: %v <= %v && %v >= %v", desc, x.cellID.RangeMin(), r.LimitID().Prev(), x.cellID.RangeMax(), r.StartID())
				}
			}
		}
		var actual []cellIndexNode
		cIter := NewCellIndexContentsIterator(index)
		for cIter.StartUnion(r); !cIter.Done(); cIter.Next() {
			actual = append(actual, cIter.node)
		}

		if !cellIndexNodesEqual(expected, actual) {
			t.Errorf("%s: comparing contents iterator contents to this range: got %+v, want %+v", desc, actual, expected)
		}
	}

	if CellIDFromFace(5).ChildEndAtLevel(MaxLevel) != minCellID {
		t.Errorf("%s: the final cell should be the sentinel value, got %v", desc, minCellID)
	}
}

func TestCellIndex(t *testing.T) {
	type cellIndexTestInput struct {
		cellID string
		label  int32
	}
	tests := []struct {
		label string
		have  []cellIndexTestInput
	}{
		{
			label: "Empty",
		},
		{
			label: "One face cell",
			have: []cellIndexTestInput{
				{"0/", 0},
			},
		},

		{
			label: "One Leaf Cell",
			have: []cellIndexTestInput{
				{"1/012301230123012301230123012301", 12},
			},
		},
		{
			label: "Duplicate Values",
			have: []cellIndexTestInput{
				{"0/", 0},
				{"0/", 0},
				{"0/", 1},
				{"0/", 17},
			},
		},
		{
			label: "Disjoint Cells",
			have: []cellIndexTestInput{
				{"0/", 0},
				{"3/", 0},
			},
		},
		{
			// Tests nested cells, including cases where several cells have the same
			// RangeMin or RangeMax and with randomly ordered labels.
			label: "Nested Cells",
			have: []cellIndexTestInput{
				{"1/", 3},
				{"1/0", 15},
				{"1/000", 9},
				{"1/00000", 11},
				{"1/012", 6},
				{"1/01212", 5},
				{"1/312", 17},
				{"1/31200", 4},
				{"1/3120000", 10},
				{"1/333", 20},
				{"1/333333", 18},
				{"5/", 3},
				{"5/3", 31},
				{"5/3333", 27},
			},
		},
		{
			// Checks that the contents iterator stops reporting values
			// once it reaches a node of the cell tree that was visited
			// by the previous call to Begin().
			label: "Contents Iterator Suppresses Duplicates",
			have: []cellIndexTestInput{
				{"2/1", 1},
				{"2/1", 2},
				{"2/10", 3},
				{"2/100", 4},
				{"2/102", 5},
				{"2/1023", 6},
				{"2/31", 7},
				{"2/313", 8},
				{"2/3132", 9},
				{"3/1", 10},
				{"3/12", 11},
				{"3/13", 12},
			},
		},
	}

	for _, test := range tests {
		index := &CellIndex{}
		for _, v := range test.have {
			index.Add(CellIDFromString(v.cellID), v.label)
		}
		cellIndexQuadraticValidate(t, test.label, index, nil)
	}
}

func TestCellIndexRandomCellUnions(t *testing.T) {
	// Construct cell unions from random CellIDs at random levels. Note that
	// because the cell level is chosen uniformly, there is a very high
	// likelihood that the cell unions will overlap.
	index := &CellIndex{}
	for i := int32(0); i < 100; i++ {
		index.AddCellUnion(randomCellUnion(10), i)
	}
	cellIndexQuadraticValidate(t, "Random Cell Unions", index, nil)
}

// TODO(roberts): Differences from C++
//
// Add remainder of TestCellIndexContentsIteratorSuppressesDuplicates
//
// additional Iterator related parts
// Intersections related
