package asaModels

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/sirupsen/logrus"
	"github.rackspace.com/gorax/asaapi/pkg/logger"
	"github.rackspace.com/gorax/tools/util"
)

var (
	aclAnyRE                  = regexp.MustCompile(`(?i)(any4|any6|any)`)
	aclHostRE                 = regexp.MustCompile(`(?i)host`)
	aclInactiveRE             = regexp.MustCompile(`(?i)inactive$`)
	aclIPNetRE                = regexp.MustCompile(`(?i)ip\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)`)
	aclIPV4RE                 = regexp.MustCompile(`(?:[0-9]{1,3}\.){3}[0-9]{1,3}`)
	aclIPV6NetRE              = regexp.MustCompile(`:/`)
	aclLoggedRE               = regexp.MustCompile(`(?i)\s(log)$`)
	aclNetParse               = regexp.MustCompile(`(?i)(\S+)\s+(\S+)`)
	aclObjectGroupRE          = regexp.MustCompile(`(?i)(object|object-group)`)
	aclObjGroupRE             = regexp.MustCompile(`(?i)object-group\s+(\S+)`)
	aclObjRE                  = regexp.MustCompile(`(?i)object\s+(\S+)`)
	aclProtoRE                = regexp.MustCompile(`(?i)(ip|udp|tcp|icmp)`)
	aclRE                     = regexp.MustCompile(`(?i)^access-list\s(\S+)\s(\S+)\s(.*?)$`)
	aclRemarkRE               = regexp.MustCompile(`(?i)remark\s+(.*$)`)
	aclSrcRE                  = regexp.MustCompile(`(?i)(permit|deny)\s+(icmp|ip|udp|tcp|object-group\s+\S+)\s+(any6|any4|any|host\s+\S+|object-group\s+\S+|object\s+\S+|\S+\s+\S+)(.*$)`)
	dstWithDstPort            = regexp.MustCompile(`(?i)(permit|deny)\s(ip|tcp|udp|icmp)\s(any6|any|object\s\S+|host\s\S+|object-group\s\S+|\S+)\s(any6|any|object\s\S+|host\s\S+|object-group\s\S+|\S+)\s(eq\s\S+|gt\s\S+|lt\s\S+|neg\s\S+|object-group\s\S+)`)
	dstWithDstPortRE          = regexp.MustCompile(`(?i)(permit|deny)\s(ip|udp|tcp|icmp)\s(any4|any6|any|object\s\S+|host\s\S+|object-group\s\S+|(?:[0-9]{1,3}\.){3}[0-9]{1,3}\s(?:[0-9]{1,3}\.){3}[0-9]{1,3})\s(object-group\s\S+|any4|any6|any|host\s\S+|object\s\S+|(?:[0-9]{1,3}\.){3}[0-9]{1,3}\s(?:[0-9]{1,3}\.){3}[0-9]{1,3})\s(eq\s\S+|gt\s\S+|lt\s\S+|neg\s\S+|range\s\S+\s\S+|object-group\s\S+)`)
	dstWithNoPorts            = regexp.MustCompile(`(?i)(permit|deny)\s(ip|tcp|udp|icmp)\s(any6|any|object\s\S+|host\s\S+|object-group\s\S+|\S+)\s(any6|any|object\s\S+|host\s\S+|object-group\s\S+|\S+)$`)
	dstWithNoPortsRE          = regexp.MustCompile(`(?i)(permit|deny)\s(ip|tcp|udp|icmp)\s(any4|any6|any|object\s\S+|host\s\S+|object-group\s\S+|(?:[0-9]{1,3}\.){3}[0-9]{1,3}\s(?:[0-9]{1,3}\.){3}[0-9]{1,3})\s(any4|any6|any|object\s\S+|host\s\S+|object-group\s\S+|(?:[0-9]{1,3}\.){3}[0-9]{1,3}\s(?:[0-9]{1,3}\.){3}[0-9]{1,3})`)
	dstWithSrcAndDstPortRE    = regexp.MustCompile(`(?i)(permit|deny)\s(ip|tcp|udp|icmp)\s(any4|any6|any|host\s\S+|object\s\S+|object-group\s\S+|(?:[0-9]{1,3}\.){3}[0-9]{1,3}\s(?:[0-9]{1,3}\.){3}[0-9]{1,3})\s(eq\s\S+|gt\s\S+|lt\s\S+|neg\s\S+|object-group\s\S+)\s(any4|any6|any|host\s\S+|object\s\S+|object-group\s\S+|(?:[0-9]{1,3}\.){3}[0-9]{1,3}\s(?:[0-9]{1,3}\.){3}[0-9]{1,3})\s(eq\s\S+|gt\s\S+|lt\s\S+|neg\s\S+|object-group\s\S+)`)
	dstWithSrcAndDstPorts     = regexp.MustCompile(`(?i)(permit|deny)\s(ip|tcp|udp|icmp)\s(any6|any|object\s\S+|host\s\S+|object-group\s\S+|\S+)\s(eq\s\S+|gt\s\S+|lt\s\S+|neg\s\S+|object-group\s\S+)\s(any6|any|object\s\S+|host\s\S+|object-group\s\S+|\S+)\s(eq\s\S+|gt\s\S+|lt\s\S+|neg\s\S+|object-group\s\S+)`)
	dstWithSrcPort            = regexp.MustCompile(`(?i)(permit|deny)\s(ip|tcp|udp|icmp)\s(any6|any|object\s\S+|host\s\S+|object-group\s\S+|\S+)\s(eq\s\S+|gt\s\S+|lt\s\S+|neg\s\S+|object-group\s\S+)\s(any6|any|object\s\S+|host\s\S+|object-group\s\S+|\S+)`)
	dstWithSrcPortObjectGroup = regexp.MustCompile(`(?i)(permit|deny)\s(object-group\s\S+)\s(any4|any6|any|object\s\S+|host\s\S+|object-group\s\S+|(?:[0-9]{1,3}\.){3}[0-9]{1,3}\s(?:[0-9]{1,3}\.){3}[0-9]{1,3})\s(any4|any6|any|object\s\S+|host\s\S+|object-group\s\S+|(?:[0-9]{1,3}\.){3}[0-9]{1,3}\s(?:[0-9]{1,3}\.){3}[0-9]{1,3})`)
	dstWithSrcPortRE          = regexp.MustCompile(`(?i)(permit|deny)\s(ip|tcp|udp|icmp)\s(any4|any6|any|object\s\S+|host\s\S+|object-group\s\S+|(?:[0-9]{1,3}\.){3}[0-9]{1,3}\s(?:[0-9]{1,3}\.){3}[0-9]{1,3})\s(eq\s\S+|gt\s\S+|lt\s\S+|neg\s\S+|range\s\S+\s\S+|object-group\s\S+)\s(any4|any6|any|object\s\S+|host\s\S+|object-group\s\S+|(?:[0-9]{1,3}\.){3}[0-9]{1,3}\s+(?:[0-9]{1,3}\.){3}[0-9]{1,3})`)
)

type (
	// AccessLists structure
	AccessLists struct {
		AccessLists []*AccessList `json:"access_lists"`
	}

	// AccessList contains both a raw and parsed access list
	AccessList struct {
		Name   string       `json:"name"`
		Type   string       `json:"type"`            // Extended or Standard
		Rules  []*string    `json:"rules,omitempty"` // Raw lines of ACL
		Parsed []*ParsedACL `json:"parsed_rules"`    // Parsed lines of ACL
	}

	// ACLType is the basic structure of a parsed access list entry
	ParsedACL struct {
		Line   int        `json:"line_number,omitempty"`
		Type   string     `json:"type"` // Standard ACE, or a Remark ACE
		Remark string     `json:"remark,omitempty"`
		Rule   *ParsedACE `json:"rule,omitempty"`
	}

	// ACLParse is the detail of a parsed access list entry
	ParsedACE struct {
		Action   string    `json:"action"`
		Proto    *ACLProto `json:"protocol,omitempty"`
		SrcNet   *ACLHost  `json:"source_network"`
		SrcPort  *ACLPort  `json:"source_port,omitempty"`
		DstNet   *ACLHost  `json:"destination_network"`
		DstPort  *ACLPort  `json:"destination_port,omitempty"`
		Inactive bool      `json:"inactive,omitempty"`
		Log      bool      `json:"log,omitempty"`
	}

	// ACLHost defines the structure of a protocol used in an Access List Entry
	ACLProto struct {
		Type string  `json:"type"` // [standard/object-group]
		Name *string `json:"name"` // proto or object-group
	}

	// ACLHost defines the structure of a Host/Network/ObjectGroup used in an Access List Entry
	ACLHost struct {
		Type    string `json:"type"`              // host, any, any4, any6, object, object-group, ipv4, ipv6
		Name    string `json:"name,omitempty"`    // name of above, only valid with object and object-group
		Network string `json:"network,omitempty"` // network, only valid with host, ipv4, ipv6
		Netmask string `json:"netmask,omitempty"` // netmask, only valid with ipv4
	}

	// ACLPort defines the structure of a port used in an Access List Entry
	ACLPort struct {
		Type string `json:"type"` // eq, gt, lt, range, object-group
		Item string `json:"item"` // port, port range (x x), or object-group name
	}
)

func NetworkParse(s *string) (source, destination string) {
	m := aclIPNetRE.FindStringSubmatch(*s)
	if len(m) == 0 {
		return
	}
	if m[1] == "object" || m[1] == "object-group" {
		return
	}
	source = m[1] + "/" + m[2]
	destination = m[3] + "/" + m[4]
	if m[1] == "host" {
		source = m[2] + "/255.255.255.255"
	}
	if m[3] == "host" {
		destination = m[4] + "/255.255.255.255"
	}
	return
}

func ParseAccessLists(s string) AccessLists {
	var a AccessLists
	a.Parse(s)
	return a
}

func (a *AccessLists) Parse(s string) {
	srules := make(map[string][]*string)
	rules := make(map[string][]*string)
	for _, v := range strings.Split(s, "\n") {
		r := aclRE.FindStringSubmatch(strings.TrimRight(v, " "))
		if len(r) == 0 {
			continue
		}
		if r[1] == "" {
			continue
		}
		if len(r) == 0 {
			continue
		}
		if r[2] == "extended" {
			rules[r[1]] = append(rules[r[1]], &r[3])
		}
		if r[2] == "standard" {
			srules[r[1]] = append(srules[r[1]], &r[3])
		}
	}

	for n, r := range rules {
		a.AccessLists = append(a.AccessLists, &AccessList{
			Name:  n,
			Rules: r,
			Type:  "extended",
		})
	}
	for n, r := range srules {
		a.AccessLists = append(a.AccessLists, &AccessList{
			Name:  n,
			Rules: r,
			Type:  "standard",
		})
	}
	a.ACLParse()
}

func (a *AccessLists) ACLParse() {
	for _, acl := range a.AccessLists {
		logger.Log.WithFields(logrus.Fields{
			"type": acl.Type,
			"name": acl.Name,
		}).Debug("Parsing ACL")
		if acl.Type == "extended" {
			for l, r := range acl.Rules {
				logger.Log.WithField("line", r).Debug("Parsing")
				acl.ACLParse(r, l+1)
			}
		}
		if acl.Type == "standard" {
			for l, r := range acl.Rules {
				logger.Log.WithField("line", r).Debug("Parsing")
				acl.ACLParseStandard(r, l+1)
			}
		}
	}
}

func (a *AccessList) ACLParseStandard(acl *string, line int) {
	standardACLRE := regexp.MustCompile(`(permit|deny) (\S+) (\S+)`)
	m := aclRemarkRE.FindStringSubmatch(*acl)
	if len(m) != 0 {
		a.Parsed = append(a.Parsed, &ParsedACL{
			Type:   "remark",
			Remark: m[1],
			Line:   line,
		})
		return
	}
	m = standardACLRE.FindStringSubmatch(*acl)
	if len(m) != 0 {
		// 1 = name
		// 2 = action
		// 3 = network or host
		// 4 = netmask or host ip
		src := ACLHost{}
		if m[3] == "host" {
			src.Type = "host"
			src.Network = m[3]
		} else {
			src.Type = "ipv4"
			src.Network = m[2]
			src.Netmask = m[3]
		}
		a.Parsed = append(a.Parsed, &ParsedACL{
			Type: "standard",
			Line: line,
			Rule: &ParsedACE{
				Action: m[1],
				SrcNet: &src,
			}})
	}
}
func (a *AccessList) ACLParse(acl *string, line int) {
	m := aclRemarkRE.FindStringSubmatch(*acl)
	if len(m) != 0 {
		a.Parsed = append(a.Parsed, &ParsedACL{
			Type:   "remark",
			Remark: m[1],
			Line:   line,
		})
		return
	}
	m = aclSrcRE.FindStringSubmatch(*acl)
	if len(m) != 0 {
		dstNet, srcPort, dstPort := parsePorts(*acl)
		if strings.Contains(m[2], "object-group") {
			//TODO: this is a temporary fix because an object group for the protocol is showing up in the source port as well. The regex needs to be fixed eventually.
			//also broken: permit tcp object-group foo eq 22 any eq 44 (source AND destination ports miss the destination port, not fixed here
			srcPort = nil
			dstPort = nil
		}
		a.Parsed = append(a.Parsed, &ParsedACL{
			Type: "extended",
			Line: line,
			Rule: &ParsedACE{
				Action:   m[1],
				Proto:    parseProto(m[2]),
				SrcNet:   parseHost(m[3]),
				SrcPort:  srcPort,
				DstNet:   dstNet,
				DstPort:  dstPort,
				Inactive: aclInactiveRE.MatchString(*acl),
				Log:      aclLoggedRE.MatchString(*acl),
			}})
	} else {
		logger.Log.WithField("line", *acl).Error("No ACLSRCRE match")
	}
}

func parsePorts(s string) (dstNet *ACLHost, srcPort *ACLPort, dstPort *ACLPort) {
	// First check if the line ends with inactive or log, if it does, strip it as it will interfere with our other regex expressions.'
	if aclLoggedRE.MatchString(s) {
		s = strings.TrimRight(s, " log")
	}
	if aclInactiveRE.MatchString(s) {
		s = strings.TrimRight(s, " inactive")
	}
	if aclIPV6NetRE.MatchString(s) {
		dstNet, srcPort, dstPort = parseIPv6Dst(&s)
	} else {
		dstNet, srcPort, dstPort = parseIPv4(&s)
	}
	return
}

func parseIPv4(s *string) (*ACLHost, *ACLPort, *ACLPort) {
	var dstNet ACLHost
	var srcPort, dstPort ACLPort
	dstNetPortion := []string{"", ""}
	srcPortPortion := []string{"", ""}
	dstPortPortion := []string{"", ""}
	switch {
	case dstWithDstPortRE.MatchString(*s):
		// Destination with destination port
		t := dstWithDstPortRE.FindStringSubmatch(*s)
		dstNetPortion = strings.Split(t[4], " ")
		dstPortPortion = strings.Split(t[5], " ")
	case dstWithNoPortsRE.MatchString(*s):
		// Destination with no source or destination port
		t := dstWithNoPortsRE.FindStringSubmatch(*s)
		dstNetPortion = strings.Split(t[4], " ")
	case dstWithSrcPortObjectGroup.MatchString(*s):
		// Destination with source port object-group
		t := dstWithSrcPortObjectGroup.FindStringSubmatch(*s)
		dstNetPortion = strings.Split(t[4], " ")
		srcPortPortion = strings.Split(t[2], " ")
	case dstWithSrcPortRE.MatchString(*s):
		// Destination with source port
		t := dstWithSrcPortRE.FindStringSubmatch(*s)
		dstNetPortion = strings.Split(t[5], " ")
		srcPortPortion = strings.Split(t[4], " ")
	case dstWithSrcAndDstPortRE.MatchString(*s):
		// Destination with source and destination ports
		t := dstWithSrcAndDstPortRE.FindStringSubmatch(*s)
		dstNetPortion = strings.Split(t[5], " ")
		srcPortPortion = strings.Split(t[4], " ")
		dstPortPortion = strings.Split(t[6], " ")
	default:
		logger.Log.WithFields(logrus.Fields{
			"line":            *s,
			"parent_function": "parseIPv4",
		}).Error("No ACL Regex Match on IPv4 Line")
		return nil, nil, nil
	}
	srcPort.Type = srcPortPortion[0]
	srcPort.Item = srcPortPortion[1]
	dstPort.Type = dstPortPortion[0]
	dstPort.Item = dstPortPortion[1]
	switch {
	case aclHostRE.MatchString(dstNetPortion[0]) || aclObjectGroupRE.MatchString(dstNetPortion[0]):
		dstNet.Type = dstNetPortion[0]
		dstNet.Name = dstNetPortion[1]
	case aclIPV4RE.MatchString(dstNetPortion[0]):
		dstNet.Type = "ipv4"
		dstNet.Network = dstNetPortion[0]
		dstNet.Netmask = dstNetPortion[1]
	case aclAnyRE.MatchString(dstNetPortion[0]):
		dstNet.Type = dstNetPortion[0]
	default:
		logger.Log.WithFields(logrus.Fields{
			"line":            *s,
			"parent_function": "parseIPv4",
		}).Warn("No destination address matched")
	}
	if srcPort.Type == "" && dstPort.Type == "" {
		return &dstNet, nil, nil
	}
	if srcPort.Type == "" && dstPort.Type != "" {
		return &dstNet, nil, &dstPort
	}
	if srcPort.Type != "" && dstPort.Type == "" {
		return &dstNet, &srcPort, nil
	}
	return &dstNet, &srcPort, &dstPort
}

func parseIPv6Dst(s *string) (*ACLHost, *ACLPort, *ACLPort) {
	// While this is called parseIPv6Dst, it only parses IPv6 networks that include a CIDR network notation. For specific hosts parseIPv4Dst is used.
	var dstNet ACLHost
	var srcPort, dstPort ACLPort
	dstNetPortion := []string{"", ""}
	srcPortPortion := []string{"", ""}
	dstPortPortion := []string{"", ""}
	switch {
	case dstWithDstPort.MatchString(*s):
		// Destination with destination port
		t := dstWithDstPort.FindStringSubmatch(*s)
		dstNetPortion = strings.Split(t[4], " ")
		dstPortPortion = strings.Split(t[5], " ")
	case dstWithNoPorts.MatchString(*s):
		// Destination with no source or destination port
		t := dstWithNoPorts.FindStringSubmatch(*s)
		dstNetPortion = strings.Split(t[4], " ")
	case dstWithSrcAndDstPorts.MatchString(*s):
		// Destination with both source and destination ports
		t := dstWithSrcAndDstPorts.FindStringSubmatch(*s)
		dstNetPortion = strings.Split(t[5], " ")
		srcPortPortion = strings.Split(t[4], " ")
		dstPortPortion = strings.Split(t[6], " ")
	case dstWithSrcPort.MatchString(*s):
		// Destination with source port
		t := dstWithSrcPort.FindStringSubmatch(*s)
		dstNetPortion = strings.Split(t[5], " ")
		srcPortPortion = strings.Split(t[4], " ")
	default:
		logger.Log.WithFields(logrus.Fields{
			"line":            *s,
			"parent_function": "parseIPv6Dst",
		}).Error("No ACL Regex Match on IPv6 Line")
		return nil, nil, nil
	}
	switch {
	case aclHostRE.MatchString(dstNetPortion[0]) || aclObjectGroupRE.MatchString(dstNetPortion[0]):
		dstNet.Type = dstNetPortion[0]
		dstNet.Name = dstNetPortion[1]
	case aclAnyRE.MatchString(dstNetPortion[0]):
		dstNet.Type = dstNetPortion[0]
	default:
		dstNet.Type = "ipv6"
		dstNet.Network = dstNetPortion[0]
	}
	srcPort.Type = srcPortPortion[0]
	srcPort.Item = srcPortPortion[1]
	dstPort.Type = dstPortPortion[0]
	dstPort.Item = dstPortPortion[1]
	return &dstNet, &srcPort, &dstPort
}

func parseProto(s string) *ACLProto {
	m := util.RegexpFindStringPtr(aclProtoRE, s)
	if m != nil {
		return &ACLProto{
			Type: "standard",
			Name: m,
		}
	}
	m = util.RegexpFindStringPtr(aclObjGroupRE, s)
	if m != nil {
		return &ACLProto{
			Type: "object-group",
			Name: m,
		}
	}
	return nil
}

func parseHost(s string) *ACLHost {
	m := aclAnyRE.FindStringSubmatch(s)
	if len(m) != 0 {
		return &ACLHost{
			Type: m[0],
		}
	}

	m = aclObjGroupRE.FindStringSubmatch(s)
	if len(m) != 0 {
		return &ACLHost{
			Type: "object-group",
			Name: m[1],
		}
	}
	m = aclObjRE.FindStringSubmatch(s)
	if len(m) != 0 {
		return &ACLHost{
			Type: "object",
			Name: m[1],
		}
	}

	m = aclNetParse.FindStringSubmatch(s)
	if len(m) != 0 {
		if m[1] == "host" {
			return &ACLHost{
				Type: "host",
				Name: m[2],
			}
		}
		return &ACLHost{
			Type:    "ipv4",
			Network: m[1],
			Netmask: m[2],
		}
	}
	return nil
}

// GetAccessList finds an access list entry in an ACL and returns it
func (a *AccessLists) GetAccessList(acl *string) *AccessList {
	for _, e := range a.AccessLists {
		if e.Name == *acl {
			return e
		}
	}
	return nil
}

// RemoveAccessList removes ACL from the AccessList object
func (a *AccessLists) RemoveAccessList(acl *string) {
	var acls []*AccessList
	for _, e := range a.AccessLists {
		if e.Name != *acl {
			acls = append(acls, e)
		}
	}
	a.AccessLists = acls
}

// FindHost performs a regex search on an access list for a given host and returns a slice of pointers to the results
func (a *AccessLists) FindHost(host string) []*AccessList {
	return ACLReSearch(fmt.Sprintf("(?i)host\\s+(%s)(\\s+|$)", host), a.AccessLists)
}

// FindObject performs a regex search on an access list for a given object and returns a slice of pointers to the results
func (a *AccessLists) FindObject(object string) []*AccessList {
	return ACLReSearch(fmt.Sprintf("(?i)object\\s+(%s)(\\s+|$)", object), a.AccessLists)
}

// FindObjectGroup performs a search on an access list for a given object-group and returns a slice of pointers to the results
func (a *AccessLists) FindObjectGroup(group string) []*AccessList {
	return ACLReSearch(fmt.Sprintf("(?i)object-group\\s+(%s)(\\s+|$)", group), a.AccessLists)
}

// ACLReSearch performs a regex search on slice of ACL's and returns a slice of results
func ACLReSearch(search string, a []*AccessList) (matches []*AccessList) {
	re, err := regexp.Compile(search)
	if err != nil {
		logger.Log.WithFields(logrus.Fields{
			"error":           err.Error(),
			"parent_function": "ACLReSearch",
			"search":          search,
		}).Error("Error calling regexp.Compile(search)")
		return
	}

	for _, acl := range a {
		NewAcl := &AccessList{
			Name: acl.Name,
		}
		for _, rule := range acl.Rules {
			if util.RegexpFindStringPtr(re, *rule) != nil {
				if util.RegexpFindStringPtr(re, *rule) != nil {
					NewAcl.Rules = append(NewAcl.Rules, rule)
				}
			}
		}
		if len(NewAcl.Rules) != 0 {
			matches = append(matches, NewAcl)
		}
	}
	return
}

// RemoveObject from an AccessList
func (a *AccessLists) RemoveObject(object string) {
	ACLReRemove(fmt.Sprintf("(?i)object\\s+(%s)($|\\s+)", object), a.AccessLists)
}

// RemoveObjectGroup from an access list
func (a *AccessLists) RemoveObjectGroup(group string) {
	ACLReRemove(fmt.Sprintf("(?i)object-group\\s+(%s)($|\\s+)", group), a.AccessLists)
}

// RemoveHost from an access list
func (a *AccessLists) RemoveHost(host string) {
	ACLReRemove(fmt.Sprintf("(?i)host\\s+(%s)($|\\s+)", host), a.AccessLists)
}

// ACLReRemove performs a removal based on a regex string
func ACLReRemove(search string, a []*AccessList) {
	re, err := regexp.Compile(search)
	if err != nil {
		return
	}

	for i, acl := range a {
		var newRules []*string
		for _, rule := range acl.Rules {
			if !re.MatchString(*rule) {
				newRules = append(newRules, rule)
			}
		}
		a[i].Rules = newRules
	}

	return
}

// EmptyACLs is a wrapper for EmptyACLs()
func (a *AccessLists) EmptyACLs() (empty []*AccessList) {
	return EmptyACLs(a.AccessLists)
}

// EmptyACLs returns a slice of empty AccessList objects
func EmptyACLs(a []*AccessList) (empty []*AccessList) {
	for _, acl := range a {
		if len(acl.Rules) == 0 {
			empty = append(empty, acl)
		}
	}
	return
}
