// +build windows

//-----------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license.
// See LICENSE.txt file in the project root for full license information.
//-----------------------------------------------------------------------------
package main

import (
	"context"
	"net"
	"os"
	"strings"
	"syscall"
	"unsafe"

	tm "github.com/nsf/termbox-go"
)

var (
	kernel32 = syscall.NewLazyDLL("kernel32.dll")
	user32   = syscall.NewLazyDLL("user32.dll")
	iphlpapi = syscall.NewLazyDLL("iphlpapi.dll")

	proc_get_tcp_statistics_ex = iphlpapi.NewProc("GetTcpStatisticsEx")
	proc_get_if_entry2         = iphlpapi.NewProc("GetIfEntry2")
	proc_get_console_window    = kernel32.NewProc("GetConsoleWindow")
	proc_get_system_menu       = user32.NewProc("GetSystemMenu")
	proc_delete_menu           = user32.NewProc("DeleteMenu")
)

type ethrNetDevInfo struct {
	bytes   uint64
	packets uint64
	drop    uint64
	errs    uint64
}

func getNetDevStats(stats *ethrNetStat) {
	ifs, err := net.Interfaces()
	if err != nil {
		ui.printErr("%v", err)
		return
	}

	for _, ifi := range ifs {
		if (ifi.Flags&net.FlagUp) == 0 || strings.Contains(ifi.Name, "Pseudo") {
			continue
		}
		row, err := getIfEntry2(uint32(ifi.Index))
		if err != nil {
			ui.printErr("%v", err)
			return
		}
		rxInfo := ethrNetDevInfo{
			bytes:   uint64(row.InOctets),
			packets: uint64(row.InUcastPkts),
			drop:    uint64(row.InDiscards),
			errs:    uint64(row.InErrors),
		}
		txInfo := ethrNetDevInfo{
			bytes:   uint64(row.OutOctets),
			packets: uint64(row.OutUcastPkts),
			drop:    uint64(row.OutDiscards),
			errs:    uint64(row.OutErrors),
		}
		netStats := ethrNetDevStat{
			interfaceName: ifi.Name,
			rxBytes:       rxInfo.bytes,
			txBytes:       txInfo.bytes,
			rxPkts:        rxInfo.packets,
			txPkts:        txInfo.packets,
		}
		stats.netDevStats = append(stats.netDevStats, netStats)
	}
}

type mib_tcpstats struct {
	DwRtoAlgorithm uint32
	DwRtoMin       uint32
	DwRtoMax       uint32
	DwMaxConn      uint32
	DwActiveOpens  uint32
	DwPassiveOpens uint32
	DwAttemptFails uint32
	DwEstabResets  uint32
	DwCurrEstab    uint32
	DwInSegs       uint32
	DwOutSegs      uint32
	DwRetransSegs  uint32
	DwInErrs       uint32
	DwOutRsts      uint32
	DwNumConns     uint32
}

const (
	AF_INET  = 2
	AF_INET6 = 23
)

func getTCPStats(stats *ethrNetStat) (errcode error) {
	tcpStats := &mib_tcpstats{}
	r0, _, _ := syscall.Syscall(proc_get_tcp_statistics_ex.Addr(), 2,
		uintptr(unsafe.Pointer(tcpStats)), uintptr(AF_INET), 0)

	if r0 != 0 {
		errcode = syscall.Errno(r0)
		return
	}
	stats.tcpStats.segRetrans = uint64(tcpStats.DwRetransSegs)
	return
}

type guid struct {
	Data1 uint32
	Data2 uint16
	Data3 uint16
	Data4 [8]byte
}

const (
	MAX_STRING_SIZE         = 256
	MAX_PHYS_ADDRESS_LENGTH = 32
	pad0for64_4for32        = 0
)

type mibIfRow2 struct {
	InterfaceLuid               uint64
	InterfaceIndex              uint32
	InterfaceGuid               guid
	Alias                       [MAX_STRING_SIZE + 1]uint16
	Description                 [MAX_STRING_SIZE + 1]uint16
	PhysicalAddressLength       uint32
	PhysicalAddress             [MAX_PHYS_ADDRESS_LENGTH]uint8
	PermanentPhysicalAddress    [MAX_PHYS_ADDRESS_LENGTH]uint8
	Mtu                         uint32
	Type                        uint32
	TunnelType                  uint32
	MediaType                   uint32
	PhysicalMediumType          uint32
	AccessType                  uint32
	DirectionType               uint32
	InterfaceAndOperStatusFlags uint32
	OperStatus                  uint32
	AdminStatus                 uint32
	MediaConnectState           uint32
	NetworkGuid                 guid
	ConnectionType              uint32
	padding1                    [pad0for64_4for32]byte
	TransmitLinkSpeed           uint64
	ReceiveLinkSpeed            uint64
	InOctets                    uint64
	InUcastPkts                 uint64
	InNUcastPkts                uint64
	InDiscards                  uint64
	InErrors                    uint64
	InUnknownProtos             uint64
	InUcastOctets               uint64
	InMulticastOctets           uint64
	InBroadcastOctets           uint64
	OutOctets                   uint64
	OutUcastPkts                uint64
	OutNUcastPkts               uint64
	OutDiscards                 uint64
	OutErrors                   uint64
	OutUcastOctets              uint64
	OutMulticastOctets          uint64
	OutBroadcastOctets          uint64
	OutQLen                     uint64
}

func getIfEntry2(ifIndex uint32) (mibIfRow2, error) {
	var res *mibIfRow2

	res = &mibIfRow2{InterfaceIndex: ifIndex}
	r0, _, _ := syscall.Syscall(proc_get_if_entry2.Addr(), 1,
		uintptr(unsafe.Pointer(res)), 0, 0)
	if r0 != 0 {
		return mibIfRow2{}, syscall.Errno(r0)
	}
	return *res, nil
}

func hideCursor() {
	tm.HideCursor()
}

const (
	MF_BYCOMMAND = 0x00000000
	SC_CLOSE     = 0xF060
	SC_MINIMIZE  = 0xF020
	SC_MAXIMIZE  = 0xF030
	SC_SIZE      = 0xF000
)

func blockWindowResize() {
	h, _, err := syscall.Syscall(proc_get_console_window.Addr(), 0, 0, 0, 0)
	if err != 0 {
		return
	}

	sysMenu, _, err := syscall.Syscall(proc_get_system_menu.Addr(), 2, h, 0, 0)
	if err != 0 {
		return
	}

	syscall.Syscall(proc_delete_menu.Addr(), 3, sysMenu, SC_MAXIMIZE, MF_BYCOMMAND)
	syscall.Syscall(proc_delete_menu.Addr(), 3, sysMenu, SC_SIZE, MF_BYCOMMAND)
}

func setSockOptInt(fd uintptr, level, opt, val int) (err error) {
	err = syscall.SetsockoptInt(syscall.Handle(fd), level, opt, val)
	if err != nil {
		ui.printErr("Failed to set socket option (%v) to value (%v) during Dial. Error: %s", opt, val, err)
	}
	return
}

const (
	SIO_RCVALL             = syscall.IOC_IN | syscall.IOC_VENDOR | 1
	RCVALL_OFF             = 0
	RCVALL_ON              = 1
	RCVALL_SOCKETLEVELONLY = 2
	RCVALL_IPLEVEL         = 3
)

func IcmpNewConn(address string) (net.PacketConn, error) {
	// This is an attempt to work around the problem described here:
	// https://github.com/golang/go/issues/38427

	// First, get the correct local interface address, as SIO_RCVALL can't be set on a 0.0.0.0 listeners.
	dialedConn, err := net.Dial(Icmp(ipVer), address)
	if err != nil {
		return nil, err
	}
	localAddr := dialedConn.LocalAddr()
	dialedConn.Close()

	// Configure the setup routine in order to extract the socket handle.
	var socketHandle syscall.Handle
	cfg := net.ListenConfig{
		Control: func(network, address string, c syscall.RawConn) error {
			return c.Control(func(s uintptr) {
				socketHandle = syscall.Handle(s)
			})
		},
	}

	// Bind to interface.
	conn, err := cfg.ListenPacket(context.Background(), Icmp(ipVer), localAddr.String())
	if err != nil {
		return nil, err
	}

	// Set socket option to receive all packets, such as ICMP error messages.
	// This is somewhat dirty, as there is guarantee that socketHandle is still valid.
	// WARNING: The Windows Firewall might just drop the incoming packets you might want to receive.
	unused := uint32(0) // Documentation states that this is unused, but WSAIoctl fails without it.
	flag := uint32(RCVALL_IPLEVEL)
	size := uint32(unsafe.Sizeof(flag))
	err = syscall.WSAIoctl(socketHandle, SIO_RCVALL, (*byte)(unsafe.Pointer(&flag)), size, nil, 0, &unused, nil, 0)
	if err != nil {
		return nil, err
	}

	return conn, nil
}

func IsAdmin() bool {
	_, err := os.Open("\\\\.\\PHYSICALDRIVE0")
	if err != nil {
		ui.printDbg("Process is not running as admin. Error: %v", err)
		return false
	}
	return true
}
