// 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 (
	"io"
	"math"

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

// CellBoundary represents canonical identifiers for the boundaries of the cell.
// It's promised that both Vertex() and BoundUV().Vertices() return vertices in this
// order.
//
// That is, for a given boundary k, the edge defining the boundary is:
//
//	{Vertex(k), Vertex(k+1)}
//
// The boundaries are defined in UV coordinates. The orientation may be
// rotated relative to other face cells, but are consistent within a face
// (i.e. a cell's left edge is its left-ward neighbor's right edge).
type CellBoundary int

const (
	CellBoundaryBottomEdge CellBoundary = iota
	CellBoundaryRightEdge
	CellBoundaryTopEdge
	CellBoundaryLeftEdge
)

// Cell is an S2 region object that represents a cell. Unlike CellIDs,
// it supports efficient containment and intersection tests. However, it is
// also a more expensive representation.
type Cell struct {
	face        int8
	level       int8
	orientation int8
	id          CellID
	uv          r2.Rect
}

// CellFromCellID constructs a Cell corresponding to the given CellID.
func CellFromCellID(id CellID) Cell {
	c := Cell{}
	c.id = id
	f, i, j, o := c.id.faceIJOrientation()
	c.face = int8(f)
	c.level = int8(c.id.Level())
	c.orientation = int8(o)
	c.uv = ijLevelToBoundUV(i, j, int(c.level))
	return c
}

// CellFromPoint constructs a cell for the given Point.
func CellFromPoint(p Point) Cell {
	return CellFromCellID(cellIDFromPoint(p))
}

// CellFromLatLng constructs a cell for the given LatLng.
func CellFromLatLng(ll LatLng) Cell {
	return CellFromCellID(CellIDFromLatLng(ll))
}

// Face returns the face this cell is on.
func (c Cell) Face() int {
	return int(c.face)
}

// oppositeFace returns the face opposite the given face.
func oppositeFace(face int) int {
	return (face + 3) % 6
}

// Level returns the level of this cell.
func (c Cell) Level() int {
	return int(c.level)
}

// ID returns the CellID this cell represents.
func (c Cell) ID() CellID {
	return c.id
}

// IsLeaf returns whether this Cell is a leaf or not.
func (c Cell) IsLeaf() bool {
	return c.level == MaxLevel
}

// SizeIJ returns the edge length of this cell in (i,j)-space.
func (c Cell) SizeIJ() int {
	return sizeIJ(int(c.level))
}

// SizeST returns the edge length of this cell in (s,t)-space.
func (c Cell) SizeST() float64 {
	return c.id.sizeST(int(c.level))
}

// Vertex returns the normalized k-th vertex of the cell (k = 0,1,2,3) in CCW order
// (lower left, lower right, upper right, upper left in the UV plane).
func (c Cell) Vertex(k int) Point {
	return Point{c.VertexRaw(k).Normalize()}
}

// VertexRaw returns the unnormalized k-th vertex of the cell (k = 0,1,2,3) in CCW order
// (lower left, lower right, upper right, upper left in the UV plane).
func (c Cell) VertexRaw(k int) Point {
	return Point{faceUVToXYZ(int(c.face), c.uv.Vertices()[k].X, c.uv.Vertices()[k].Y)}
}

// Edge returns the normalized inward-facing normal of the great circle passing through
// the CCW ordered edge from vertex k to vertex k+1 (mod 4) (for k = 0,1,2,3)
func (c Cell) Edge(k int) Point {
	return Point{c.EdgeRaw(k).Normalize()}
}

// EdgeRaw returns the inward-facing normal of the great circle passing through
// the CCW ordered edge from vertex k to vertex k+1 (mod 4) (for k = 0,1,2,3).
//
// The normals returned by EdgeRaw are not necessarily unit length, but their
// length is bounded by Sqrt(2) since the worst case is two components of magnitude 1.
//
// The vertices returned by Vertex are not guaranteed to actually be on the
// boundary of the cell exactly. Instead, they're the nearest representable
// point to the corner.
//
// Cell edge normals returned by EdgeRaw, however, are computed exactly and
// can be used with exact predicates to determine spatial relationships to the
// cell exactly.
func (c Cell) EdgeRaw(k int) Point {
	switch k {
	case 0:
		return Point{vNorm(int(c.face), c.uv.Y.Lo)} // Bottom
	case 1:
		return Point{uNorm(int(c.face), c.uv.X.Hi)} // Right
	case 2:
		return Point{vNorm(int(c.face), c.uv.Y.Hi).Mul(-1.0)} // Top
	default:
		return Point{uNorm(int(c.face), c.uv.X.Lo).Mul(-1.0)} // Left
	}
}

// BoundUV returns the bounds of this cell in (u,v)-space.
func (c Cell) BoundUV() r2.Rect {
	return c.uv
}

// UVCoordOfEdge returns either U or V for the given edge, whichever is constant along it.
//
// E.g. boundaries 0 and 2 are constant in the V axis so we return those
// coordinates, but boundaries 1 and 3 are constant in the U axis, so we
// return those coordinates.
//
// For convenience, the argument is reduced modulo 4 to the range [0..3].
func (c Cell) UVCoordOfEdge(k int) float64 {
	k %= 4
	if k%2 == 0 {
		return c.BoundUV().Vertices()[k].Y
	}
	return c.BoundUV().Vertices()[k].X
}

// IJCoordOfEdge returns either I or J for the given edge, whichever is constant along it.
//
// E.g. boundaries 0 and 2 are constant in the J axis so we return those
// coordinates, but boundaries 1 and 3 are constant in the I axis, so we
// return those coordinates.
//
// The returned value is not clamped to kLimitIJ-1 as in StToIJ, so
// that cell edges at the maximum extent of a face are properly returned as
// kLimitIJ.
//
// For convenience, the argument is reduced modulo 4 to the range [0..3].
func (c Cell) IJCoordOfEdge(k int) int {
	// We can just convert UV->ST->IJ for this because the IJ coordinates only
	// have 30 bits of resolution in each axis.  The error in the conversion
	// will be a couple of epsilon which is <<< 2^-30, so if we use a proper
	// round-to-nearest operation, we'll always round to the correct IJ value.
	//
	// Intel CPUs that support SSE4.1 have the ROUNDSD instruction, and ARM CPUs
	// with VFP have the VCVT instruction, both of which can implement correct
	// rounding efficiently regardless of the current FPU rounding mode.
	return int(math.Round(MaxSize * uvToST(c.UVCoordOfEdge(k))))
}

// Center returns the direction vector corresponding to the center in
// (s,t)-space of the given cell. This is the point at which the cell is
// divided into four subcells; it is not necessarily the centroid of the
// cell in (u,v)-space or (x,y,z)-space
func (c Cell) Center() Point {
	return Point{c.id.rawPoint().Normalize()}
}

// Children returns the four direct children of this cell in traversal order
// and returns true. If this is a leaf cell, or the children could not be created,
// false is returned.
// The C++ method is called Subdivide.
func (c Cell) Children() ([4]Cell, bool) {
	var children [4]Cell

	if c.id.IsLeaf() {
		return children, false
	}

	// Compute the cell midpoint in uv-space.
	uvMid := c.id.centerUV()

	// Create four children with the appropriate bounds.
	cid := c.id.ChildBegin()
	for pos := range 4 {
		children[pos] = Cell{
			face:        c.face,
			level:       c.level + 1,
			orientation: c.orientation ^ int8(posToOrientation[pos]),
			id:          cid,
		}

		// We want to split the cell in half in u and v. To decide which
		// side to set equal to the midpoint value, we look at cell's (i,j)
		// position within its parent. The index for i is in bit 1 of ij.
		ij := posToIJ[c.orientation][pos]
		i := ij >> 1
		j := ij & 1
		if i == 1 {
			children[pos].uv.X.Hi = c.uv.X.Hi
			children[pos].uv.X.Lo = uvMid.X
		} else {
			children[pos].uv.X.Lo = c.uv.X.Lo
			children[pos].uv.X.Hi = uvMid.X
		}
		if j == 1 {
			children[pos].uv.Y.Hi = c.uv.Y.Hi
			children[pos].uv.Y.Lo = uvMid.Y
		} else {
			children[pos].uv.Y.Lo = c.uv.Y.Lo
			children[pos].uv.Y.Hi = uvMid.Y
		}
		cid = cid.Next()
	}
	return children, true
}

// ExactArea returns the area of this cell as accurately as possible.
func (c Cell) ExactArea() float64 {
	v0, v1, v2, v3 := c.Vertex(0), c.Vertex(1), c.Vertex(2), c.Vertex(3)
	return PointArea(v0, v1, v2) + PointArea(v0, v2, v3)
}

// ApproxArea returns the approximate area of this cell. This method is accurate
// to within 3% percent for all cell sizes and accurate to within 0.1% for cells
// at level 5 or higher (i.e. squares 350km to a side or smaller on the Earth's
// surface). It is moderately cheap to compute.
func (c Cell) ApproxArea() float64 {
	// All cells at the first two levels have the same area.
	if c.level < 2 {
		return c.AverageArea()
	}

	// First, compute the approximate area of the cell when projected
	// perpendicular to its normal. The cross product of its diagonals gives
	// the normal, and the length of the normal is twice the projected area.
	flatArea := 0.5 * (c.Vertex(2).Sub(c.Vertex(0).Vector).
		Cross(c.Vertex(3).Sub(c.Vertex(1).Vector)).Norm())

	// Now, compensate for the curvature of the cell surface by pretending
	// that the cell is shaped like a spherical cap. The ratio of the
	// area of a spherical cap to the area of its projected disc turns out
	// to be 2 / (1 + sqrt(1 - r*r)) where r is the radius of the disc.
	// For example, when r=0 the ratio is 1, and when r=1 the ratio is 2.
	// Here we set Pi*r*r == flatArea to find the equivalent disc.
	return flatArea * 2 / (1 + math.Sqrt(1-math.Min(1/math.Pi*flatArea, 1)))
}

// AverageArea returns the average area of cells at the level of this cell.
// This is accurate to within a factor of 1.7.
func (c Cell) AverageArea() float64 {
	return AvgAreaMetric.Value(int(c.level))
}

// IntersectsCell reports whether the intersection of this cell and the other cell is not nil.
func (c Cell) IntersectsCell(oc Cell) bool {
	return c.id.Intersects(oc.id)
}

// ContainsCell reports whether this cell contains the other cell.
func (c Cell) ContainsCell(oc Cell) bool {
	return c.id.Contains(oc.id)
}

// CellUnionBound computes a covering of the Cell.
func (c Cell) CellUnionBound() []CellID {
	return []CellID{c.id}
}

// latitude returns the latitude of the cell vertex in radians given by (i,j),
// where i and j indicate the Hi (1) or Lo (0) corner.
func (c Cell) latitude(i, j int) float64 {
	var u, v float64
	switch {
	case i == 0 && j == 0:
		u = c.uv.X.Lo
		v = c.uv.Y.Lo
	case i == 0 && j == 1:
		u = c.uv.X.Lo
		v = c.uv.Y.Hi
	case i == 1 && j == 0:
		u = c.uv.X.Hi
		v = c.uv.Y.Lo
	case i == 1 && j == 1:
		u = c.uv.X.Hi
		v = c.uv.Y.Hi
	default:
		panic("i and/or j is out of bounds")
	}
	return latitude(Point{faceUVToXYZ(int(c.face), u, v)}).Radians()
}

// longitude returns the longitude of the cell vertex in radians given by (i,j),
// where i and j indicate the Hi (1) or Lo (0) corner.
func (c Cell) longitude(i, j int) float64 {
	var u, v float64
	switch {
	case i == 0 && j == 0:
		u = c.uv.X.Lo
		v = c.uv.Y.Lo
	case i == 0 && j == 1:
		u = c.uv.X.Lo
		v = c.uv.Y.Hi
	case i == 1 && j == 0:
		u = c.uv.X.Hi
		v = c.uv.Y.Lo
	case i == 1 && j == 1:
		u = c.uv.X.Hi
		v = c.uv.Y.Hi
	default:
		panic("i and/or j is out of bounds")
	}
	return longitude(Point{faceUVToXYZ(int(c.face), u, v)}).Radians()
}

var (
	poleMinLat = math.Asin(math.Sqrt(1.0/3)) - 0.5*machineEpsilon64
)

// RectBound returns the bounding rectangle of this cell.
func (c Cell) RectBound() Rect {
	if c.level > 0 {
		// Except for cells at level 0, the latitude and longitude extremes are
		// attained at the vertices.  Furthermore, the latitude range is
		// determined by one pair of diagonally opposite vertices and the
		// longitude range is determined by the other pair.
		//
		// We first determine which corner (i,j) of the cell has the largest
		// absolute latitude.  To maximize latitude, we want to find the point in
		// the cell that has the largest absolute z-coordinate and the smallest
		// absolute x- and y-coordinates.  To do this we look at each coordinate
		// (u and v), and determine whether we want to minimize or maximize that
		// coordinate based on the axis direction and the cell's (u,v) quadrant.
		u := c.uv.X.Lo + c.uv.X.Hi
		v := c.uv.Y.Lo + c.uv.Y.Hi
		var i, j int
		if uAxis(int(c.face)).Z == 0 {
			if u < 0 {
				i = 1
			}
		} else if u > 0 {
			i = 1
		}
		if vAxis(int(c.face)).Z == 0 {
			if v < 0 {
				j = 1
			}
		} else if v > 0 {
			j = 1
		}
		lat := r1.IntervalFromPoint(c.latitude(i, j)).AddPoint(c.latitude(1-i, 1-j))
		lng := s1.EmptyInterval().AddPoint(c.longitude(i, 1-j)).AddPoint(c.longitude(1-i, j))

		// We grow the bounds slightly to make sure that the bounding rectangle
		// contains LatLngFromPoint(P) for any point P inside the loop L defined by the
		// four *normalized* vertices.  Note that normalization of a vector can
		// change its direction by up to 0.5 * machineEpsilon64 radians, and it is not
		// enough just to add Normalize calls to the code above because the
		// latitude/longitude ranges are not necessarily determined by diagonally
		// opposite vertex pairs after normalization.
		//
		// We would like to bound the amount by which the latitude/longitude of a
		// contained point P can exceed the bounds computed above.  In the case of
		// longitude, the normalization error can change the direction of rounding
		// leading to a maximum difference in longitude of 2 * machineEpsilon64.  In
		// the case of latitude, the normalization error can shift the latitude by
		// up to 0.5 * machineEpsilon64 and the other sources of error can cause the
		// two latitudes to differ by up to another 1.5 * machineEpsilon64, which also
		// leads to a maximum difference of 2 * machineEpsilon64.
		return Rect{lat, lng}.expanded(LatLng{s1.Angle(2 * machineEpsilon64), s1.Angle(2 * machineEpsilon64)}).PolarClosure()
	}

	// The 4 cells around the equator extend to +/-45 degrees latitude at the
	// midpoints of their top and bottom edges.  The two cells covering the
	// poles extend down to +/-35.26 degrees at their vertices.  The maximum
	// error in this calculation is 0.5 * machineEpsilon64.
	var bound Rect
	switch c.face {
	case 0:
		bound = Rect{r1.Interval{Lo: -math.Pi / 4, Hi: math.Pi / 4}, s1.Interval{Lo: -math.Pi / 4, Hi: math.Pi / 4}}
	case 1:
		bound = Rect{r1.Interval{Lo: -math.Pi / 4, Hi: math.Pi / 4}, s1.Interval{Lo: math.Pi / 4, Hi: 3 * math.Pi / 4}}
	case 2:
		bound = Rect{r1.Interval{Lo: poleMinLat, Hi: math.Pi / 2}, s1.FullInterval()}
	case 3:
		bound = Rect{r1.Interval{Lo: -math.Pi / 4, Hi: math.Pi / 4}, s1.Interval{Lo: 3 * math.Pi / 4, Hi: -3 * math.Pi / 4}}
	case 4:
		bound = Rect{r1.Interval{Lo: -math.Pi / 4, Hi: math.Pi / 4}, s1.Interval{Lo: -3 * math.Pi / 4, Hi: -math.Pi / 4}}
	default:
		bound = Rect{r1.Interval{Lo: -math.Pi / 2, Hi: -poleMinLat}, s1.FullInterval()}
	}

	// Finally, we expand the bound to account for the error when a point P is
	// converted to an LatLng to test for containment. (The bound should be
	// large enough so that it contains the computed LatLng of any contained
	// point, not just the infinite-precision version.) We don't need to expand
	// longitude because longitude is calculated via a single call to math.Atan2,
	// which is guaranteed to be semi-monotonic.
	return bound.expanded(LatLng{s1.Angle(machineEpsilon64), s1.Angle(0)})
}

// CapBound returns the bounding cap of this cell.
func (c Cell) CapBound() Cap {
	// We use the cell center in (u,v)-space as the cap axis.  This vector is very close
	// to GetCenter() and faster to compute.  Neither one of these vectors yields the
	// bounding cap with minimal surface area, but they are both pretty close.
	cap := CapFromPoint(Point{faceUVToXYZ(int(c.face), c.uv.Center().X, c.uv.Center().Y).Normalize()})
	for k := range 4 {
		cap = cap.AddPoint(c.Vertex(k))
	}
	return cap
}

// ContainsPoint reports whether this cell contains the given point. Note that
// unlike Loop/Polygon, a Cell is considered to be a closed set. This means
// that a point on a Cell's edge or vertex belong to the Cell and the relevant
// adjacent Cells too.
//
// If you want every point to be contained by exactly one Cell,
// you will need to convert the Cell to a Loop.
func (c Cell) ContainsPoint(p Point) bool {
	// We can't just call XYZtoFaceUV, because for points that lie on the
	// boundary between two faces (i.e. u or v is +1/-1) we need to return
	// true for both adjacent cells.
	//
	// We can get away with not checking the face if the point matches the face of
	// the cell here because, for the 4 faces adjacent to c.face, p will be
	// projected outside the range of ([-1,1]x[-1,1]) and thus can't intersect the
	// cell bounds (except on the face boundary which we want).
	//
	// For the face opposite c.face, the sign of the UV coordinates of P will be
	// flipped so it will automatically fall outside the cell boundary as no cells
	// cross the origin.
	var uv r2.Point
	var ok bool
	if uv.X, uv.Y, ok = faceXYZToUV(int(c.face), p); !ok {
		return false
	}

	// Expand the (u,v) bound to ensure that
	//
	//   CellFromPoint(p).ContainsPoint(p)
	//
	// is always true. To do this, we need to account for the error when
	// converting from (u,v) coordinates to (s,t) coordinates. In the
	// normal case the total error is at most 1.125 * machineEpsilon64.
	// See https://github.com/google/s2geometry/issues/463.
	return c.uv.ExpandedByMargin((1.125 + machineEpsilon64) * machineEpsilon64).ContainsPoint(uv)
}

// Encode encodes the Cell.
func (c Cell) Encode(w io.Writer) error {
	e := &encoder{w: w}
	c.encode(e)
	return e.err
}

func (c Cell) encode(e *encoder) {
	c.id.encode(e)
}

// Decode decodes the Cell.
func (c *Cell) Decode(r io.Reader) error {
	d := &decoder{r: asByteReader(r)}
	c.decode(d)
	return d.err
}

func (c *Cell) decode(d *decoder) {
	c.id.decode(d)
	*c = CellFromCellID(c.id)
}

// vertexChordDist2 returns the squared chord distance from point P to the
// given corner vertex specified by the Hi or Lo values of each.
func (c Cell) vertexChordDist2(p Point, xHi, yHi bool) s1.ChordAngle {
	x := c.uv.X.Lo
	y := c.uv.Y.Lo
	if xHi {
		x = c.uv.X.Hi
	}
	if yHi {
		y = c.uv.Y.Hi
	}

	return ChordAngleBetweenPoints(p, PointFromCoords(x, y, 1))
}

// uEdgeIsClosest reports whether a point P is closer to the interior of the specified
// Cell edge (either the lower or upper edge of the Cell) or to the endpoints.
func (c Cell) uEdgeIsClosest(p Point, vHi bool) bool {
	u0 := c.uv.X.Lo
	u1 := c.uv.X.Hi
	v := c.uv.Y.Lo
	if vHi {
		v = c.uv.Y.Hi
	}
	// These are the normals to the planes that are perpendicular to the edge
	// and pass through one of its two endpoints.
	dir0 := r3.Vector{X: v*v + 1, Y: -u0 * v, Z: -u0}
	dir1 := r3.Vector{X: v*v + 1, Y: -u1 * v, Z: -u1}
	return p.Dot(dir0) > 0 && p.Dot(dir1) < 0
}

// vEdgeIsClosest reports whether a point P is closer to the interior of the specified
// Cell edge (either the right or left edge of the Cell) or to the endpoints.
func (c Cell) vEdgeIsClosest(p Point, uHi bool) bool {
	v0 := c.uv.Y.Lo
	v1 := c.uv.Y.Hi
	u := c.uv.X.Lo
	if uHi {
		u = c.uv.X.Hi
	}
	dir0 := r3.Vector{X: -u * v0, Y: u*u + 1, Z: -v0}
	dir1 := r3.Vector{X: -u * v1, Y: u*u + 1, Z: -v1}
	return p.Dot(dir0) > 0 && p.Dot(dir1) < 0
}

// edgeDistance reports the distance from a Point P to a given Cell edge. The point
// P is given by its dot product, and the uv edge by its normal in the
// given coordinate value.
func edgeDistance(ij, uv float64) s1.ChordAngle {
	// Let P by the target point and let R be the closest point on the given
	// edge AB.  The desired distance PR can be expressed as PR^2 = PQ^2 + QR^2
	// where Q is the point P projected onto the plane through the great circle
	// through AB.  We can compute the distance PQ^2 perpendicular to the plane
	// from "dirIJ" (the dot product of the target point P with the edge
	// normal) and the squared length the edge normal (1 + uv**2).
	pq2 := (ij * ij) / (1 + uv*uv)

	// We can compute the distance QR as (1 - OQ) where O is the sphere origin,
	// and we can compute OQ^2 = 1 - PQ^2 using the Pythagorean theorem.
	// (This calculation loses accuracy as angle POQ approaches Pi/2.)
	qr := 1 - math.Sqrt(1-pq2)
	return s1.ChordAngleFromSquaredLength(pq2 + qr*qr)
}

// distanceInternal reports the distance from the given point to the interior of
// the cell if toInterior is true or to the boundary of the cell otherwise.
func (c Cell) distanceInternal(targetXYZ Point, toInterior bool) s1.ChordAngle {
	// All calculations are done in the (u,v,w) coordinates of this cell's face.
	target := faceXYZtoUVW(int(c.face), targetXYZ)

	// Compute dot products with all four upward or rightward-facing edge
	// normals. dirIJ is the dot product for the edge corresponding to axis
	// I, endpoint J. For example, dir01 is the right edge of the Cell
	// (corresponding to the upper endpoint of the u-axis).
	dir00 := target.X - target.Z*c.uv.X.Lo
	dir01 := target.X - target.Z*c.uv.X.Hi
	dir10 := target.Y - target.Z*c.uv.Y.Lo
	dir11 := target.Y - target.Z*c.uv.Y.Hi
	inside := true
	if dir00 < 0 {
		inside = false // Target is to the left of the cell
		if c.vEdgeIsClosest(target, false) {
			return edgeDistance(-dir00, c.uv.X.Lo)
		}
	}
	if dir01 > 0 {
		inside = false // Target is to the right of the cell
		if c.vEdgeIsClosest(target, true) {
			return edgeDistance(dir01, c.uv.X.Hi)
		}
	}
	if dir10 < 0 {
		inside = false // Target is below the cell
		if c.uEdgeIsClosest(target, false) {
			return edgeDistance(-dir10, c.uv.Y.Lo)
		}
	}
	if dir11 > 0 {
		inside = false // Target is above the cell
		if c.uEdgeIsClosest(target, true) {
			return edgeDistance(dir11, c.uv.Y.Hi)
		}
	}
	if inside {
		if toInterior {
			return s1.ChordAngle(0)
		}
		// Although you might think of Cells as rectangles, they are actually
		// arbitrary quadrilaterals after they are projected onto the sphere.
		// Therefore the simplest approach is just to find the minimum distance to
		// any of the four edges.
		return min(edgeDistance(-dir00, c.uv.X.Lo),
			edgeDistance(dir01, c.uv.X.Hi),
			edgeDistance(-dir10, c.uv.Y.Lo),
			edgeDistance(dir11, c.uv.Y.Hi))
	}

	// Otherwise, the closest point is one of the four cell vertices. Note that
	// it is *not* trivial to narrow down the candidates based on the edge sign
	// tests above, because (1) the edges don't meet at right angles and (2)
	// there are points on the far side of the sphere that are both above *and*
	// below the cell, etc.
	return min(c.vertexChordDist2(target, false, false),
		c.vertexChordDist2(target, true, false),
		c.vertexChordDist2(target, false, true),
		c.vertexChordDist2(target, true, true))
}

// Distance reports the distance from the cell to the given point. Returns zero if
// the point is inside the cell.
func (c Cell) Distance(target Point) s1.ChordAngle {
	return c.distanceInternal(target, true)
}

// MaxDistance reports the maximum distance from the cell (including its interior) to the
// given point.
func (c Cell) MaxDistance(target Point) s1.ChordAngle {
	// First check the 4 cell vertices.  If all are within the hemisphere
	// centered around target, the max distance will be to one of these vertices.
	targetUVW := faceXYZtoUVW(int(c.face), target)
	maxDist := max(c.vertexChordDist2(targetUVW, false, false),
		c.vertexChordDist2(targetUVW, true, false),
		c.vertexChordDist2(targetUVW, false, true),
		c.vertexChordDist2(targetUVW, true, true))

	if maxDist <= s1.RightChordAngle {
		return maxDist
	}

	// Otherwise, find the minimum distance dMin to the antipodal point and the
	// maximum distance will be pi - dMin.
	return s1.StraightChordAngle - c.Distance(Point{target.Mul(-1)})
}

// BoundaryDistance reports the distance from the cell boundary to the given point.
func (c Cell) BoundaryDistance(target Point) s1.ChordAngle {
	return c.distanceInternal(target, false)
}

// DistanceToEdge returns the minimum distance from the cell to the given edge AB. Returns
// zero if the edge intersects the cell interior.
func (c Cell) DistanceToEdge(a, b Point) s1.ChordAngle {
	// Possible optimizations:
	//  - Currently the (cell vertex, edge endpoint) distances are computed
	//    twice each, and the length of AB is computed 4 times.
	//  - To fix this, refactor GetDistance(target) so that it skips calculating
	//    the distance to each cell vertex. Instead, compute the cell vertices
	//    and distances in this function, and add a low-level UpdateMinDistance
	//    that allows the XA, XB, and AB distances to be passed in.
	//  - It might also be more efficient to do all calculations in UVW-space,
	//    since this would involve transforming 2 points rather than 4.

	// First, check the minimum distance to the edge endpoints A and B.
	// (This also detects whether either endpoint is inside the cell.)
	minDist := min(c.Distance(a), c.Distance(b))
	if minDist == 0 {
		return minDist
	}

	// Otherwise, check whether the edge crosses the cell boundary.
	crosser := NewChainEdgeCrosser(a, b, c.Vertex(3))
	for i := range 4 {
		if crosser.ChainCrossingSign(c.Vertex(i)) != DoNotCross {
			return 0
		}
	}

	// Finally, check whether the minimum distance occurs between a cell vertex
	// and the interior of the edge AB. (Some of this work is redundant, since
	// it also checks the distance to the endpoints A and B again.)
	//
	// Note that we don't need to check the distance from the interior of AB to
	// the interior of a cell edge, because the only way that this distance can
	// be minimal is if the two edges cross (already checked above).
	for i := range 4 {
		minDist, _ = UpdateMinDistance(c.Vertex(i), a, b, minDist)
	}
	return minDist
}

// MaxDistanceToEdge returns the maximum distance from the cell (including its interior)
// to the given edge AB.
func (c Cell) MaxDistanceToEdge(a, b Point) s1.ChordAngle {
	// If the maximum distance from both endpoints to the cell is less than π/2
	// then the maximum distance from the edge to the cell is the maximum of the
	// two endpoint distances.
	maxDist := max(c.MaxDistance(a), c.MaxDistance(b))
	if maxDist <= s1.RightChordAngle {
		return maxDist
	}

	return s1.StraightChordAngle - c.DistanceToEdge(Point{a.Mul(-1)}, Point{b.Mul(-1)})
}

// DistanceToCell returns the minimum distance from this cell to the given cell.
// It returns zero if one cell contains the other.
func (c Cell) DistanceToCell(target Cell) s1.ChordAngle {
	// If the cells intersect, the distance is zero.  We use the (u,v) ranges
	// rather than CellID intersects so that cells that share a partial edge or
	// corner are considered to intersect.
	if c.face == target.face && c.uv.Intersects(target.uv) {
		return 0
	}

	// Otherwise, the minimum distance always occurs between a vertex of one
	// cell and an edge of the other cell (including the edge endpoints).  This
	// represents a total of 32 possible (vertex, edge) pairs.
	//
	// TODO(roberts): This could be optimized to be at least 5x faster by pruning
	// the set of possible closest vertex/edge pairs using the faces and (u,v)
	// ranges of both cells.
	var va, vb [4]Point
	for i := range 4 {
		va[i] = c.Vertex(i)
		vb[i] = target.Vertex(i)
	}
	minDist := s1.InfChordAngle()
	for i := range 4 {
		for j := range 4 {
			minDist, _ = UpdateMinDistance(va[i], vb[j], vb[(j+1)&3], minDist)
			minDist, _ = UpdateMinDistance(vb[i], va[j], va[(j+1)&3], minDist)
		}
	}
	return minDist
}

// MaxDistanceToCell returns the maximum distance from the cell (including its
// interior) to the given target cell.
func (c Cell) MaxDistanceToCell(target Cell) s1.ChordAngle {
	// Need to check the antipodal target for intersection with the cell. If it
	// intersects, the distance is the straight ChordAngle.
	// antipodalUV is the transpose of the original UV, interpreted within the opposite face.
	antipodalUV := r2.Rect{X: target.uv.Y, Y: target.uv.X}
	if int(c.face) == oppositeFace(int(target.face)) && c.uv.Intersects(antipodalUV) {
		return s1.StraightChordAngle
	}

	// Otherwise, the maximum distance always occurs between a vertex of one
	// cell and an edge of the other cell (including the edge endpoints).  This
	// represents a total of 32 possible (vertex, edge) pairs.
	//
	// TODO(roberts): When the maximum distance is at most π/2, the maximum is
	// always attained between a pair of vertices, and this could be made much
	// faster by testing each vertex pair once rather than the current 4 times.
	var va, vb [4]Point
	for i := range 4 {
		va[i] = c.Vertex(i)
		vb[i] = target.Vertex(i)
	}
	maxDist := s1.NegativeChordAngle
	for i := range 4 {
		for j := range 4 {
			maxDist, _ = UpdateMaxDistance(va[i], vb[j], vb[(j+1)&3], maxDist)
			maxDist, _ = UpdateMaxDistance(vb[i], va[j], va[(j+1)&3], maxDist)
		}
	}
	return maxDist
}
