package host

import (
	p "git.loafle.net/overflow/overflow_probe/discovery/core/pcapwrapper"
	"git.loafle.net/overflow/overflow_probe/discovery/discovery/types"
	"git.loafle.net/overflow/overflow_probe/discovery/util/converter"
	log "github.com/cihub/seelog"
	"github.com/google/gopacket"
	"github.com/google/gopacket/layers"
	"github.com/google/gopacket/pcap"
	"net"
	"time"
)

type ArpRecvHandler func(arp *layers.ARP)
type HistoryHandler func(ip net.IP)

// discovery
func Scan(zone *types.DiscoveryZone, recvhandler ArpRecvHandler, historyhandler HistoryHandler) error {
	// get channel by zone
	w, err := p.GetInstance(zone)
	if err != nil {
	}

	//// save singleton channel
	ch := w.OpenARPChannel()
	//
	//// read channel
	go func() {
		for data := range ch {
			recvhandler(data)
		}
	}()
	rangeHost := MakeTargetHostRange(zone)
	//// send arp
	err = sendARP(w.HND, rangeHost, zone, historyhandler)
	if err != nil {
	}
	//
	//// wait
	time.Sleep(10 * time.Second)
	//
	//// release arp
	w.CloseARPChannel(ch)
	p.Release(zone)

	return nil
}

func sendARP(handle *pcap.Handle, rangeHost []net.IP, zone *types.DiscoveryZone, hh HistoryHandler) error {

	hw := zone.Mac
	localIp := converter.IntToIP4(zone.Ip)

	eth := makeEthernetPacket(converter.IntToMac(hw))
	arp := makeArpPacket(converter.IntToMac(hw), localIp.To4())
	opts := gopacket.SerializeOptions{FixLengths: true, ComputeChecksums: true}

	buf := gopacket.NewSerializeBuffer()

	for _, targetHost := range rangeHost {
		if hh != nil {
			hh(targetHost)
		}
		arp.DstProtAddress = []byte(targetHost)
		gopacket.SerializeLayers(buf, opts, &eth, &arp)
		if err := handle.WritePacketData(buf.Bytes()); err != nil {
			log.Critical("ARP Send Error : ", err)
			return err
		}
		time.Sleep(time.Microsecond * 500)
	}
	return nil
}

func MakeTargetHostRange(zone *types.DiscoveryZone) []net.IP {

	//returnSlice := make([]net.IP, 0)
	//
	//for i := zone.FirstScanRange; i <= zone.LastScanRange; i++ {
	//	cIp := converter.IntToIP4(i)
	//	returnSlice = append(returnSlice, cIp)
	//}
	//
	//return returnSlice

	minIp := converter.IntToIP4(zone.FirstScanRange).To4()
	maxIp := converter.IntToIP4(zone.LastScanRange).To4()

	returnSlice := make([]net.IP, 0)
	for i := minIp[3]; i <= maxIp[3]; i++ {
		returnSlice = append(returnSlice, []byte{minIp[0], minIp[1], minIp[2], i})
	}
	return returnSlice
}

func makeArpPacket(hw net.HardwareAddr, ip net.IP) layers.ARP {
	return layers.ARP{
		AddrType:          layers.LinkTypeEthernet,
		Protocol:          layers.EthernetTypeIPv4,
		HwAddressSize:     6,
		ProtAddressSize:   4,
		Operation:         layers.ARPRequest,
		SourceHwAddress:   []byte(hw),
		SourceProtAddress: []byte(ip),
		DstHwAddress:      []byte{0, 0, 0, 0, 0, 0},
	}
}

func makeEthernetPacket(hw net.HardwareAddr) layers.Ethernet {
	return layers.Ethernet{
		SrcMAC:       hw,
		DstMAC:       net.HardwareAddr{0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
		EthernetType: layers.EthernetTypeARP,
	}
}
