package master_pb

import "sort"

func (v *VolumeLocation) IsEmptyUrl() bool {
	return v.Url == "" || v.Url == ":0"
}

// SplitByPhysicalDisk returns one DiskInfo per physical disk. The wire format
// keys DataNodeInfo.DiskInfos by disk type, collapsing same-type disks into one
// entry, so this rebuilds the per-disk view. MaxVolumeCountByDisk, when present,
// is the authoritative set (empty disks included) and gives each disk its exact
// max; otherwise the set is the DiskIds seen on records with aggregate Max/Free
// split evenly.
func (d *DiskInfo) SplitByPhysicalDisk() []*DiskInfo {
	if d == nil {
		return nil
	}

	// DiskId 0 is overloaded: it is both the first physical disk (Locations[0])
	// and the protobuf default for "unset". Only treat 0 as unset when every
	// record reports 0 (the legacy case where the volume server didn't populate
	// DiskId). If any record carries a non-zero DiskId, the reporting is real and
	// a 0 means physical disk 0 — keep it distinct instead of folding it onto
	// d.DiskId, which would merge two physical disks and drop disk 0 from view.
	hasNonZero := false
	for _, vi := range d.VolumeInfos {
		if vi.DiskId != 0 {
			hasNonZero = true
			break
		}
	}
	if !hasNonZero {
		for _, eci := range d.EcShardInfos {
			if eci.DiskId != 0 {
				hasNonZero = true
				break
			}
		}
	}

	normalize := func(id uint32) uint32 {
		if id == 0 && !hasNonZero && d.DiskId != 0 {
			return d.DiskId
		}
		return id
	}

	perDiskVolumes := make(map[uint32][]*VolumeInformationMessage)
	for _, vi := range d.VolumeInfos {
		id := normalize(vi.DiskId)
		perDiskVolumes[id] = append(perDiskVolumes[id], vi)
	}
	perDiskShards := make(map[uint32][]*VolumeEcShardInformationMessage)
	for _, eci := range d.EcShardInfos {
		id := normalize(eci.DiskId)
		perDiskShards[id] = append(perDiskShards[id], eci)
	}

	diskIDs := make(map[uint32]struct{})
	for id := range perDiskVolumes {
		diskIDs[id] = struct{}{}
	}
	for id := range perDiskShards {
		diskIDs[id] = struct{}{}
	}

	// MaxVolumeCountByDisk (when present) is the authoritative set with each
	// disk's exact max.
	exactMax := len(d.MaxVolumeCountByDisk) > 0
	for diskID := range d.MaxVolumeCountByDisk {
		diskIDs[diskID] = struct{}{}
	}

	if len(diskIDs) == 0 {
		diskIDs[d.DiskId] = struct{}{}
	}

	// A lone disk equal to the aggregate needs no reconstruction; exactMax disks
	// always reconstruct so they report their own max.
	if !exactMax && len(diskIDs) == 1 {
		for diskID := range diskIDs {
			if diskID == d.DiskId {
				return []*DiskInfo{d}
			}
		}
	}

	// Sort disk IDs so the remainder distribution is deterministic and the
	// reconstructed slice is in DiskId order, which is what downstream
	// renderers expect.
	ids := make([]uint32, 0, len(diskIDs))
	for id := range diskIDs {
		ids = append(ids, id)
	}
	sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] })

	count := int64(len(ids))
	// share returns total / count, plus one extra for the first
	// (total % count) entries so the sum of shares equals total. Without
	// the remainder distribution, splitting 10 across 3 disks would yield
	// 3+3+3 = 9 and under-report aggregate capacity.
	share := func(total int64, idx int) int64 {
		base := total / count
		if int64(idx) < total%count {
			return base + 1
		}
		return base
	}

	result := make([]*DiskInfo, 0, len(ids))
	for i, diskID := range ids {
		var activeCount, remoteCount int64
		for _, vi := range perDiskVolumes[diskID] {
			if !vi.ReadOnly {
				activeCount++
			}
			if vi.RemoteStorageName != "" {
				remoteCount++
			}
		}
		volumeCount := int64(len(perDiskVolumes[diskID]))
		maxVolumeCount := share(d.MaxVolumeCount, i)
		freeVolumeCount := share(d.FreeVolumeCount, i)
		if exactMax {
			maxVolumeCount = d.MaxVolumeCountByDisk[diskID]
			// EC slots aren't subtracted (needs the configurable ratio, outside
			// this package); planners subtract them from EcShardInfos. Clamp so an
			// over-allocated disk can't report negative free.
			freeVolumeCount = maxVolumeCount - (volumeCount - remoteCount)
			if freeVolumeCount < 0 {
				freeVolumeCount = 0
			}
		}
		result = append(result, &DiskInfo{
			Type:              d.Type,
			MaxVolumeCount:    maxVolumeCount,
			VolumeCount:       volumeCount,
			FreeVolumeCount:   freeVolumeCount,
			ActiveVolumeCount: activeCount,
			RemoteVolumeCount: remoteCount,
			VolumeInfos:       perDiskVolumes[diskID],
			EcShardInfos:      perDiskShards[diskID],
			DiskId:            diskID,
			Tags:              append([]string(nil), d.Tags...),
			DiskTotalBytes:    uint64(share(int64(d.DiskTotalBytes), i)),
			DiskFreeBytes:     uint64(share(int64(d.DiskFreeBytes), i)),
		})
	}
	return result
}
