// Copyright 2014 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 (
	"math"
	"math/rand"
	"reflect"
	"testing"

	"github.com/golang/geo/r1"
	"github.com/golang/geo/s1"
)

func TestCellUnionDuplicateCellsNotValid(t *testing.T) {
	id := cellIDFromPoint(PointFromCoords(1, 0, 0))
	cu := CellUnion([]CellID{id, id})
	if cu.IsValid() {
		t.Errorf("%v.IsValid() = true, want false", cu)
	}
}

func TestCellUnionUnsortedCellsNotValid(t *testing.T) {
	id := cellIDFromPoint(PointFromCoords(1, 0, 0)).Parent(10)
	cu := CellUnion([]CellID{id, id.Prev()})
	if cu.IsValid() {
		t.Errorf("%v.IsValid() = true, want false", cu)
	}
}

func TestCellUnionIsNormalized(t *testing.T) {
	id := cellIDFromPoint(PointFromCoords(1, 0, 0)).Parent(10)
	children := id.Children()
	cu := CellUnion([]CellID{children[0], children[1], children[2], children[3]})
	if !(cu.IsValid()) {
		t.Errorf("%v.IsValid() = false, want true", cu)
	}
	if cu.IsNormalized() {
		t.Errorf("%v.IsNormalized() = true, want false", cu)
	}
}

func TestCellUnionInvalidCellIdNotValid(t *testing.T) {
	cu := CellUnion([]CellID{CellID(0)})
	if cu.IsValid() {
		t.Error("CellUnion containing an invalid CellID should not be valid")
	}
}

func TestCellUnionAreSiblings(t *testing.T) {
	id := cellIDFromPoint(PointFromCoords(1, 0, 0)).Parent(10)
	children := id.Children()
	if siblings := areSiblings(children[0], children[1], children[2], children[3]); !siblings {
		t.Errorf("areSiblings(%v, %v, %v, %v) = false, want true", children[0], children[1], children[2], children[3])
	}

	if siblings := areSiblings(id, children[1], children[2], children[3]); siblings {
		t.Errorf("areSiblings(%v, %v, %v, %v) = true, want false", id, children[1], children[2], children[3])
	}
}

func TestCellUnionNormalization(t *testing.T) {
	cu := CellUnion{
		0x80855c0000000000, // A: a cell over Pittsburg CA
		0x80855d0000000000, // B, a child of A
		0x8085634000000000, // first child of X, disjoint from A
		0x808563c000000000, // second child of X
		0x80855dc000000000, // a child of B
		0x808562c000000000, // third child of X
		0x8085624000000000, // fourth child of X
		0x80855d0000000000, // B again
	}
	exp := CellUnion{
		0x80855c0000000000, // A
		0x8085630000000000, // X
	}
	cu.Normalize()
	if !reflect.DeepEqual(cu, exp) {
		t.Errorf("got %v, want %v", cu, exp)
	}

	// add a redundant cell
	/* TODO(dsymonds)
	cu.Add(0x808562c000000000)
	if !reflect.DeepEqual(cu, exp) {
		t.Errorf("after redundant add, got %v, want %v", cu, exp)
	}
	*/
}

func TestCellUnionBasic(t *testing.T) {
	empty := CellUnion{}
	empty.Normalize()
	if len(empty) != 0 {
		t.Errorf("empty CellUnion had %d cells, want 0", len(empty))
	}

	face1ID := CellIDFromFace(1)
	face1Cell := CellFromCellID(face1ID)
	face1Union := CellUnion{face1ID}
	face1Union.Normalize()
	if len(face1Union) != 1 {
		t.Errorf("%v had %d cells, want 1", face1Union, len(face1Union))
	}
	if face1ID != face1Union[0] {
		t.Errorf("%v[0] = %v, want %v", face1Union, face1Union[0], face1ID)
	}
	if got := face1Union.ContainsCell(face1Cell); !got {
		t.Errorf("%v.ContainsCell(%v) = %t, want %t", face1Union, face1Cell, got, true)
	}

	face2ID := CellIDFromFace(2)
	face2Cell := CellFromCellID(face2ID)
	face2Union := CellUnion{face2ID}
	face2Union.Normalize()
	if len(face2Union) != 1 {
		t.Errorf("%v had %d cells, want 1", face2Union, len(face2Union))
	}
	if face2ID != face2Union[0] {
		t.Errorf("%v[0] = %v, want %v", face2Union, face2Union[0], face2ID)
	}

	if got := face1Union.ContainsCell(face2Cell); got {
		t.Errorf("%v.ContainsCell(%v) = %t, want %t", face1Union, face2Cell, got, false)
	}

}

func TestCellUnion(t *testing.T) {
	tests := []struct {
		cells     []CellID // A test CellUnion.
		contained []CellID // List of cellIDs contained in the CellUnion.
		overlaps  []CellID // List of CellIDs that intersects the CellUnion but not contained in it.
		disjoint  []CellID // List of CellIDs that are disjoint from the CellUnion.
	}{
		{
			// Single cell around NYC, and some simple nearby probes
			cells: []CellID{0x89c25c0000000000},
			contained: []CellID{
				CellID(0x89c25c0000000000).ChildBegin(),
				CellID(0x89c25c0000000000).ChildBeginAtLevel(28),
			},
			overlaps: []CellID{
				CellID(0x89c25c0000000000).immediateParent(),
				CellIDFromFace(CellID(0x89c25c0000000000).Face()), // the whole face
			},
			disjoint: []CellID{
				CellID(0x89c25c0000000000).Next(),                       // Cell next to this one at same level
				CellID(0x89c25c0000000000).Next().ChildBeginAtLevel(28), // Cell next to this one at deep level
				0x89c2700000000000,                                      // Big(er) neighbor cell
				0x89e9000000000000,                                      // Very big next door cell.
				0x89c1000000000000,                                      // Very big cell, smaller value than probe
			},
		},

		{
			// NYC and SFO:
			cells: []CellID{
				0x89c25b0000000000, // NYC
				0x89c2590000000000, // NYC
				0x89c2f70000000000, // NYC
				0x89c2f50000000000, // NYC
				0x8085870000000000, // SFO
				0x8085810000000000, // SFO
				0x808f7d0000000000, // SFO
				0x808f7f0000000000, // SFO
			},
			contained: []CellID{
				0x808f7ef300000000, // SFO
				0x808f7e5cf0000000, // SFO
				0x808587f000000000, // SFO
				0x89c25ac000000000, // NYC
				0x89c259a400000000, // NYC
				0x89c258fa10000000, // NYC
				0x89c258f174007000, // NYC
			},
			overlaps: []CellID{
				0x808c000000000000, // Big SFO
				0x89c4000000000000, // Big NYC
			},
			disjoint: []CellID{
				0x89c15a4fcb1bb000, // outside NYC
				0x89c15a4e4aa95000, // outside NYC
				0x8094000000000000, // outside SFO (big)
				0x8096f10000000000, // outside SFO (smaller)

				0x87c0000000000000, // Midwest very big
			},
		},
		{
			// CellUnion with cells at many levels:
			cells: []CellID{
				0x8100000000000000, // starting around california
				0x8740000000000000, // adjacent cells at increasing
				0x8790000000000000, // levels, moving eastward.
				0x87f4000000000000,
				0x87f9000000000000, // going down across the midwest
				0x87ff400000000000,
				0x87ff900000000000,
				0x87fff40000000000,
				0x87fff90000000000,
				0x87ffff4000000000,
				0x87ffff9000000000,
				0x87fffff400000000,
				0x87fffff900000000,
				0x87ffffff40000000,
				0x87ffffff90000000,
				0x87fffffff4000000,
				0x87fffffff9000000,
				0x87ffffffff400000, // to a very small cell in Wisconsin
			},
			contained: []CellID{
				0x808f400000000000,
				0x80eb118b00000000,
				0x8136a7a11d000000,
				0x8136a7a11dac0000,
				0x876c7c0000000000,
				0x87f96d0000000000,
				0x87ffffffff400000,
			},
			overlaps: []CellID{
				CellID(0x8100000000000000).immediateParent(),
				CellID(0x8740000000000000).immediateParent(),
			},
			disjoint: []CellID{
				0x52aaaaaaab300000,
				0x52aaaaaaacd00000,
				0x87fffffffa100000,
				0x87ffffffed500000,
				0x87ffffffa0100000,
				0x87fffffed5540000,
				0x87fffffed6240000,
				0x52aaaacccb340000,
				0x87a0000400000000,
				0x87a000001f000000,
				0x87a0000029d00000,
				0x9500000000000000,
			},
		},
	}
	for _, test := range tests {
		union := CellUnion(test.cells)
		union.Normalize()

		// Ensure self-containment tests are correct.
		for _, id := range test.cells {
			if !union.IntersectsCellID(id) {
				t.Errorf("CellUnion %v should self-intersect %v but does not", union, id)
			}
			if !union.ContainsCellID(id) {
				t.Errorf("CellUnion %v should self-contain %v but does not", union, id)
			}
		}
		// Test for containment specified in test case.
		for _, id := range test.contained {
			if !union.IntersectsCellID(id) {
				t.Errorf("CellUnion %v should intersect %v but does not", union, id)
			}
			if !union.ContainsCellID(id) {
				t.Errorf("CellUnion %v should contain %v but does not", union, id)
			}
		}
		// Make sure the CellUnion intersect these cells but do not contain.
		for _, id := range test.overlaps {
			if !union.IntersectsCellID(id) {
				t.Errorf("CellUnion %v should intersect %v but does not", union, id)
			}
			if union.ContainsCellID(id) {
				t.Errorf("CellUnion %v should not contain %v but does", union, id)
			}
		}
		// Negative cases make sure the CellUnion neither contain nor intersect these cells
		for _, id := range test.disjoint {
			if union.IntersectsCellID(id) {
				t.Errorf("CellUnion %v should not intersect %v but does", union, id)
			}
			if union.ContainsCellID(id) {
				t.Errorf("CellUnion %v should not contain %v but does", union, id)
			}
		}
	}
}

func addCells(id CellID, selected bool, input *[]CellID, expected *[]CellID, t *testing.T) {
	// Decides whether to add "id" and/or some of its descendants to the test case.
	// If "selected" is true, then the region covered by "id" *must* be added to
	// the test case (either by adding "id" itself, or some combination of its
	// descendants, or both). If cell ids are to the test case "input", then the
	// corresponding expected result after simplification is added to "expected".

	if id == 0 {
		// Initial call: decide whether to add cell(s) from each face.
		for face := 0; face < 6; face++ {
			addCells(CellIDFromFace(face), false, input, expected, t)
		}
		return
	}

	if id.IsLeaf() {
		// The oneIn() call below ensures that the parent of a leaf cell will
		// always be selected (if we make it that far down the hierarchy).
		if selected != true {
			t.Errorf("id IsLeaf() and not selected")
		}
		*input = append(*input, id)
		return
	}

	// The following code ensures that the probability of selecting a cell at each level is
	// approximately the same, i.e. we test normalization of cells at all levels.
	// TODO(rsned): Change this probability to match
	// absl::Bernoulli(bitgen, 1.0 / (MaxLevel - id.level()))
	if !selected && oneIn(MaxLevel-id.Level()) {
		// Once a cell has been selected, the expected output is predetermined.
		// We then make sure that cells are selected that will normalize to
		// the desired output.
		*expected = append(*expected, id)
		selected = true
	}

	// With the rnd.OneIn() constants below, this function adds an average
	// of 5/6 * (kMaxLevel - level) cells to "input" where "level" is the
	// level at which the cell was first selected (level 15 on average).
	// Therefore the average number of input cells in a test case is about
	// (5/6 * 15 * 6) = 75.  The average number of output cells is about 6.

	// If a cell is selected, we add it to "input" with probability 5/6.
	added := false
	if selected && !oneIn(6) {
		*input = append(*input, id)
		added = true
	}

	numChildren := 0
	for child := id.ChildBegin(); child != id.ChildEnd(); child = child.Next() {
		// If the cell is selected, on average we recurse on 4/12 = 1/3 child.
		// This intentionally may result in a cell and some of its children
		// being included in the test case.
		//
		// If the cell is not selected, on average we recurse on one child.
		// We also make sure that we do not recurse on all 4 children, since
		// then we might include all 4 children in the input case by accident
		// (in which case the expected output would not be correct).
		recurse := false
		if selected {
			recurse = oneIn(12)
		} else {
			recurse = oneIn(4)
		}
		if recurse && numChildren < 3 {
			addCells(child, selected, input, expected, t)
			numChildren++
		}

		// If this cell was selected but the cell itself was not added, we
		// must ensure that all 4 children (or some combination of their
		// descendants) are added.
		if selected && !added {
			addCells(child, selected, input, expected, t)
		}
	}
}

// TODO(rsned): This test has been split out into several smaller test cases in C++.
// It might make sense to refactor this to stay more inline with what's current.
func TestCellUnionNormalizePseudoRandom(t *testing.T) {
	// TODO(rsned): https://github.com/golang/geo/issues/120
	// Test is still flaky without a fixed seed. Specify one for now until
	// issue 120 is fully resolved.
	random = rand.New(rand.NewSource(1))

	// Try a bunch of random test cases, and keep track of average statistics
	// for normalization (to see if they agree with the analysis above).

	inSum := 0
	outSum := 0
	iters := 2000

	for i := 0; i < iters; i++ {
		input := []CellID{}
		expected := []CellID{}
		addCells(CellID(0), false, &input, &expected, t)
		inSum += len(input)
		outSum += len(expected)
		cellunion := CellUnion(input)
		cellunion.Normalize()

		if len(expected) != len(cellunion) {
			t.Errorf("Expected size of union to be %d, but got %d.",
				len(expected), len(cellunion))
		}

		// Test CapBound().
		cb := cellunion.CapBound()
		for _, ci := range cellunion {
			if !cb.ContainsCell(CellFromCellID(ci)) {
				t.Errorf("CapBound %v of union %v should contain cellID %v", cb, cellunion, ci)
			}
		}

		for _, j := range input {
			if !cellunion.ContainsCellID(j) {
				t.Errorf("Expected containment of CellID %v", j)
			}

			if cellunion.IntersectsCellID(j) == false {
				t.Errorf("Expected intersection with %v.", j)
			}

			if !j.isFace() {
				if cellunion.IntersectsCellID(j.immediateParent()) == false {
					t.Errorf("Expected intersection with parent cell %v.", j.immediateParent())
					if j.Level() > 1 {
						if cellunion.IntersectsCellID(j.immediateParent().immediateParent()) == false {
							t.Errorf("Expected intersection with parent's parent %v.",
								j.immediateParent().immediateParent())
						}
						if cellunion.IntersectsCellID(j.Parent(0)) == false {
							t.Errorf("Expected intersection with parent %v at level 0.", j.Parent(0))
						}
					}
				}
			}

			if !j.IsLeaf() {
				if cellunion.ContainsCellID(j.ChildBegin()) == false {
					t.Errorf("Expected containment of %v.", j.ChildBegin())
				}
				if cellunion.IntersectsCellID(j.ChildBegin()) == false {
					t.Errorf("Expected intersection with %v.", j.ChildBegin())
				}
				if cellunion.ContainsCellID(j.ChildEnd().Prev()) == false {
					t.Errorf("Expected containment of %v.", j.ChildEnd().Prev())
				}
				if cellunion.IntersectsCellID(j.ChildEnd().Prev()) == false {
					t.Errorf("Expected intersection with %v.", j.ChildEnd().Prev())
				}
				if cellunion.ContainsCellID(j.ChildBeginAtLevel(MaxLevel)) == false {
					t.Errorf("Expected containment of %v.", j.ChildBeginAtLevel(MaxLevel))
				}
				if cellunion.IntersectsCellID(j.ChildBeginAtLevel(MaxLevel)) == false {
					t.Errorf("Expected intersection with %v.", j.ChildBeginAtLevel(MaxLevel))
				}
			}
		}

		for _, exp := range expected {
			if !exp.isFace() {
				if cellunion.ContainsCellID(exp.Parent(exp.Level() - 1)) {
					t.Errorf("cellunion should not contain its parent %v", exp.Parent(exp.Level()-1))
				}
				if cellunion.ContainsCellID(exp.Parent(0)) {
					t.Errorf("cellunion should not contain the top level parent %v", exp.Parent(0))
				}
			}
		}

		var test []CellID
		var dummy []CellID
		addCells(CellID(0), false, &test, &dummy, t)
		for _, j := range test {
			intersects := false
			contains := false
			for _, k := range expected {
				if k.Contains(j) {
					contains = true
				}
				if k.Intersects(j) {
					intersects = true
				}
			}
			if cellunion.ContainsCellID(j) != contains {
				t.Errorf("Expected contains with %v.", (uint64)(j))
			}
			if cellunion.IntersectsCellID(j) != intersects {
				t.Errorf("Expected intersection with %v.", (uint64)(j))
			}
		}
	}
	t.Logf("avg in %.2f, avg out %.2f\n", (float64)(inSum)/(float64)(iters), (float64)(outSum)/(float64)(iters))
}

func TestCellUnionDenormalize(t *testing.T) {
	tests := []struct {
		name string
		minL int
		lMod int
		cu   *CellUnion
		exp  *CellUnion
	}{
		{
			"not expanded, level mod == 1",
			10,
			1,
			&CellUnion{
				CellIDFromFace(2).ChildBeginAtLevel(11),
				CellIDFromFace(2).ChildBeginAtLevel(11),
				CellIDFromFace(3).ChildBeginAtLevel(14),
				CellIDFromFace(0).ChildBeginAtLevel(10),
			},
			&CellUnion{
				CellIDFromFace(2).ChildBeginAtLevel(11),
				CellIDFromFace(2).ChildBeginAtLevel(11),
				CellIDFromFace(3).ChildBeginAtLevel(14),
				CellIDFromFace(0).ChildBeginAtLevel(10),
			},
		},
		{
			"not expanded, level mod > 1",
			10,
			2,
			&CellUnion{
				CellIDFromFace(2).ChildBeginAtLevel(12),
				CellIDFromFace(2).ChildBeginAtLevel(12),
				CellIDFromFace(3).ChildBeginAtLevel(14),
				CellIDFromFace(0).ChildBeginAtLevel(10),
			},
			&CellUnion{
				CellIDFromFace(2).ChildBeginAtLevel(12),
				CellIDFromFace(2).ChildBeginAtLevel(12),
				CellIDFromFace(3).ChildBeginAtLevel(14),
				CellIDFromFace(0).ChildBeginAtLevel(10),
			},
		},
		{
			"expended, (level - min_level) is not multiple of level mod",
			10,
			3,
			&CellUnion{
				CellIDFromFace(2).ChildBeginAtLevel(12),
				CellIDFromFace(5).ChildBeginAtLevel(11),
			},
			&CellUnion{
				CellIDFromFace(2).ChildBeginAtLevel(12).Children()[0],
				CellIDFromFace(2).ChildBeginAtLevel(12).Children()[1],
				CellIDFromFace(2).ChildBeginAtLevel(12).Children()[2],
				CellIDFromFace(2).ChildBeginAtLevel(12).Children()[3],
				CellIDFromFace(5).ChildBeginAtLevel(11).Children()[0].Children()[0],
				CellIDFromFace(5).ChildBeginAtLevel(11).Children()[0].Children()[1],
				CellIDFromFace(5).ChildBeginAtLevel(11).Children()[0].Children()[2],
				CellIDFromFace(5).ChildBeginAtLevel(11).Children()[0].Children()[3],
				CellIDFromFace(5).ChildBeginAtLevel(11).Children()[1].Children()[0],
				CellIDFromFace(5).ChildBeginAtLevel(11).Children()[1].Children()[1],
				CellIDFromFace(5).ChildBeginAtLevel(11).Children()[1].Children()[2],
				CellIDFromFace(5).ChildBeginAtLevel(11).Children()[1].Children()[3],
				CellIDFromFace(5).ChildBeginAtLevel(11).Children()[2].Children()[0],
				CellIDFromFace(5).ChildBeginAtLevel(11).Children()[2].Children()[1],
				CellIDFromFace(5).ChildBeginAtLevel(11).Children()[2].Children()[2],
				CellIDFromFace(5).ChildBeginAtLevel(11).Children()[2].Children()[3],
				CellIDFromFace(5).ChildBeginAtLevel(11).Children()[3].Children()[0],
				CellIDFromFace(5).ChildBeginAtLevel(11).Children()[3].Children()[1],
				CellIDFromFace(5).ChildBeginAtLevel(11).Children()[3].Children()[2],
				CellIDFromFace(5).ChildBeginAtLevel(11).Children()[3].Children()[3],
			},
		},
		{
			"expended, level < min_level",
			10,
			3,
			&CellUnion{
				CellIDFromFace(2).ChildBeginAtLevel(9),
			},
			&CellUnion{
				CellIDFromFace(2).ChildBeginAtLevel(9).Children()[0],
				CellIDFromFace(2).ChildBeginAtLevel(9).Children()[1],
				CellIDFromFace(2).ChildBeginAtLevel(9).Children()[2],
				CellIDFromFace(2).ChildBeginAtLevel(9).Children()[3],
			},
		},
	}
	for _, test := range tests {
		if test.cu.Denormalize(test.minL, test.lMod); !reflect.DeepEqual(test.cu, test.exp) {
			t.Errorf("test: %s; got %v, want %v", test.name, test.cu, test.exp)
		}
	}
}

func TestCellUnionRectBound(t *testing.T) {
	tests := []struct {
		cu   *CellUnion
		want Rect
	}{
		{&CellUnion{}, EmptyRect()},
		{
			&CellUnion{CellIDFromFace(1)},
			Rect{
				r1.Interval{Lo: -math.Pi / 4, Hi: math.Pi / 4},
				s1.Interval{Lo: math.Pi / 4, Hi: 3 * math.Pi / 4},
			},
		},
		{
			&CellUnion{
				0x808c000000000000, // Big SFO
			},
			Rect{
				r1.Interval{
					Lo: float64(s1.Degree * 34.644220547108482),
					Hi: float64(s1.Degree * 38.011928357226651),
				},
				s1.Interval{
					Lo: float64(s1.Degree * -124.508522987668428),
					Hi: float64(s1.Degree * -121.628309835221216),
				},
			},
		},
		{
			&CellUnion{
				0x89c4000000000000, // Big NYC
			},
			Rect{
				r1.Interval{
					Lo: float64(s1.Degree * 38.794595155857657),
					Hi: float64(s1.Degree * 41.747046884651063),
				},
				s1.Interval{
					Lo: float64(s1.Degree * -76.456308667788633),
					Hi: float64(s1.Degree * -73.465162142654819),
				},
			},
		},
		{
			&CellUnion{
				0x89c4000000000000, // Big NYC
				0x808c000000000000, // Big SFO
			},
			Rect{
				r1.Interval{
					Lo: float64(s1.Degree * 34.644220547108482),
					Hi: float64(s1.Degree * 41.747046884651063),
				},
				s1.Interval{
					Lo: float64(s1.Degree * -124.508522987668428),
					Hi: float64(s1.Degree * -73.465162142654819),
				},
			},
		},
	}

	for _, test := range tests {
		if got := test.cu.RectBound(); !rectsApproxEqual(got, test.want, epsilon, epsilon) {
			t.Errorf("%v.RectBound() = %v, want %v", test.cu, got, test.want)
		}
	}
}

func TestCellUnionLeafCellsCovered(t *testing.T) {
	fiveFaces := CellUnion{CellIDFromFace(0)}
	fiveFaces.ExpandAtLevel(0)
	wholeWorld := CellUnion{CellIDFromFace(0)}
	wholeWorld.ExpandAtLevel(0)
	wholeWorld.ExpandAtLevel(0)

	tests := []struct {
		have []CellID
		want int64
	}{
		{},
		{
			have: []CellID{},
			want: 0,
		},
		{
			// One leaf cell on face 0.
			have: []CellID{
				CellIDFromFace(0).ChildBeginAtLevel(MaxLevel),
			},
			want: 1,
		},
		{
			// Face 0 itself (which includes the previous leaf cell).
			have: []CellID{
				CellIDFromFace(0).ChildBeginAtLevel(MaxLevel),
				CellIDFromFace(0),
			},
			want: 1 << 60,
		},
		{
			have: fiveFaces,
			want: 5 << 60,
		},
		{
			have: wholeWorld,
			want: 6 << 60,
		},
		{
			// Add some disjoint cells.
			have: []CellID{
				CellIDFromFace(0).ChildBeginAtLevel(MaxLevel),
				CellIDFromFace(0),
				CellIDFromFace(1).ChildBeginAtLevel(1),
				CellIDFromFace(2).ChildBeginAtLevel(2),
				CellIDFromFace(2).ChildEndAtLevel(2).Prev(),
				CellIDFromFace(3).ChildBeginAtLevel(14),
				CellIDFromFace(4).ChildBeginAtLevel(27),
				CellIDFromFace(4).ChildEndAtLevel(15).Prev(),
				CellIDFromFace(5).ChildBeginAtLevel(30),
			},
			want: 1 + (1 << 6) + (1 << 30) + (1 << 32) +
				(2 << 56) + (1 << 58) + (1 << 60),
		},
	}

	for _, test := range tests {
		cu := CellUnion(test.have)
		cu.Normalize()
		if got := cu.LeafCellsCovered(); got != test.want {
			t.Errorf("CellUnion(%v).LeafCellsCovered() = %v, want %v", cu, got, test.want)
		}
	}
}

func TestCellUnionFromRange(t *testing.T) {
	for iter := 0; iter < 2000; iter++ {
		min := randomCellIDForLevel(MaxLevel)
		max := randomCellIDForLevel(MaxLevel)
		if min > max {
			min, max = max, min
		}

		cu := CellUnionFromRange(min, max.Next())
		if len(cu) <= 0 {
			t.Errorf("len(CellUnionFromRange(%v, %v)) = %d, want > 0", min, max.Next(), len(cu))
		}
		if min != cu[0].RangeMin() {
			t.Errorf("%v.RangeMin of CellUnion should not be below the minimum value it was created from %v", cu[0], min)
		}
		if max != cu[len(cu)-1].RangeMax() {
			t.Errorf("%v.RangeMax of CellUnion should not be above the maximum value it was created from %v", cu[len(cu)-1], max)
		}
		for i := 1; i < len(cu); i++ {
			if got, want := cu[i].RangeMin(), cu[i-1].RangeMax().Next(); got != want {
				t.Errorf("%v.RangeMin() = %v, want %v", cu[i], got, want)
			}
		}
	}

	// Focus on test cases that generate an empty or full range.

	// Test an empty range before the minimum CellID.
	idBegin := CellIDFromFace(0).ChildBeginAtLevel(MaxLevel)
	cu := CellUnionFromRange(idBegin, idBegin)
	if len(cu) != 0 {
		t.Errorf("CellUnionFromRange with begin and end as the first CellID should be empty, got %d", len(cu))
	}

	// Test an empty range after the maximum CellID.
	idEnd := CellIDFromFace(5).ChildEndAtLevel(MaxLevel)
	cu = CellUnionFromRange(idEnd, idEnd)
	if len(cu) != 0 {
		t.Errorf("CellUnionFromRange with begin and end as the last CellID should be empty, got %d", len(cu))
	}

	// Test the full sphere.
	cu = CellUnionFromRange(idBegin, idEnd)
	if len(cu) != 6 {
		t.Errorf("CellUnionFromRange from first CellID to last CellID should have 6 cells, got %d", len(cu))
	}

	for i := 0; i < len(cu); i++ {
		if !cu[i].isFace() {
			t.Errorf("CellUnionFromRange for full sphere cu[%d].isFace() = %t, want %t", i, cu[i].isFace(), true)
		}
	}
}

func TestCellUnionFromUnionDiffIntersection(t *testing.T) {
	const iters = 2000
	for i := 0; i < iters; i++ {
		input := []CellID{}
		expected := []CellID{}
		addCells(CellID(0), false, &input, &expected, t)

		var x, y, xOrY, xAndY []CellID
		for _, id := range input {
			inX := oneIn(2)
			inY := oneIn(2)

			if inX {
				x = append(x, id)
			}
			if inY {
				y = append(y, id)
			}
			if inX || inY {
				xOrY = append(xOrY, id)
			}
		}

		xcells := CellUnion(x)
		xcells.Normalize()
		ycells := CellUnion(y)
		ycells.Normalize()
		xOrYExpected := CellUnion(xOrY)
		xOrYExpected.Normalize()

		xOrYCells := CellUnionFromUnion(xcells, ycells)

		if !xOrYCells.Equal(xOrYExpected) {
			t.Errorf("CellUnionFromUnion(%v, %v) = %v, want %v", xcells, ycells, xOrYCells, xOrYExpected)
		}

		// Compute the intersection of x with each cell of y,
		// check that this intersection is correct, and append the
		// results to xAndYExpected.
		for _, yid := range ycells {
			u := CellUnionFromIntersectionWithCellID(xcells, yid)
			for _, xid := range xcells {
				if xid.Contains(yid) {
					if !(len(u) == 1 && u[0] == yid) { // nolint staticcheck - DeMorgan's doesn't work here.
						t.Errorf("CellUnionFromIntersectionWithCellID(%v, %v) = %v with len: %d, want len of 1.", xcells, yid, u, len(u))
					}
				} else if yid.Contains(xid) {
					if !u.ContainsCellID(xid) {
						t.Errorf("%v.ContainsCellID(%v) = false, want true", u, xid)
					}
				}
			}
			for _, uCellID := range u {
				if !xcells.ContainsCellID(uCellID) {
					t.Errorf("%v.ContainsCellID(%v) = false, but should contain CellID that was used to create CellUnion", xcells, uCellID)
				}
				if !yid.Contains(uCellID) {
					t.Errorf("%v.Contains(%v) = false, but should contain CellID that was used to create CellUnion", yid, uCellID)
				}
			}

			xAndY = append(xAndY, u...)
		}

		xAndYExpected := CellUnion(xAndY)
		xAndYExpected.Normalize()

		xAndYCells := CellUnionFromIntersection(xcells, ycells)
		if !xAndYCells.Equal(xAndYExpected) {
			t.Errorf("CellUnionFromIntersection(%v, %v) = %v, want %v", xcells, ycells, xAndYCells, xAndYExpected)
		}

		xMinusYCells := CellUnionFromDifference(xcells, ycells)
		yMinusXCells := CellUnionFromDifference(ycells, xcells)
		if !xcells.Contains(xMinusYCells) {
			t.Errorf("%v.Contains(%v) = false, want true", xcells, xMinusYCells)
		}
		if xMinusYCells.Intersects(ycells) {
			t.Errorf("%v.Intersects(%v) = true, want false", xMinusYCells, ycells)
		}
		if !ycells.Contains(yMinusXCells) {
			t.Errorf("%v.Contains(%v) = false, want true", ycells, yMinusXCells)
		}
		if yMinusXCells.Intersects(xcells) {
			t.Errorf("%v.Intersects(%v) = true, want false", yMinusXCells, xcells)
		}
		if xMinusYCells.Intersects(yMinusXCells) {
			t.Errorf("%v.Intersects(%v) = true, want false", xMinusYCells, yMinusXCells)
		}

		diffUnion := CellUnionFromUnion(xMinusYCells, yMinusXCells)
		diffIntersectionUnion := CellUnionFromUnion(diffUnion, xAndYCells)
		if !diffIntersectionUnion.Equal(xOrYCells) {
			t.Errorf("Union(%v, %v).Union(%v) = %v, want %v", xMinusYCells, yMinusXCells, xAndYCells, diffIntersectionUnion, xOrYCells)
		}
	}
}

// cellUnionDistanceFromAxis returns the maximum geodesic distance from axis to any point of
// the given CellUnion.
func cellUnionDistanceFromAxis(cu CellUnion, axis Point) float64 {
	var maxDist float64
	for _, cid := range cu {
		cell := CellFromCellID(cid)
		for j := 0; j < 4; j++ {
			a := cell.Vertex(j)
			b := cell.Vertex((j + 1) & 3)
			var dist float64
			// The maximum distance is not always attained at a cell vertex: if at
			// least one vertex is in the opposite hemisphere from axis then the
			// maximum may be attained along an edge. We solve this by computing
			// the minimum distance from the edge to (-axis) instead. We can't
			// simply do this all the time because DistanceFromSegment() has
			// poor accuracy when the result is close to Pi.
			//
			// TODO: Improve edgeutil's DistanceFromSegment accuracy near Pi.
			if a.Angle(axis.Vector) > math.Pi/2 || b.Angle(axis.Vector) > math.Pi/2 {
				dist = math.Pi - DistanceFromSegment(Point{axis.Mul(-1)}, a, b).Radians()
			} else {
				dist = float64(a.Angle(axis.Vector))
			}
			maxDist = math.Max(maxDist, dist)
		}
	}
	return maxDist
}

func TestCellUnionExpand(t *testing.T) {
	// This test generates coverings for caps of random sizes, expands
	// the coverings by a random radius, and then make sure that the new
	// covering covers the expanded cap.  It also makes sure that the
	// new covering is not too much larger than expected.
	for i := 0; i < 5000; i++ {
		rndCap := randomCap(AvgAreaMetric.Value(MaxLevel), 4*math.Pi)

		// Expand the cap area by a random factor whose log is uniformly
		// distributed between 0 and log(1e2).
		expandedCap := CapFromCenterHeight(
			rndCap.center, math.Min(2.0, math.Pow(1e2, randomFloat64())*rndCap.Height()))

		radius := (expandedCap.Radius() - rndCap.Radius()).Radians()
		maxLevelDiff := randomUniformInt(8)

		// Generate a covering for the original cap, and measure the maximum
		// distance from the cap center to any point in the covering.
		coverer := &RegionCoverer{
			MaxLevel: MaxLevel,
			MaxCells: 1 + skewedInt(10),
			LevelMod: 1,
		}
		covering := coverer.CellUnion(rndCap)
		checkCellUnionCovering(t, rndCap, covering, true, 0)
		coveringRadius := cellUnionDistanceFromAxis(covering, rndCap.center)

		// This code duplicates the logic in Expand(min_radius, max_level_diff)
		// that figures out an appropriate cell level to use for the expansion.
		minLevel := MaxLevel
		for _, cid := range covering {
			minLevel = minInt(minLevel, cid.Level())
		}
		expandLevel := minInt(minLevel+maxLevelDiff, MinWidthMetric.MaxLevel(radius))

		// Generate a covering for the expanded cap, and measure the new maximum
		// distance from the cap center to any point in the covering.
		covering.ExpandByRadius(s1.Angle(radius), maxLevelDiff)
		checkCellUnionCovering(t, expandedCap, covering, false, 0)
		expandedCoveringRadius := cellUnionDistanceFromAxis(covering, rndCap.center)

		// If the covering includes a tiny cell along the boundary, in theory the
		// maximum angle of the covering from the cap center can increase by up to
		// twice the maximum length of a cell diagonal.
		if expandedCoveringRadius-coveringRadius >= 2*MaxDiagMetric.Value(expandLevel) {
			t.Errorf("covering.ExpandByRadius(%v, %v) distance from center = %v want < %v", radius, maxLevelDiff, expandedCoveringRadius-coveringRadius, 2*MaxDiagMetric.Value(expandLevel))
		}
	}
}

// checkCellUnionCovering checks that the given covering completely covers the given region.
// If checkTight is true, it also checks that it does not contain any cells that do not
// intersect the given region. The id is the CellID to start at for the checks. If an
// invalid value is used as the ID, then all faces are checked.
func checkCellUnionCovering(t *testing.T, r Region, covering CellUnion, checkTight bool, id CellID) {
	if !id.IsValid() {
		for face := 0; face < 6; face++ {
			checkCellUnionCovering(t, r, covering, checkTight, CellIDFromFace(face))
		}
		return
	}

	if !r.IntersectsCell(CellFromCellID(id)) {
		// If region does not intersect the id, then neither should the covering.
		if checkTight {
			if covering.IntersectsCellID(id) {
				t.Errorf("%v.IntersectsCellID(%v) = true, want false", covering, id)
			}
		}
		return
	}
	if !covering.ContainsCellID(id) {
		// The region may intersect id, but we can't assert that the covering
		// intersects id because we may discover that the region does not actually
		// intersect upon further subdivision.  (IntersectsCell is not exact.)
		if r.ContainsCell(CellFromCellID(id)) {
			t.Errorf("%v.ContainsCell(%v) = true, want false", r, id)
			return
		}
		if id.IsLeaf() {
			t.Errorf("%v.IsLeaf() = true, want false", id)
			return
		}
		for child := id.ChildBegin(); child != id.ChildEnd(); child = child.Next() {
			checkCellUnionCovering(t, r, covering, checkTight, child)
		}
	}
}

func TestCellUnionEmpty(t *testing.T) {
	var empty CellUnion

	// Normalize()
	empty.Normalize()
	if len(empty) != 0 {
		t.Errorf("len(empty.Normalize()) = %d, want 0", len(empty))
	}

	// Denormalize(...)
	empty.Denormalize(0, 2)
	if len(empty) != 0 {
		t.Errorf("len(empty.Denormalize(0, 2)) = %d, want 0", len(empty))
	}

	face1ID := CellIDFromFace(1)
	// Contains(...)
	if empty.ContainsCellID(face1ID) {
		t.Errorf("empty.ContainsCellID(%v) = true, want false", face1ID)
	}
	if !empty.Contains(empty) {
		t.Errorf("empty.Contains(%v) = false, want true", empty)
	}

	// Intersects(...)
	if empty.IntersectsCellID(face1ID) {
		t.Errorf("empty.IntersectsCellID(%v) = true, want false", face1ID)
	}
	if empty.Intersects(empty) {
		t.Errorf("empty.Intersects(%v) = true, want false", empty)
	}

	// Union(...)
	cellUnion := CellUnionFromUnion(empty, empty)
	if len(cellUnion) != 0 {
		t.Errorf("CellUnionFromUnion(empty, empty) has %v cells, want 0", len(cellUnion))
	}

	// Intersection(...)
	intersection := CellUnionFromIntersectionWithCellID(empty, face1ID)
	if len(intersection) != 0 {
		t.Errorf("CellUnionFromIntersectionWithCellID(%v, %v) = %v cells, want 0", empty, face1ID, len(intersection))
	}

	intersection = CellUnionFromIntersection(empty, empty)
	if len(intersection) != 0 {
		t.Errorf("CellUnionFromIntersection(%v, %v) = %v cells, want 0", empty, empty, len(intersection))
	}

	// Difference(...)
	difference := CellUnionFromDifference(empty, empty)
	if len(difference) != 0 {
		t.Errorf("CellUnionFromDifference(%v, %v) = %v cells, want 0", empty, empty, len(difference))
	}

	// Expand(...)
	empty.ExpandByRadius(s1.Angle(1), 20)
	if len(empty) != 0 {
		t.Errorf("empty.ExpandByRadius(1, 20) = %v cells, want 0", len(empty))
	}

	empty.ExpandAtLevel(10)
	if len(empty) != 0 {
		t.Errorf("empty.ExpandAtLevel(10) = %v cells, want 0", len(empty))
	}
}

func BenchmarkCellUnionFromRange(b *testing.B) {
	x := CellIDFromFace(0).ChildBeginAtLevel(MaxLevel)
	y := CellIDFromFace(5).ChildEndAtLevel(MaxLevel)
	for i := 0; i < b.N; i++ {
		CellUnionFromRange(x, y)
	}
}
