// Copyright 2017 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 (
	"errors"
	"fmt"

	"github.com/golang/geo/r3"
)

// maxEncodedVertices is the maximum number of vertices, in a row, to be encoded or decoded.
// On decode, this defends against malicious encodings that try and have us exceed RAM.
const maxEncodedVertices = 50000000

// xyzFaceSiTi represents the The XYZ and face,si,ti coordinates of a Point
// and, if this point is equal to the center of a Cell, the level of this cell
// (-1 otherwise). This is used for Loops and Polygons to store data in a more
// compressed format.
type xyzFaceSiTi struct {
	xyz    Point
	face   int
	si, ti uint32
	level  int
}

const derivativeEncodingOrder = 2

func appendFace(faces []faceRun, face int) []faceRun {
	if len(faces) == 0 || faces[len(faces)-1].face != face {
		return append(faces, faceRun{face, 1})
	}
	faces[len(faces)-1].count++
	return faces
}

// encodePointsCompressed uses an optimized compressed format to encode the given values.
func encodePointsCompressed(e *encoder, vertices []xyzFaceSiTi, level int) {
	var faces []faceRun
	for _, v := range vertices {
		faces = appendFace(faces, v.face)
	}
	encodeFaces(e, faces)

	type piQi struct {
		pi, qi uint32
	}
	verticesPiQi := make([]piQi, len(vertices))
	for i, v := range vertices {
		verticesPiQi[i] = piQi{siTitoPiQi(v.si, level), siTitoPiQi(v.ti, level)}
	}
	piCoder, qiCoder := newNthDerivativeCoder(derivativeEncodingOrder), newNthDerivativeCoder(derivativeEncodingOrder)
	for i, v := range verticesPiQi {
		f := encodePointCompressed
		if i == 0 {
			// The first point will be just the (pi, qi) coordinates
			// of the Point. NthDerivativeCoder will not save anything
			// in that case, so we encode in fixed format rather than varint
			// to avoid the varint overhead.
			f = encodeFirstPointFixedLength
		}
		f(e, v.pi, v.qi, level, piCoder, qiCoder)
	}

	var offCenter []int
	for i, v := range vertices {
		if v.level != level {
			offCenter = append(offCenter, i)
		}
	}
	e.writeUvarint(uint64(len(offCenter)))
	for _, idx := range offCenter {
		e.writeUvarint(uint64(idx))
		e.writeFloat64(vertices[idx].xyz.X)
		e.writeFloat64(vertices[idx].xyz.Y)
		e.writeFloat64(vertices[idx].xyz.Z)
	}
}

func encodeFirstPointFixedLength(e *encoder, pi, qi uint32, level int, piCoder, qiCoder *nthDerivativeCoder) {
	// Do not ZigZagEncode the first point, since it cannot be negative.
	codedPi, codedQi := piCoder.encode(int32(pi)), qiCoder.encode(int32(qi))
	// Interleave to reduce overhead from two partial bytes to one.
	interleaved := interleaveUint32(uint32(codedPi), uint32(codedQi))

	// Write as little endian.
	bytesRequired := (level + 7) / 8 * 2
	for range bytesRequired {
		e.writeUint8(uint8(interleaved))
		interleaved >>= 8
	}
}

// encodePointCompressed encodes points into e.
// Given a sequence of Points assumed to be the center of level-k cells,
// compresses it into a stream using the following method:
// - decompose the points into (face, si, ti) tuples.
// - run-length encode the faces, combining face number and count into a
//
//	varint32. See the faceRun struct.
//
// - right shift the (si, ti) to remove the part that's constant for all cells
//
//	of level-k. The result is called the (pi, qi) space.
//
// - 2nd derivative encode the pi and qi sequences (linear prediction)
// - zig-zag encode all derivative values but the first, which cannot be
//
//	negative
//
// - interleave the zig-zag encoded values
// - encode the first interleaved value in a fixed length encoding
//
//	(varint would make this value larger)
//
// - encode the remaining interleaved values as varint64s, as the
//
//	derivative encoding should make the values small.
//
// In addition, provides a lossless method to compress a sequence of points even
// if some points are not the center of level-k cells. These points are stored
// exactly, using 3 float64 values, after the above encoded string,
// together with their index in the sequence (this leads to some redundancy - it
// is expected that only a small fraction of the points are not cell centers).
//
// To encode leaf cells, this requires 8 bytes for the first vertex plus
// an average of 3.8 bytes for each additional vertex, when computed on
// Google's geographic repository.
func encodePointCompressed(e *encoder, pi, qi uint32, level int, piCoder, qiCoder *nthDerivativeCoder) {
	// ZigZagEncode, as varint requires the maximum number of bytes for
	// negative numbers.
	zzPi := zigzagEncode(piCoder.encode(int32(pi)))
	zzQi := zigzagEncode(qiCoder.encode(int32(qi)))
	// Interleave to reduce overhead from two partial bytes to one.
	interleaved := interleaveUint32(zzPi, zzQi)
	e.writeUvarint(interleaved)
}

type faceRun struct {
	face, count int
}

func decodeFaceRun(d *decoder) faceRun {
	faceAndCount := d.readUvarint()
	fr := faceRun{
		face:  int(faceAndCount % NumFaces),
		count: int(faceAndCount / NumFaces),
	}
	if fr.count <= 0 && d.err == nil {
		d.err = errors.New("non-positive count for face run")
	}
	return fr
}

func decodeFaces(numVertices int, d *decoder) []faceRun {
	var frs []faceRun
	for nparsed := 0; nparsed < numVertices; {
		fr := decodeFaceRun(d)
		if d.err != nil {
			return nil
		}
		frs = append(frs, fr)
		nparsed += fr.count
	}
	return frs
}

// encodeFaceRun encodes each faceRun as a varint64 with value NumFaces * count + face.
func encodeFaceRun(e *encoder, fr faceRun) {
	// It isn't necessary to encode the number of faces left for the last run,
	// but since this would only help if there were more than 21 faces, it will
	// be a small overall savings, much smaller than the bound encoding.
	coded := NumFaces*uint64(fr.count) + uint64(fr.face)
	e.writeUvarint(coded)
}

func encodeFaces(e *encoder, frs []faceRun) {
	for _, fr := range frs {
		encodeFaceRun(e, fr)
	}
}

type facesIterator struct {
	faces []faceRun
	// How often have we yet shown the current face?
	numCurrentFaceShown int
	curFace             int
}

func (fi *facesIterator) next() (ok bool) {
	if len(fi.faces) == 0 {
		return false
	}
	fi.curFace = fi.faces[0].face
	fi.numCurrentFaceShown++

	// Advance fs if needed.
	if fi.faces[0].count <= fi.numCurrentFaceShown {
		fi.faces = fi.faces[1:]
		fi.numCurrentFaceShown = 0
	}

	return true
}

func decodePointsCompressed(d *decoder, level int, target []Point) {
	faces := decodeFaces(len(target), d)

	piCoder := newNthDerivativeCoder(derivativeEncodingOrder)
	qiCoder := newNthDerivativeCoder(derivativeEncodingOrder)

	iter := facesIterator{faces: faces}
	for i := range target {
		decodeFn := decodePointCompressed
		if i == 0 {
			decodeFn = decodeFirstPointFixedLength
		}
		pi, qi := decodeFn(d, level, piCoder, qiCoder)
		if ok := iter.next(); !ok && d.err == nil {
			d.err = fmt.Errorf("ran out of faces at target %d", i)
			return
		}
		target[i] = Point{facePiQitoXYZ(iter.curFace, pi, qi, level)}
	}

	numOffCenter := d.readUvarint()
	if d.err != nil {
		return
	}
	if numOffCenter > uint64(len(target)) {
		d.err = fmt.Errorf("numOffCenter = %d, should be at most len(target) = %d", numOffCenter, len(target))
		return
	}
	for range numOffCenter {
		idx := d.readUvarint()
		if d.err != nil {
			return
		}
		if idx >= uint64(len(target)) {
			d.err = fmt.Errorf("off center index = %d, should be < len(target) = %d", idx, len(target))
			return
		}
		target[idx].X = d.readFloat64()
		target[idx].Y = d.readFloat64()
		target[idx].Z = d.readFloat64()
	}
}

func decodeFirstPointFixedLength(d *decoder, level int, piCoder, qiCoder *nthDerivativeCoder) (pi, qi uint32) {
	bytesToRead := (level + 7) / 8 * 2
	var interleaved uint64
	for i := range bytesToRead {
		rr := d.readUint8()
		interleaved |= (uint64(rr) << uint(i*8))
	}

	piCoded, qiCoded := deinterleaveUint32(interleaved)

	return uint32(piCoder.decode(int32(piCoded))), uint32(qiCoder.decode(int32(qiCoded)))
}

func zigzagEncode(x int32) uint32 {
	return (uint32(x) << 1) ^ uint32(x>>31)
}

func zigzagDecode(x uint32) int32 {
	return int32((x >> 1) ^ uint32((int32(x&1)<<31)>>31))
}

func decodePointCompressed(d *decoder, level int, piCoder, qiCoder *nthDerivativeCoder) (pi, qi uint32) {
	interleavedZigZagEncodedDerivPiQi := d.readUvarint()
	piZigzag, qiZigzag := deinterleaveUint32(interleavedZigZagEncodedDerivPiQi)
	return uint32(piCoder.decode(zigzagDecode(piZigzag))), uint32(qiCoder.decode(zigzagDecode(qiZigzag)))
}

// We introduce a new coordinate system (pi, qi), which is (si, ti)
// with the bits that are constant for cells of that level shifted
// off to the right.
// si = round(s * 2^31)
// pi = si >> (31 - level)
//    = floor(s * 2^level)
// If the point has been snapped to the level, the bits that are
// shifted off will be a 1 in the msb, then 0s after that, so the
// fractional part discarded by the cast is (close to) 0.5.

// stToPiQi returns the value transformed to the PiQi coordinate space.
func stToPiQi(s float64, level uint) uint32 {
	return uint32(s * float64(int(1)<<level))
}

// siTitoPiQi returns the value transformed into the PiQi coordinate spade.
// encodeFirstPointFixedLength encodes the return value using level bits,
// so we clamp si to the range [0, 2**level - 1] before trying to encode
// it. This is okay because if si == maxSiTi, then it is not a cell center
// anyway and will be encoded separately as an off-center point.
func siTitoPiQi(siTi uint32, level int) uint32 {
	s := uint(siTi)
	const max = maxSiTi - 1
	if s > max {
		s = max
	}

	return uint32(s >> (MaxLevel + 1 - uint(level)))
}

// piQiToST returns the value transformed to ST space.
func piQiToST(pi uint32, level int) float64 {
	// We want to recover the position at the center of the cell. If the point
	// was snapped to the center of the cell, then math.Modf(s * 2^level) == 0.5.
	// Inverting STtoPiQi gives:
	// s = (pi + 0.5) / 2^level.
	return (float64(pi) + 0.5) / float64(int(1)<<uint(level))
}

func facePiQitoXYZ(face int, pi, qi uint32, level int) r3.Vector {
	return faceUVToXYZ(face, stToUV(piQiToST(pi, level)), stToUV(piQiToST(qi, level))).Normalize()
}
