package arpdis

import (
	"errors"
	"net"
	"os"
	"time"

	"golang.org/x/net/icmp"
	"golang.org/x/net/ipv4"
)

const (
	ProtocolICMP     = 1
	ProtocolIPv6ICMP = 58
)

func doPing(ip string) error {
	raddr, _ := net.ResolveIPAddr("ip4:icmp", ip)
	conn, err := icmp.ListenPacket("ip4:icmp", "")
	if err != nil {
		return err
	}

	ipv4Conn := conn.IPv4PacketConn()
	// 限制跳跃数
	err = ipv4Conn.SetTTL(10)
	if err != nil {
		return err
	}

	msg := &icmp.Message{
		Type: ipv4.ICMPTypeEcho,
		Code: 0,
		Body: &icmp.Echo{
			ID:   os.Getpid() & 0xffff,
			Seq:  1,
			Data: timeToBytes(time.Now()),
		},
	}

	b, err := msg.Marshal(nil)
	if err != nil {
		return err
	}
	_, err = conn.WriteTo(b, raddr)
	if err != nil {
		return err
	}

	_ = conn.SetReadDeadline(time.Now().Add(time.Second * 2))

	for {
		buf := make([]byte, 512)
		n, dst, err := conn.ReadFrom(buf)
		if err != nil {
			return err
		}
		if dst.String() != ip {
			continue
		}

		var result *icmp.Message
		result, err = icmp.ParseMessage(ProtocolICMP, buf[:n])
		if err != nil {
			return err
		}

		switch result.Type {
		case ipv4.ICMPTypeEchoReply:
			// success
			if rply, ok := result.Body.(*icmp.Echo); ok {
				_ = rply
				// log.Printf("%+v \n", rply)
			}
			return nil

		// case ipv4.ICMPTypeTimeExceeded:
		// case ipv4.ICMPTypeDestinationUnreachable:
		default:
			return errors.New("DestinationUnreachable")
		}
	}
}

func timeToBytes(t time.Time) []byte {
	nsec := t.UnixNano()
	b := make([]byte, 8)
	for i := uint8(0); i < 8; i++ {
		b[i] = byte((nsec >> ((7 - i) * 8)) & 0xff)
	}
	return b
}

func bytesToTime(b []byte) time.Time {
	var nsec int64
	for i := uint8(0); i < 8; i++ {
		nsec += int64(b[i]) << ((7 - i) * 8)
	}
	return time.Unix(nsec/1000000000, nsec%1000000000)
}