package tcp

import (
	"git.loafle.net/overflow/overflow_probe/discovery/discovery/types"
	"net"
	"time"

	"git.loafle.net/overflow/overflow_probe/discovery/discovery/types/timestamp"
	log "github.com/cihub/seelog"
	"github.com/google/gopacket"
	"github.com/google/gopacket/layers"

	//"git.loafle.net/overflow/overflow_probe/central/client/events"
	//"git.loafle.net/overflow/overflow_probe/discovery/communicate"
	pw "git.loafle.net/overflow/overflow_probe/discovery/core/pcapwrapper"
	"git.loafle.net/overflow/overflow_probe/discovery/util/converter"
)

type tcpRecvCallback func(tcp *layers.TCP)

func Scan(host *types.DiscoveryHost, tcpCB tcpRecvCallback) {

	//Recv(host.Zone, PcapTcpHandler{})

	w, err := pw.GetInstance(host.Zone)
	if err != nil {
		log.Error(err)
	}

	ch := w.OpenTCPChannel(host)

	//read channel
	go func() {
		for data := range ch {
			tcpCB(data)
		}
	}()

	Send(host, nil)

	time.Sleep(time.Second * 3)

	//communicate.Send(events.NewEvent(events.CENTRAL_EVENT, events.NewPortEndEvent(host.Zone.CidrInt64(), host.Ip, host.Histories, types.TYPE_TCP)))

	w.CloseTCPChannel(host, ch)
	pw.Release(host.Zone)

}

func Check(host *types.DiscoveryHost, ports []*types.DiscoveryPort) {

	//Recv(host.Zone, PcapTcpHandler{})
	Send(host, ports)

	time.Sleep(time.Second * 3)
}

func Send(host *types.DiscoveryHost, ports []*types.DiscoveryPort) {

	portPacket := CreatePortPacket(host.Zone, host)

	defer portPacket.PacketConn.Close()

	//communicate.Send(events.NewEvent(events.CENTRAL_EVENT, events.NewPortStartEvent(host.Zone.CidrInt64(), host.Ip, 10000, types.TYPE_TCP)))
	host.PortDiscoveryTime = timestamp.Now()

	if ports == nil {
		for portNumber := host.FirstScanRange; portNumber <= host.LastScanRange; portNumber++ {
			SendPortPacket(portNumber, portPacket, host)
		}
	} else {
		for _, port := range ports {
			SendPortPacket(port.Number, portPacket, host)
		}
	}

}

type PortPacket struct {
	Ip         *layers.IPv4
	Tcp        *layers.TCP
	Opts       gopacket.SerializeOptions
	PacketConn net.PacketConn
}

func CreatePortPacket(zone *types.DiscoveryZone, host *types.DiscoveryHost) *PortPacket {

	pp := &PortPacket{}

	pp.Ip = &layers.IPv4{
		SrcIP:    converter.IntToIP4(zone.Ip),
		DstIP:    net.ParseIP(host.Ip_),
		Version:  4,
		TTL:      64,
		Protocol: layers.IPProtocolTCP,
	}
	pp.Tcp = &layers.TCP{
		SrcPort: 60000,
		DstPort: 0, // will be incremented during the scan
		SYN:     true,
		Seq:     0,
	}
	pp.Opts = gopacket.SerializeOptions{
		ComputeChecksums: true,
		FixLengths:       true,
	}

	conn, err := net.ListenPacket("ip4:tcp", "0.0.0.0")
	if err != nil {
		log.Error("SYN create socket error : " + err.Error())
		return nil
	}

	pp.PacketConn = conn

	return pp
}

func SendPortPacket(port uint16, pp *PortPacket, host *types.DiscoveryHost) {

	pp.Tcp.DstPort = layers.TCPPort(port)
	pp.Tcp.SetNetworkLayerForChecksum(pp.Ip)

	buf := gopacket.NewSerializeBuffer()
	if err := gopacket.SerializeLayers(buf, pp.Opts, pp.Tcp); err != nil {
		log.Error(err)
	}

	if _, err := pp.PacketConn.WriteTo(buf.Bytes(), &net.IPAddr{IP: net.ParseIP(host.Ip_)}); err != nil {
		log.Error(err)
	}

	types.NewPortScanHistory(host, port, types.TYPE_TCP, types.SEND, "")
	time.Sleep(time.Microsecond * 200)
}
