// Package hashable provides handy utility types for making unhashable values
// hashable.
package hashable

import (
	"net"
	"net/netip"
)

// MACAddr is a hashable encoding of a MAC address.
type MACAddr uint64

// MACAddrFromSlice parses the 6-byte slice as a MAC-48 address.
// Note that a [net.HardwareAddr] can be passed directly as the []byte argument.
// If slice's length is not 6, MACAddrFromSlice returns 0, false.
func MACAddrFromSlice(slice net.HardwareAddr) (MACAddr, bool) {
	if len(slice) != 6 {
		return 0, false
	}
	return MACAddrFrom6([6]byte(slice)), true
}

// MACAddrFrom6 returns the address of the MAC-48 address
// given by the bytes in addr.
func MACAddrFrom6(addr [6]byte) MACAddr {
	return MACAddr(addr[0])<<40 | MACAddr(addr[1])<<32 | MACAddr(addr[2])<<24 |
		MACAddr(addr[3])<<16 | MACAddr(addr[4])<<8 | MACAddr(addr[5])
}

// ParseMAC parses s as an IEEE 802 MAC-48 address using one of the formats
// accepted by [net.ParseMAC].
func ParseMAC(s string) (MACAddr, error) {
	hw, err := net.ParseMAC(s)
	if err != nil {
		return 0, err
	}
	mac, ok := MACAddrFromSlice(hw)
	if !ok {
		return 0, &net.AddrError{Err: "not a MAC-48 address", Addr: s}
	}
	return mac, nil
}

// AsSlice returns a MAC address in its 6-byte representation.
func (p MACAddr) AsSlice() []byte {
	mac := [6]byte{
		byte(p >> 40), byte(p >> 32), byte(p >> 24),
		byte(p >> 16), byte(p >> 8), byte(p),
	}
	return mac[:]
}

// String returns net.HardwareAddr(p.AsSlice()).String().
func (p MACAddr) String() string {
	return net.HardwareAddr(p.AsSlice()).String()
}

// IPMAC is a hashable tuple of an IP address and a MAC address suitable for use as a map key.
type IPMAC struct {
	ip  netip.Addr
	mac MACAddr
}

// IPMACFrom returns an [IPMAC] with the provided IP and MAC addresses.
func IPMACFrom(ip netip.Addr, mac MACAddr) IPMAC {
	return IPMAC{ip: ip, mac: mac}
}

func (i IPMAC) String() string {
	return i.ip.String() + " " + i.mac.String()
}

func (i IPMAC) IP() netip.Addr {
	return i.ip
}

func (i IPMAC) MAC() MACAddr {
	return i.mac
}