package netsplit

import (
	"encoding/xml"
	"net"
	"net/netip"
	`time`

	"go4.org/netipx"
)

// SplitErr is used to wrap an error with context surrounding when/how that error was encountered.
type SplitErr struct {
	// Wrapped is the originating error during a split (or other parsing operation).
	Wrapped error
	// Nets are the subnets parsed out/collected so far.
	Nets []*netip.Prefix
	// Remaining is an IPSet of subnets/addresses that haven't been, or were unable to be, split out.
	Remaining *netipx.IPSet
	// LastSubnet is the most recently split out subnet.
	LastSubnet *netip.Prefix
	// RequestedPrefixLen is the network prefix length size, if relevant, that was attempted to be split out of Remaining.
	RequestedPrefixLen uint8
}

// NetSplitter is used to split a network into multiple nets (and any remaining prefixes/addresses that didn't fit).
type NetSplitter interface {
	SetParent(pfx net.IPNet)
	Split() (nets []*netip.Prefix, remaining *netipx.IPSet, err error)
}

// BaseSplitter is used to encapsulate the "parent" network to be split.
type BaseSplitter struct {
	network *net.IPNet
}

/*
CIDRSplitter is used to split a network based on a fixed prefix size.
It attemps to split the network into as many networks of size PrefixLength as cleanly as possible.
*/
type CIDRSplitter struct {
	// PrefixLength specifies the CIDR/prefix length of the subnets to split out.
	PrefixLength  uint8 `json:"prefix" xml:"prefix,attr" yaml:"network Prefix Length"`
	*BaseSplitter `json:"net" xml:"net,omitempty" yaml:"network,omitempty"`
}

/*
HostSplitter is used to split a network based on total number of hosts.
It attempts to evenly distribute addresses amoungs subnets.
*/
type HostSplitter struct {
	// NumberHosts is the number of hosts to be placed in each subnet to split out.
	NumberHosts uint `json:"hosts" xml:"hosts,attr" yaml:"Number of Hosts Per Subnet"`
	// InclNetAddr, if true, specifies that NumberHosts includes the network address.
	InclNetAddr bool `json:"net_addr" xml:"netAddr,attr,omitempty" yaml:"Network Address Included,omitempty"`
	// InclBcastAddr, if true, specifies that NumberHosts includes the broadcast address.
	InclBcastAddr bool `json:"bcast_addr" xml:"bcast,attr,omitempty" yaml:"Broadcast Address Included,omitempty"`
	// Strict, if true, will return an error from Split if the network cannot split into subnets of NumberHosts-addressable networks exactly.
	Strict        bool `json:"strict" xml:"strict,attr,omitempty" yaml:"Strictly Equal Hosts Per Subnet"`
	*BaseSplitter `json:"net" xml:"net,omitempty" yaml:"network,omitempty"`
}

/*
SubnetSplitter is used to split a network into a specific number of subnets of equal prefix lengths
as cleanly as poossible.
*/
type SubnetSplitter struct {
	// NumberSubnets indicates the number of subnets to split the network into.
	NumberSubnets uint `json:"nets" xml:"nets,attr" yaml:"Number of Target Subnets"`
	// Strict, if true, will return an error from Split if the network sizes cannot split into equally-sized networks.
	Strict        bool `json:"strict" xml:"strict,attr,omitempty" yaml:"Strictly Equal Subnet Sizes"`
	*BaseSplitter `json:"net" xml:"net,omitempty" yaml:"network,omitempty"`
}

/*
VLSMSplitter is used to split a network via VLSM (Variable-Length Subnet Masks) into multiple PrefixLengths,
in which there are multiple desired subnets of varying lengths.
*/
type VLSMSplitter struct {
	/*
		Ascending, if true, will subnet smaller networks/larger prefixes near the beginning
		(ascending order) instead of larger networks/smaller prefixes (descending order).
		You almost assuredly do not want to do this.
	*/
	Ascending bool `json:"asc,omitempty" xml:"asc,attr,omitempty" yaml:"Ascending Order,omitempty"`
	/*
		Explicit, if true, will ignore Ascending completely and split in the explicit order of PrefixLengths.

		This has the potential to be *extremely* wasteful of addressing space as the resulting blocks are
		VERY unoptimized.
	*/
	Explicit bool `json:"explicit,omitempty" xml:"explicit,attr,omitempty" yaml:"Explicit Ordering,omitempty"`
	// PrefixLengths contains the prefix lengths of each subnet to split out from the network.
	PrefixLengths []uint8 `json:"prefixes" xml:"prefixes>prefix" yaml:"Prefix Lengths"`
	*BaseSplitter `json:"net" xml:"net,omitempty" yaml:"network,omitempty"`
}

/*
StructuredResults is used for serializing prefixes into a structured/defined data format.
*/
type StructuredResults struct {
	XMLName xml.Name `json:"-" xml:"results" yaml:"-"`
	// Original is the provided parent network/prefix.
	Original *netip.Prefix `json:"orig" xml:"orig,attr,omitempty" yaml:"Original/Parent network"`
	// HostAddr is nil if Original falls on a network prefix boundary, otherwise it is the specified host address.
	HostAddr *netip.Addr `json:"host" xml:"host,attr,omitempty" yaml:"Host Address,omitempty"`
	// Canonical is the canonical network of Original (e.g. with host bits masked out). It is nil if Original.Addr() falls on the (lower) boundary.
	Canonical *netip.Prefix `json:"masked" xml:"masked,attr,omitempty" yaml:"Bound Original/Parent network"`
	// Splitter contains the spplitter and its options used to split the network.
	Splitter *SplitOpts `json:"splitter" xml:"splitter,omitempty" yaml:"Splitter,omitempty"`
	// Allocated contains valid subnet(s) in Original per the user-specified subnetting rules.
	Allocated []*ContainedResult `json:"subnets" xml:"subnets>subnet,omitempty" yaml:"Subnets"`
	// Unallocated contains subnets from Original that did not meet the splitting criteria or were left over from the split operation.
	Unallocated []*ContainedResult `json:"remaining" xml:"remaining>subnet,omitempty" yaml:"Remaining/Unallocated/Left Over,omitempty"`
	// Reservations contains any reserved addresses/prefixes within this set that are considered "special usage" and thus are likely to not be usable.
	Reservations []*IANAAddrNetResRecord `json:"reserved,omitempty" xml:"reserved>reservation,omitempty" yaml:"Matching Reserved Subnets,omitempty"`
}

type SplitOpts struct {
	XMLName xml.Name        `json:"-" xml:"splitter" yaml:"-"`
	CIDR    *CIDRSplitter   `json:"cidr,omitempty" xml:"cidr,omitempty" yaml:"CIDR Splitter,omitempty"`
	Host    *HostSplitter   `json:"host,omitempty" xml:"host,omitempty" yaml:"Host Splitter,omitempty"`
	Subnet  *SubnetSplitter `json:"subnet,omitempty" xml:"subnet,omitempty" yaml:"Subnet Splitter,omitempty"`
	VLSM    *VLSMSplitter   `json:"vlsm,omitempty" xml:"vlsm,omitempty" yaml:"VLSM Splitter,omitempty"`
}

// ContainedResult is a single Network (either an allocated subnet or a remaining block).
type ContainedResult struct {
	XMLName xml.Name      `json:"-" yaml:"-" xml:"subnet"`
	Network *netip.Prefix `json:"net" xml:"net,attr,omitempty" yaml:"network,omitempty"`
}

type IANADate time.Time

/*
	WHAT a PITA.
	IANA publishes their reservations in XML (YAY!) ... except they use inner XML all over the place
	in their text.
	So.
*/
type IANARegistry struct {
	XMLName   xml.Name                `json:"-" yaml:"-" xml:"registry"`
	Title     string                  `json:"title" yaml:"Title" xml:"title"`
	Category  string                  `json:"category,omitempty" yaml:"Category,omitempty" xml:"category,omitempty"`
	Created   IANADate                `json:"created" yaml:"Created" xml:"created"`
	Updated   *IANADate               `json:"updated,omitempty" yaml:"Updated,omitempty" xml:"updated,omitempty"`
	Notice    *IANARegistryData       `json:"notice" yaml:"Notice" xml:"registry"`
	Footnotes []*IANARegistryFootnote `json:"footnotes,omitempty" yaml:"Footnotes,omitempty" xml:"footnote,omitempty"`
}

type IANAPrefix struct {
	// IANA may include multiple prefixes in the same record.
	Prefixes   []*netip.Prefix `json:"prefix" yaml:"prefix" xml:"prefixes>prefix,attr"`
	References []*IANARef      `json:"refs,omitempty" yaml:"References,omitempty" xml:"refs,omitempty"`
}

type IANAString struct {
	Text       string     `json:"text" yaml:"Text" xml:"text"`
	References []*IANARef `json:"refs" yaml:"References" xml:"references"`
}

type IANARegistryFootnote struct {
	XMLName      xml.Name    `json:"-" yaml:"-" xml:"footnote"`
	ReferenceIdx uint        `json:"ref" yaml:"Reference Index/ID" xml:"anchor,attr"`
	Note         *IANAString `json:"note" yaml:"Note" xml:"node"`
}

type IANARegistryData struct {
	XMLName xml.Name                `json:"-" yaml:"-" xml:"registry"`
	ID      string                  `json:"id" yaml:"ID" xml:"id,attr"`
	Title   string                  `json:"title" yaml:"Title" xml:"title"`
	Refs    []*IANARef              `json:"refs" yaml:"Referencess" xml:"xref"`
	Rule    string                  `json:"rule" yaml:"Registration Rule" xml:"registration_rule"`
	Note    *IANAString             `json:"note,omitempty" yaml:"Note,omitempty" xml:"note,omitempty"`
	Records []*IANAAddrNetResRecord `json:"records,omitempty" yaml:"Records,omitempty" xml:"record,omitempty"`
}

// IANARef is used to hold references to RFCs etc.
type IANARef struct {
	XMLName xml.Name `json:"-" yaml:"-" xml:"xref"`
	Type    string   `json:"type" yaml:"Type" xml:"type,attr"`
	// TODO: This may have inner XML.
	Reference string `json:"ref" yaml:"Reference ID" xml:"data,attr"`
}

type IANABool struct {
	Applicable *bool `json:"applicable,omitempty" yaml:"Is Applicable,omitempty" xml:"applicable,attr,omitempty"`
	Evaluated  *bool `json:"bool,omitempty" yaml:"As Boolean,omitempty" xml:"evaluated,attr,omitempty"`
}
type IANAAddrNetResRecord struct {
	XMLName  xml.Name    `json:"-" yaml:"-" xml:"record"`
	Updated  *IANADate   `json:"updated,omitempty" xml:"updated,omitempty"`
	Networks *IANAPrefix `json:"net,omitempty" yaml:"Address/Network,omitempty" xml:"address,omitempty"`
	Name     string      `json:"name" yaml:"Name" xml:"name"`
	// TODO: This has inner XML.
	Spec          *IANAString `json:"spec" yaml:"Spec" xml:"spec"`
	Allocation    IANADate    `json:"alloc" yaml:"Allocation Month" xml:"allocation"`
	Termination   *IANADate   `json:"term,omitempty" yaml:"Termination,omitempty" xml:"termination,omitempty"`
	Source        *IANABool   `json:"source,omitempty" yaml:"Is Source,omitempty" xml:"source,omitempty"`
	Dest          *IANABool   `json:"dest,omitempty" yaml:"Is Destination,omitempty" xml:"dest,omitempty"`
	Forwardable   *IANABool   `json:"forwardable,omitempty" yaml:"Is Forwardable,omitempty" xml:"forwardable,omitempty"`
	GlobalReach   *IANABool   `json:"global,omitempty" yaml:"Is Globally Reachable,omitempty" xml:"global,omitempty"`
	ProtoReserved *IANABool   `json:"reserved,omitempty" yaml:"Is Reserved by Protocol,omitempty" xml:"reserved,omitempty"`
}