package netsplit

import (
	"net/netip"
	`sort`

	"go4.org/netipx"
)

/*
	Split splits the network defined in a VLSMSplitter alongside its configuration and performs the subnetting.
	This strategy allows for multiple subnets of differing sizes to be specified.

	remaining may or may not be nil depending on if all desired subnet sizes fit cleanly into the network boundaries.
*/
func (v *VLSMSplitter) Split() (nets []*netip.Prefix, remaining *netipx.IPSet, err error) {

	var ok bool
	var pfxLen int
	var pfxLen8 uint8
	var base netip.Prefix
	var sub netip.Prefix
	var subPtr *netip.Prefix
	var ipsb = new(netipx.IPSetBuilder)

	if err = ValidateSizes(v.network, v.PrefixLengths...); err != nil {
		return
	}

	/*
		I thought about using the following:

			* https://pkg.go.dev/net/netip
			* https://pkg.go.dev/github.com/sacloud/packages-go/cidr
			* https://pkg.go.dev/github.com/projectdiscovery/mapcidr
			* https://pkg.go.dev/github.com/EvilSuperstars/go-cidrman

		But, as I expected, netipx ftw again.
	*/

	if v == nil || v.PrefixLengths == nil || len(v.PrefixLengths) == 0 || v.BaseSplitter == nil || v.network == nil {
		return
	}

	if !v.Explicit {
		sort.SliceStable(
			v.PrefixLengths,
			func(i, j int) (isBefore bool) { // We use a reverse sorting by default so we get larger prefixes at the beginning.
				if v.Ascending {
					isBefore = v.PrefixLengths[i] > v.PrefixLengths[j]
				} else {
					isBefore = v.PrefixLengths[i] < v.PrefixLengths[j]
				}
				return
			},
		)
	}

	pfxLen, _ = v.network.Mask.Size()
	pfxLen8 = uint8(pfxLen)

	if base, ok = netipx.FromStdIPNet(v.network); !ok {
		err = ErrBadBoundary
		return
	}
	if !base.IsValid() {
		err = ErrBadBoundary
		return
	}

	ipsb.AddPrefix(base)
	if remaining, err = ipsb.IPSet(); err != nil {
		return
	}

	for _, size := range v.PrefixLengths {
		if size < pfxLen8 {
			err = &SplitErr{
				Wrapped:            ErrBigPrefix,
				Nets:               nets,
				Remaining:          remaining,
				LastSubnet:         &sub,
				RequestedPrefixLen: size,
			}
			return
		}

		if sub, remaining, ok = remaining.RemoveFreePrefix(size); !ok {
			err = &SplitErr{
				Wrapped:            ErrNoNetSpace,
				Nets:               nets,
				Remaining:          remaining,
				LastSubnet:         &sub,
				RequestedPrefixLen: size,
			}
			return
		}

		subPtr = new(netip.Prefix)
		*subPtr = sub
		nets = append(nets, subPtr)
	}

	return
}