package main

import (
	"fmt"
	"io"
	"log"
	"net"

	"github.com/vishvananda/netlink"
	"github.com/vishvananda/netlink/nl"
)

type AddrSet struct {
	linkAttrs netlink.LinkAttrs
	linkChan  chan netlink.LinkUpdate
	addrChan  chan netlink.AddrUpdate
	family    Family

	addrs map[string]net.IP
}

func (addrs *AddrSet) String() string {
	return fmt.Sprintf("AddrSet iface=%v", addrs.linkAttrs.Name)
}

func (addrs *AddrSet) testFlag(flag net.Flags) bool {
	return addrs.linkAttrs.Flags&flag != 0
}

func (addrs *AddrSet) Up() bool {
	return addrs.testFlag(net.FlagUp)
}

func InterfaceAddrs(iface string, family Family) (*AddrSet, error) {
	var addrs AddrSet

	link, err := netlink.LinkByName(iface)
	if err != nil {
		return nil, fmt.Errorf("netlink.LinkByName %v: %v", iface, err)
	} else {
		addrs.linkAttrs = *link.Attrs()
		addrs.family = family
	}

	// list
	if addrList, err := netlink.AddrList(link, int(family)); err != nil {
		return nil, fmt.Errorf("netlink.AddrList %v: %v", link, err)
	} else {
		addrs.addrs = make(map[string]net.IP)

		for _, addr := range addrList {
			addrs.updateAddr(addr, true)
		}
	}

	// update
	addrs.linkChan = make(chan netlink.LinkUpdate)
	addrs.addrChan = make(chan netlink.AddrUpdate)

	if err := netlink.LinkSubscribe(addrs.linkChan, nil); err != nil {
		return nil, fmt.Errorf("netlink.LinkSubscribe: %v", err)
	}

	if err := netlink.AddrSubscribe(addrs.addrChan, nil); err != nil {
		return nil, fmt.Errorf("netlink.AddrSubscribe: %v", err)
	}

	return &addrs, nil
}

func (addrs *AddrSet) Read() error {
	for {
		select {
		case linkUpdate, ok := <-addrs.linkChan:
			if !ok {
				return io.EOF
			}

			linkAttrs := linkUpdate.Attrs()

			if linkAttrs.Index != addrs.linkAttrs.Index {
				continue
			}

			// update state
			addrs.updateLink(*linkAttrs)

		case addrUpdate, ok := <-addrs.addrChan:
			if !ok {
				return io.EOF
			}

			if addrUpdate.LinkIndex != addrs.linkAttrs.Index {
				continue
			}

			addrUpdateFamily := Family(nl.GetIPFamily(addrUpdate.LinkAddress.IP))
			if addrs.family != netlink.FAMILY_ALL && addrUpdateFamily != addrs.family {
				continue
			}

			// XXX: scope and other filters?
			addrs.updateAddr(netlink.Addr{
				IPNet: &addrUpdate.LinkAddress,
				Scope: addrUpdate.Scope}, addrUpdate.NewAddr)

			return nil
		}
	}
}

// Update state for address
func (addrs *AddrSet) updateAddr(addr netlink.Addr, up bool) {
	if addr.Scope >= int(netlink.SCOPE_LINK) {
		return
	}

	ip := addr.IPNet.IP

	if up {
		log.Printf("%v: up %v", addrs, ip)

		addrs.addrs[ip.String()] = ip

	} else {
		log.Printf("%v: down %v", addrs, ip)

		delete(addrs.addrs, ip.String())
	}
}

func (addrs *AddrSet) updateLink(linkAttrs netlink.LinkAttrs) {
	addrs.linkAttrs = linkAttrs

	if !addrs.Up() {
		log.Printf("%v: down", addrs)
	}
}

func (addrs *AddrSet) Each(visitFunc func(net.IP)) {
	if !addrs.Up() {
		// link down has no up addrs
		return
	}

	for _, ip := range addrs.addrs {
		visitFunc(ip)
	}
}
