package types

import (
	"github.com/samber/lo"
	"gitlink.org.cn/cloudream/common/pkgs/types"
	"gitlink.org.cn/cloudream/common/utils/math2"
	"gitlink.org.cn/cloudream/common/utils/serder"
)

type Redundancy interface {
	GetRedundancyType() string
}

var RedundancyUnion = serder.UseTypeUnionInternallyTagged(types.Ref(types.NewTypeUnion[Redundancy](
	(*NoneRedundancy)(nil),
	(*RepRedundancy)(nil),
	(*ECRedundancy)(nil),
	(*LRCRedundancy)(nil),
	(*SegmentRedundancy)(nil),
	(*MultipartUploadRedundancy)(nil),
)), "type")

type NoneRedundancy struct {
	Redundancy      `json:"-"`
	serder.Metadata `union:"none"`
	Type            string `json:"type"`
}

func NewNoneRedundancy() *NoneRedundancy {
	return &NoneRedundancy{
		Type: "none",
	}
}

var DefaultRepRedundancy = *NewRepRedundancy(2)

type RepRedundancy struct {
	Redundancy      `json:"-"`
	serder.Metadata `union:"rep"`
	Type            string `json:"type"`
	RepCount        int    `json:"repCount"`
}

func NewRepRedundancy(repCount int) *RepRedundancy {
	return &RepRedundancy{
		Type:     "rep",
		RepCount: repCount,
	}
}

var DefaultECRedundancy = *NewECRedundancy(2, 3, 1024*1024*5)

type ECRedundancy struct {
	Redundancy      `json:"-"`
	serder.Metadata `union:"ec"`
	Type            string `json:"type"`
	K               int    `json:"k"`
	N               int    `json:"n"`
	ChunkSize       int    `json:"chunkSize"`
}

func NewECRedundancy(k int, n int, chunkSize int) *ECRedundancy {
	return &ECRedundancy{
		Type:      "ec",
		K:         k,
		N:         n,
		ChunkSize: chunkSize,
	}
}

func (b *ECRedundancy) StripSize() int64 {
	return int64(b.ChunkSize) * int64(b.K)
}

var DefaultLRCRedundancy = *NewLRCRedundancy(2, 4, []int{2}, 1024*1024*5)

type LRCRedundancy struct {
	Redundancy      `json:"-"`
	serder.Metadata `union:"lrc"`
	Type            string `json:"type"`
	K               int    `json:"k"`
	N               int    `json:"n"`
	Groups          []int  `json:"groups"`
	ChunkSize       int    `json:"chunkSize"`
}

func NewLRCRedundancy(k int, n int, groups []int, chunkSize int) *LRCRedundancy {
	return &LRCRedundancy{
		Type:      "lrc",
		K:         k,
		N:         n,
		Groups:    groups,
		ChunkSize: chunkSize,
	}
}

// 判断指定块属于哪个组。如果都不属于，则返回-1。
func (b *LRCRedundancy) FindGroup(idx int) int {
	if idx >= b.N-len(b.Groups) {
		return idx - (b.N - len(b.Groups))
	}

	for i, group := range b.Groups {
		if idx < group {
			return i
		}
		idx -= group
	}

	return -1
}

// M = N - len(Groups)，即数据块+校验块的总数，不包括组校验块。
func (b *LRCRedundancy) M() int {
	return b.N - len(b.Groups)
}

func (b *LRCRedundancy) GetGroupElements(grp int) []int {
	var idxes []int

	grpStart := 0
	for i := 0; i < grp; i++ {
		grpStart += b.Groups[i]
	}

	for i := 0; i < b.Groups[grp]; i++ {
		idxes = append(idxes, grpStart+i)
	}

	idxes = append(idxes, b.N-len(b.Groups)+grp)
	return idxes
}

type SegmentRedundancy struct {
	Redundancy      `json:"-"`
	serder.Metadata `union:"segment"`
	Type            string  `json:"type"`
	Segments        []int64 `json:"segments"` // 每一段的大小
}

func NewSegmentRedundancy(totalSize int64, segmentCount int) *SegmentRedundancy {
	return &SegmentRedundancy{
		Type:     "segment",
		Segments: math2.SplitN(totalSize, segmentCount),
	}
}

func (r *SegmentRedundancy) SegmentCount() int {
	return len(r.Segments)
}

func (r *SegmentRedundancy) CalcSegmentStart(index int) int64 {
	return lo.Sum(r.Segments[:index])
}

// 计算指定位置取整到最近的段的起始位置。
func (r *SegmentRedundancy) FloorSegmentPosition(pos int64) int64 {
	fpos := int64(0)
	for _, segLen := range r.Segments {
		segEnd := fpos + segLen
		if pos < segEnd {
			break
		}
		fpos += segLen
	}

	return fpos
}

// 计算指定范围内的段索引范围，参数和返回值所代表的范围都是左闭右开的。
// 如果end == -1，则代表计算从start到最后一个字节的范围。
func (b *SegmentRedundancy) CalcSegmentRange(start int64, end *int64) (segIdxStart int, segIdxEnd int) {
	segIdxStart = len(b.Segments)
	segIdxEnd = len(b.Segments)

	// 找到第一个包含start的段索引
	segStart := int64(0)
	for i, segLen := range b.Segments {
		segEnd := segStart + segLen
		if start < segEnd {
			segIdxStart = i
			break
		}
		segStart += segLen
	}

	if end != nil {
		// 找到第一个包含end的段索引
		segStart = int64(0)
		for i, segLen := range b.Segments {
			segEnd := segStart + segLen
			if *end <= segEnd {
				segIdxEnd = i + 1
				break
			}
			segStart += segLen
		}
	}

	return
}

type MultipartUploadRedundancy struct {
	Redundancy      `json:"-"`
	serder.Metadata `union:"multipartUpload"`
	Type            string `json:"type"`
}

func NewMultipartUploadRedundancy() *MultipartUploadRedundancy {
	return &MultipartUploadRedundancy{
		Type: "multipartUpload",
	}
}
