package cache

import (
	"github.com/samber/lo"
	"gitlink.org.cn/cloudream/common/utils/lo2"
	"gitlink.org.cn/cloudream/common/utils/math2"
	"gitlink.org.cn/cloudream/common/utils/sort2"
)

// 查找第一个包含指定位置的段索引。如果所有段都不包含指定位置，那么有以下三种情况：
//
// 1. pos小于第一个段的位置，返回-1
//
// 2. pos大于等于最后一个段的结束位置，返回BuzyCount() - 1
//
// 3. pos在段之间的空洞内，那么会返回小于pos的最后一个段
//
// 注：2、3情况返回的结果是相同的
func FirstContainsIndex(arr []*Range, pos int64) int {
	low, high := 0, len(arr)-1

	for low <= high {
		mid := (low + high) / 2

		if arr[mid].GetPosition() > pos {
			high = mid - 1
		} else {
			low = mid + 1
		}
	}

	return low - 1
}

func FirstContains(arr []*Range, pos int64) (*Range, bool) {
	index := FirstContainsIndex(arr, pos)

	var ret *Range
	if index < 0 {
		return nil, false
	}

	ret = arr[index]

	if ret.End() <= pos {
		return nil, false
	}

	return ret, true
}

func AddRange(arr []*Range, rng *Range) []*Range {
	if len(arr) == 0 {
		return []*Range{rng}
	}

	idx := FirstContainsIndex(arr, rng.GetPosition())
	arr = lo2.Insert(arr, idx+1, rng)

	lowerIdx := math2.Max(0, idx)
	lowerRng := arr[lowerIdx]

	for i := lowerIdx + 1; i < len(arr); i++ {
		nextRng := arr[i]
		if nextRng.GetPosition() > lowerRng.End() {
			break
		}

		curRngEnd := lowerRng.End()
		nextRngEnd := nextRng.End()
		lowerRng.SetPosition(math2.Min(lowerRng.GetPosition(), nextRng.GetPosition()))
		lowerRng.SetLength(math2.Max(curRngEnd, nextRngEnd) - lowerRng.GetPosition())

		nextRng.SetLength(0)
	}

	return lo.Reject(arr, func(i *Range, idx int) bool {
		return i.GetLength() == 0
	})
}

func MergeRanges(arr []*Range, rngs []*Range) []*Range {
	type Point struct {
		pos     int64
		isStart bool
	}

	points := make([]Point, 0, (len(arr)+len(rngs))*2)

	for _, rng := range arr {
		points = append(points, Point{rng.GetPosition(), true})
		points = append(points, Point{rng.GetPosition() + rng.GetLength(), false})
	}

	for _, rng := range rngs {
		points = append(points, Point{rng.GetPosition(), true})
		points = append(points, Point{rng.GetPosition() + rng.GetLength(), false})
	}

	points = sort2.Sort(points, func(a, b Point) int {
		if a.pos == b.pos {
			// 位置相同，则endpoint在后
			return sort2.CmpBool(b.isStart, a.isStart)
		}
		return math2.Sign(a.pos - b.pos)
	})

	var merged []*Range
	depth := 0
	for _, p := range points {
		if p.isStart {
			depth++
			if depth == 1 {
				merged = append(merged, &Range{p.pos, 0})
			}
		} else {
			depth--
			if depth == 0 {
				lastIdx := len(merged) - 1
				merged[lastIdx].Length = p.pos - merged[lastIdx].Position
			}
		}
	}

	return merged
}

func TruncateRange(arr []*Range, length int64) []*Range {
	if len(arr) == 0 {
		return arr
	}

	idx := FirstContainsIndex(arr, length)
	if idx < 0 {
		return arr[:0]
	}

	rng := arr[idx]
	if rng.End() > length {
		rng.Length = length - rng.Position
	}

	if rng.Length <= 0 {
		return arr[:idx]
	}

	return arr[:idx+1]
}

// 修剪指定的范围，使其仅包含在arr的空洞中。
// 如果范围会被切分成多个，那么就只会返回第一个切分段。
func DifferentRange(rng *Range, arr []*Range) {
	rngStart := rng.Position
	rngEnd := rng.End()

	idx := FirstContainsIndex(arr, rng.Position)
	if idx >= 0 {
		leftRng := arr[idx]
		rngStart = math2.Max(rng.Position, leftRng.End())
	}

	if idx+1 < len(arr) {
		rightRng := arr[idx+1]
		rngEnd = math2.Min(rngEnd, rightRng.Position)
	}

	rng.Position = rngStart
	rng.Length = rngEnd - rngStart
}
