package xt

import (
	"golang.org/x/sys/unix"
)

// TableFamily specifies the address family of the table Match or Target Info
// data is contained in. On purpose, we don't import the expr package here in
// order to keep the option open to import this package instead into expr.
type TableFamily byte

// InfoAny is a (un)marshaling implemented by any info type.
type InfoAny interface {
	marshal(fam TableFamily, rev uint32) ([]byte, error)
	unmarshal(fam TableFamily, rev uint32, data []byte) error
}

// Marshal a Match or Target Info type into its binary representation.
func Marshal(fam TableFamily, rev uint32, info InfoAny) ([]byte, error) {
	return info.marshal(fam, rev)
}

// Unmarshal Info binary payload into its corresponding dedicated type as
// indicated by the name argument. In several cases, unmarshalling depends on
// the specific table family the Target or Match expression with the info
// payload belongs to, as well as the specific info structure revision.
func Unmarshal(name string, fam TableFamily, rev uint32, data []byte) (InfoAny, error) {
	var i InfoAny
	switch name {
	case "addrtype":
		switch rev {
		case 0:
			i = &AddrType{}
		case 1:
			i = &AddrTypeV1{}
		}
	case "conntrack":
		switch rev {
		case 1:
			i = &ConntrackMtinfo1{}
		case 2:
			i = &ConntrackMtinfo2{}
		case 3:
			i = &ConntrackMtinfo3{}
		}
	case "tcp":
		i = &Tcp{}
	case "udp":
		i = &Udp{}
	case "SNAT":
		if fam == unix.NFPROTO_IPV4 {
			i = &NatIPv4MultiRangeCompat{}
		}
	case "DNAT":
		switch fam {
		case unix.NFPROTO_IPV4:
			if rev == 0 {
				i = &NatIPv4MultiRangeCompat{}
				break
			}
			fallthrough
		case unix.NFPROTO_IPV6:
			switch rev {
			case 1:
				i = &NatRange{}
			case 2:
				i = &NatRange2{}
			}
		}
	case "MASQUERADE":
		switch fam {
		case unix.NFPROTO_IPV4:
			i = &NatIPv4MultiRangeCompat{}
		}
	case "REDIRECT":
		switch fam {
		case unix.NFPROTO_IPV4:
			if rev == 0 {
				i = &NatIPv4MultiRangeCompat{}
				break
			}
			fallthrough
		case unix.NFPROTO_IPV6:
			i = &NatRange{}
		}
	}
	if i == nil {
		i = &Unknown{}
	}
	if err := i.unmarshal(fam, rev, data); err != nil {
		return nil, err
	}
	return i, nil
}