package base

import (
	"math"
	"unsafe"
)

// precalculate infinity
var mathInfNeg = math.Inf(-1)
var mathInfPos = math.Inf(+1)

type treeNode struct {
	min, max []float64
	children []*treeNode
	count    int
	height   int
	leaf     bool
}

func (node *treeNode) unsafeItem() *treeItem {
	return (*treeItem)(unsafe.Pointer(node))
}

func (tr *RTree) createNode(children []*treeNode) *treeNode {
	n := &treeNode{
		height:   1,
		leaf:     true,
		children: make([]*treeNode, tr.maxEntries+1),
	}
	if len(children) > 0 {
		n.count = len(children)
		copy(n.children[:n.count], children)
	}
	n.min = make([]float64, tr.dims)
	n.max = make([]float64, tr.dims)
	for i := 0; i < tr.dims; i++ {
		n.min[i] = mathInfPos
		n.max[i] = mathInfNeg
	}
	return n
}

func (node *treeNode) extend(b *treeNode) {
	for i := 0; i < len(node.min); i++ {
		if b.min[i] < node.min[i] {
			node.min[i] = b.min[i]
		}
		if b.max[i] > node.max[i] {
			node.max[i] = b.max[i]
		}
	}
}

func (node *treeNode) area() float64 {
	area := node.max[0] - node.min[0]
	for i := 1; i < len(node.min); i++ {
		area *= node.max[i] - node.min[i]
	}
	return area
}

func (node *treeNode) enlargedAreaAxis(b *treeNode, axis int) float64 {
	var max, min float64
	if b.max[axis] > node.max[axis] {
		max = b.max[axis]
	} else {
		max = node.max[axis]
	}
	if b.min[axis] < node.min[axis] {
		min = b.min[axis]
	} else {
		min = node.min[axis]
	}
	return max - min
}

func (node *treeNode) enlargedArea(b *treeNode) float64 {
	area := node.enlargedAreaAxis(b, 0)
	for i := 1; i < len(node.min); i++ {
		area *= node.enlargedAreaAxis(b, i)
	}
	return area
}

func (node *treeNode) intersectionAreaAxis(b *treeNode, axis int) float64 {
	var max, min float64
	if node.max[axis] < b.max[axis] {
		max = node.max[axis]
	} else {
		max = b.max[axis]
	}
	if node.min[axis] > b.min[axis] {
		min = node.min[axis]
	} else {
		min = b.min[axis]
	}
	if max > min {
		return max - min
	}
	return 0
}
func (node *treeNode) intersectionArea(b *treeNode) float64 {
	area := node.intersectionAreaAxis(b, 0)
	for i := 1; i < len(node.min); i++ {
		area *= node.intersectionAreaAxis(b, i)
	}
	return area
}
func (node *treeNode) margin() float64 {
	margin := node.max[0] - node.min[0]
	for i := 1; i < len(node.min); i++ {
		margin += node.max[i] - node.min[i]
	}
	return margin
}

type result int

const (
	not        result = 0
	intersects result = 1
	contains   result = 2
)

func (node *treeNode) overlaps(b *treeNode) result {
	for i := 0; i < len(node.min); i++ {
		if b.min[i] > node.max[i] || b.max[i] < node.min[i] {
			return not
		}
		if node.min[i] > b.min[i] || b.max[i] > node.max[i] {
			i++
			for ; i < len(node.min); i++ {
				if b.min[i] > node.max[i] || b.max[i] < node.min[i] {
					return not
				}
			}
			return intersects
		}
	}
	return contains
}

func (node *treeNode) intersects(b *treeNode) bool {
	for i := 0; i < len(node.min); i++ {
		if b.min[i] > node.max[i] || b.max[i] < node.min[i] {
			return false
		}
	}
	return true
}

func (node *treeNode) findItem(item interface{}) int {
	for i := 0; i < node.count; i++ {
		if node.children[i].unsafeItem().item == item {
			return i
		}
	}
	return -1
}

func (node *treeNode) contains(b *treeNode) bool {
	for i := 0; i < len(node.min); i++ {
		if node.min[i] > b.min[i] || b.max[i] > node.max[i] {
			return false
		}
	}
	return true
}

func (node *treeNode) childCount() int {
	if node.leaf {
		return node.count
	}
	var n int
	for i := 0; i < node.count; i++ {
		n += node.children[i].childCount()
	}
	return n
}

type treeItem struct {
	min, max []float64
	item     interface{}
}

//go:nocheckptr
func (item *treeItem) unsafeNode() *treeNode {
	return (*treeNode)(unsafe.Pointer(item))
}

// RTree is an R-tree
type RTree struct {
	dims       int
	maxEntries int
	minEntries int
	data       *treeNode // root node
	// resusable fields, these help performance of common mutable operations.
	reuse struct {
		path    []*treeNode // for reinsertion path
		indexes []int       // for remove function
		stack   []int       // for bulk loading
	}
}

// New creates a new R-tree
func New(dims, maxEntries int) *RTree {
	if dims <= 0 {
		panic("invalid dimensions")
	}

	tr := &RTree{}
	tr.dims = dims
	tr.maxEntries = int(math.Max(4, float64(maxEntries)))
	tr.minEntries = int(math.Max(2, math.Ceil(float64(tr.maxEntries)*0.4)))
	tr.data = tr.createNode(nil)
	return tr
}

// Insert inserts an item
func (tr *RTree) Insert(min, max []float64, item interface{}) {
	if len(min) != tr.dims || len(max) != tr.dims {
		panic("invalid dimensions")
	}
	if item == nil {
		panic("nil item")
	}
	bbox := treeNode{min: min, max: max}
	tr.insert(&bbox, item, tr.data.height-1, false)
}

func (tr *RTree) insert(bbox *treeNode, item interface{}, level int, isNode bool) {
	tr.reuse.path = tr.reuse.path[:0]
	node, insertPath := tr.chooseSubtree(bbox, tr.data, level, tr.reuse.path)
	if item == nil {
		// item is only nil when bulk loading a node
		if node.leaf {
			panic("loading node into leaf")
		}
		node.children[node.count] = bbox
		node.count++
	} else {
		ti := &treeItem{min: bbox.min, max: bbox.max, item: item}
		node.children[node.count] = ti.unsafeNode()
		node.count++
	}
	node.extend(bbox)
	for level >= 0 {
		if insertPath[level].count > tr.maxEntries {
			insertPath = tr.split(insertPath, level)
			level--
		} else {
			break
		}
	}
	tr.adjustParentBBoxes(bbox, insertPath, level)
	tr.reuse.path = insertPath
}

func (tr *RTree) adjustParentBBoxes(bbox *treeNode, path []*treeNode, level int) {
	// adjust bboxes along the given tree path
	for i := level; i >= 0; i-- {
		path[i].extend(bbox)
	}
}

func (tr *RTree) chooseSubtree(bbox, node *treeNode, level int, path []*treeNode) (*treeNode, []*treeNode) {
	var targetNode *treeNode
	var area, enlargement, minArea, minEnlargement float64
	for {
		path = append(path, node)
		if node.leaf || len(path)-1 == level {
			break
		}
		minEnlargement = mathInfPos
		minArea = minEnlargement
		for i := 0; i < node.count; i++ {
			child := node.children[i]
			area = child.area()
			enlargement = bbox.enlargedArea(child) - area
			if enlargement < minEnlargement {
				minEnlargement = enlargement
				if area < minArea {
					minArea = area
				}
				targetNode = child
			} else if enlargement == minEnlargement {
				if area < minArea {
					minArea = area
					targetNode = child
				}
			}
		}
		if targetNode != nil {
			node = targetNode
		} else if node.count > 0 {
			node = (*treeNode)(node.children[0])
		} else {
			node = nil
		}
	}
	return node, path
}
func (tr *RTree) split(insertPath []*treeNode, level int) []*treeNode {
	var node = insertPath[level]
	var M = node.count
	var m = tr.minEntries

	tr.chooseSplitAxis(node, m, M)
	splitIndex := tr.chooseSplitIndex(node, m, M)

	spliced := make([]*treeNode, node.count-splitIndex)
	copy(spliced, node.children[splitIndex:])
	node.count = splitIndex

	newNode := tr.createNode(spliced)
	newNode.height = node.height
	newNode.leaf = node.leaf

	tr.calcBBox(node)
	tr.calcBBox(newNode)

	if level != 0 {
		insertPath[level-1].children[insertPath[level-1].count] = newNode
		insertPath[level-1].count++
	} else {
		tr.splitRoot(node, newNode)
	}
	return insertPath
}
func (tr *RTree) chooseSplitIndex(node *treeNode, m, M int) int {
	var i int
	var bbox1, bbox2 *treeNode
	var overlap, area, minOverlap, minArea float64
	var index int

	minArea = mathInfPos
	minOverlap = minArea

	for i = m; i <= M-m; i++ {
		bbox1 = tr.distBBox(node, 0, i, nil)
		bbox2 = tr.distBBox(node, i, M, nil)

		overlap = bbox1.intersectionArea(bbox2)
		area = bbox1.area() + bbox2.area()

		// choose distribution with minimum overlap
		if overlap < minOverlap {
			minOverlap = overlap
			index = i

			if area < minArea {
				minArea = area
			}
		} else if overlap == minOverlap {
			// otherwise choose distribution with minimum area
			if area < minArea {
				minArea = area
				index = i
			}
		}
	}
	return index
}
func (tr *RTree) calcBBox(node *treeNode) {
	tr.distBBox(node, 0, node.count, node)
}
func (tr *RTree) chooseSplitAxis(node *treeNode, m, M int) {
	minMargin := tr.allDistMargin(node, m, M, 0)
	var minAxis int
	for axis := 1; axis < tr.dims; axis++ {
		margin := tr.allDistMargin(node, m, M, axis)
		if margin < minMargin {
			minMargin = margin
			minAxis = axis
		}
	}
	if minAxis < tr.dims {
		tr.sortNodes(node, minAxis)
	}
}
func (tr *RTree) splitRoot(node, newNode *treeNode) {
	tr.data = tr.createNode([]*treeNode{node, newNode})
	tr.data.height = node.height + 1
	tr.data.leaf = false
	tr.calcBBox(tr.data)
}
func (tr *RTree) distBBox(node *treeNode, k, p int, destNode *treeNode) *treeNode {
	if destNode == nil {
		destNode = tr.createNode(nil)
	} else {
		for i := 0; i < tr.dims; i++ {
			destNode.min[i] = mathInfPos
			destNode.max[i] = mathInfNeg
		}
	}
	for i := k; i < p; i++ {
		if node.leaf {
			destNode.extend(node.children[i])
		} else {
			destNode.extend((*treeNode)(node.children[i]))
		}
	}
	return destNode
}
func (tr *RTree) allDistMargin(node *treeNode, m, M int, axis int) float64 {
	tr.sortNodes(node, axis)

	var leftBBox = tr.distBBox(node, 0, m, nil)
	var rightBBox = tr.distBBox(node, M-m, M, nil)
	var margin = leftBBox.margin() + rightBBox.margin()

	var i int

	if node.leaf {
		for i = m; i < M-m; i++ {
			leftBBox.extend(node.children[i])
			margin += leftBBox.margin()
		}
		for i = M - m - 1; i >= m; i-- {
			leftBBox.extend(node.children[i])
			margin += rightBBox.margin()
		}
	} else {
		for i = m; i < M-m; i++ {
			child := (*treeNode)(node.children[i])
			leftBBox.extend(child)
			margin += leftBBox.margin()
		}
		for i = M - m - 1; i >= m; i-- {
			child := (*treeNode)(node.children[i])
			leftBBox.extend(child)
			margin += rightBBox.margin()
		}
	}
	return margin
}
func (tr *RTree) sortNodes(node *treeNode, axis int) {
	sortByAxis(node.children[:node.count], axis)
}

func sortByAxis(items []*treeNode, axis int) {
	if len(items) < 2 {
		return
	}
	left, right := 0, len(items)-1
	pivotIndex := len(items) / 2
	items[pivotIndex], items[right] = items[right], items[pivotIndex]
	for i := range items {
		if items[i].min[axis] < items[right].min[axis] {
			items[i], items[left] = items[left], items[i]
			left++
		}
	}
	items[left], items[right] = items[right], items[left]
	sortByAxis(items[:left], axis)
	sortByAxis(items[left+1:], axis)
}

// Search searches the tree for items in the input rectangle
func (tr *RTree) Search(min, max []float64, iter func(item interface{}) bool) bool {
	bbox := &treeNode{min: min, max: max}
	if !tr.data.intersects(bbox) {
		return true
	}
	return tr.search(tr.data, bbox, iter)
}

func (tr *RTree) search(node, bbox *treeNode, iter func(item interface{}) bool) bool {
	if node.leaf {
		for i := 0; i < node.count; i++ {
			if bbox.intersects(node.children[i]) {
				if !iter(node.children[i].unsafeItem().item) {
					return false
				}
			}
		}
	} else {
		for i := 0; i < node.count; i++ {
			r := bbox.overlaps(node.children[i])
			if r == intersects {
				if !tr.search(node.children[i], bbox, iter) {
					return false
				}
			} else if r == contains {
				if !scan(node.children[i], iter) {
					return false
				}
			}
		}
	}
	return true
}

func (tr *RTree) IsEmpty() bool {
	empty := true
	tr.Scan(func(item interface{}) bool {
		empty = false
		return false
	})
	return empty
}

// Remove removes an item from the R-tree.
func (tr *RTree) Remove(min, max []float64, item interface{}) {
	bbox := &treeNode{min: min, max: max}
	tr.remove(bbox, item)
}

func (tr *RTree) remove(bbox *treeNode, item interface{}) {
	path := tr.reuse.path[:0]
	indexes := tr.reuse.indexes[:0]

	var node = tr.data
	var i int
	var parent *treeNode
	var index int
	var goingUp bool

	for node != nil || len(path) != 0 {
		if node == nil {
			node = path[len(path)-1]
			path = path[:len(path)-1]
			if len(path) == 0 {
				parent = nil
			} else {
				parent = path[len(path)-1]
			}
			i = indexes[len(indexes)-1]
			indexes = indexes[:len(indexes)-1]
			goingUp = true
		}

		if node.leaf {
			index = node.findItem(item)
			if index != -1 {
				// item found, remove the item and condense tree upwards
				copy(node.children[index:], node.children[index+1:])
				node.children[node.count-1] = nil
				node.count--
				path = append(path, node)
				tr.condense(path)
				goto done
			}
		}
		if !goingUp && !node.leaf && node.contains(bbox) { // go down
			path = append(path, node)
			indexes = append(indexes, i)
			i = 0
			parent = node
			node = (*treeNode)(node.children[0])
		} else if parent != nil { // go right
			i++
			if i == parent.count {
				node = nil
			} else {
				node = (*treeNode)(parent.children[i])
			}
			goingUp = false
		} else {
			node = nil
		}
	}
done:
	tr.reuse.path = path
	tr.reuse.indexes = indexes
	return
}
func (tr *RTree) condense(path []*treeNode) {
	// go through the path, removing empty nodes and updating bboxes
	var siblings []*treeNode
	for i := len(path) - 1; i >= 0; i-- {
		if path[i].count == 0 {
			if i > 0 {
				siblings = path[i-1].children[:path[i-1].count]
				index := -1
				for j := 0; j < len(siblings); j++ {
					if siblings[j] == path[i] {
						index = j
						break
					}
				}
				copy(siblings[index:], siblings[index+1:])
				siblings[len(siblings)-1] = nil
				path[i-1].count--
				//siblings = siblings[:len(siblings)-1]
				//path[i-1].children = siblings
			} else {
				tr.data = tr.createNode(nil) // clear tree
			}
		} else {
			tr.calcBBox(path[i])
		}
	}
}

// Count returns the number of items in the R-tree.
func (tr *RTree) Count() int {
	return tr.data.childCount()
}

// Traverse iterates over the entire R-tree and includes all nodes and items.
func (tr *RTree) Traverse(iter func(min, max []float64, level int, item interface{}) bool) bool {
	return tr.traverse(tr.data, iter)
}

func (tr *RTree) traverse(node *treeNode, iter func(min, max []float64, level int, item interface{}) bool) bool {
	if !iter(node.min, node.max, int(node.height), nil) {
		return false
	}
	if node.leaf {
		for i := 0; i < node.count; i++ {
			child := node.children[i]
			if !iter(child.min, child.max, 0, child.unsafeItem().item) {
				return false
			}
		}
	} else {
		for i := 0; i < node.count; i++ {
			child := node.children[i]
			if !tr.traverse(child, iter) {
				return false
			}
		}
	}
	return true
}

// Scan iterates over the entire R-tree
func (tr *RTree) Scan(iter func(item interface{}) bool) bool {
	return scan(tr.data, iter)
}

func scan(node *treeNode, iter func(item interface{}) bool) bool {
	if node.leaf {
		for i := 0; i < node.count; i++ {
			child := node.children[i]
			if !iter(child.unsafeItem().item) {
				return false
			}
		}
	} else {
		for i := 0; i < node.count; i++ {
			child := node.children[i]
			if !scan(child, iter) {
				return false
			}
		}
	}
	return true
}

// Bounds returns the bounding box of the entire R-tree
func (tr *RTree) Bounds() (min, max []float64) {
	if tr.data.count > 0 {
		return tr.data.min, tr.data.max
	}
	return make([]float64, tr.dims), make([]float64, tr.dims)
}

// Complexity returns the complexity of the R-tree. The higher the value, the
// more complex the tree. The value of 1 is the lowest.
func (tr *RTree) Complexity() float64 {
	var nodeCount int
	var itemCount int
	tr.Traverse(func(_, _ []float64, level int, _ interface{}) bool {
		if level == 0 {
			itemCount++
		} else {
			nodeCount++
		}
		return true
	})
	return float64(tr.maxEntries*nodeCount) / float64(itemCount)
}
