// Copyright 2019 The GoPacket Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file in the root of the source
// tree.

// +build ignore

package main

// This file generates the godefs needed for the windows version.
// Rebuild is only necessary if additional libpcap functionality is implemented, or a new arch is implemented in golang.
// Call with go run generate_windows.go [-I includepath]
// Needs npcap sdk, go tool cgo, and gofmt to work. Location of npcap includes can be specified with -I

import (
	"bytes"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)

const header = `// Copyright 2019 The GoPacket Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file in the root of the source
// tree.

// This file contains necessary structs/constants generated from libpcap headers with cgo -godefs
// generated with: %s
// DO NOT MODIFY

`

const source = `
package pcap

//#include <pcap.h>
import "C"

import "syscall" // needed for RawSockaddr

const errorBufferSize = C.PCAP_ERRBUF_SIZE

const (
	pcapErrorNotActivated    = C.PCAP_ERROR_NOT_ACTIVATED
	pcapErrorActivated       = C.PCAP_ERROR_ACTIVATED
	pcapWarningPromisc       = C.PCAP_WARNING_PROMISC_NOTSUP
	pcapErrorNoSuchDevice    = C.PCAP_ERROR_NO_SUCH_DEVICE
	pcapErrorDenied          = C.PCAP_ERROR_PERM_DENIED
	pcapErrorNotUp           = C.PCAP_ERROR_IFACE_NOT_UP
	pcapError                = C.PCAP_ERROR
	pcapWarning              = C.PCAP_WARNING
	pcapDIN                  = C.PCAP_D_IN
	pcapDOUT                 = C.PCAP_D_OUT
	pcapDINOUT               = C.PCAP_D_INOUT
	pcapNetmaskUnknown       = C.PCAP_NETMASK_UNKNOWN
	pcapTstampPrecisionMicro = C.PCAP_TSTAMP_PRECISION_MICRO
	pcapTstampPrecisionNano  = C.PCAP_TSTAMP_PRECISION_NANO
)

type timeval C.struct_timeval
type pcapPkthdr C.struct_pcap_pkthdr
type pcapTPtr uintptr
type pcapBpfInstruction C.struct_bpf_insn
type pcapBpfProgram C.struct_bpf_program
type pcapStats C.struct_pcap_stat
type pcapCint C.int
type pcapIf C.struct_pcap_if
// +godefs map struct_sockaddr syscall.RawSockaddr
type pcapAddr C.struct_pcap_addr
`

var includes = flag.String("I", "C:\\npcap-sdk-1.01\\Include", "Include path containing libpcap headers")

func main() {
	flag.Parse()

	infile, err := ioutil.TempFile(".", "defs.*.go")
	if err != nil {
		log.Fatal("Couldn't create temporary source file: ", err)
	}
	defer infile.Close()
	defer os.Remove(infile.Name())

	_, err = infile.WriteString(source)
	if err != nil {
		log.Fatalf("Couldn't write definitions to temporary file %s: %s", infile.Name(), err)
	}
	err = infile.Close()
	if err != nil {
		log.Fatalf("Couldn't close temporary source file %s: %s", infile.Name(), err)
	}

	archs := []string{"386", "amd64"}
	for _, arch := range archs {
		env := append(os.Environ(), "GOARCH="+arch)
		cmd := exec.Command("go", "tool", "cgo", "-godefs", "--", "-I", *includes, infile.Name())
		cmd.Env = env
		cmd.Stderr = os.Stderr
		var generated bytes.Buffer
		cmd.Stdout = &generated
		err := cmd.Run()
		if err != nil {
			log.Fatalf("Couldn't generated defs for %s: %s\n", arch, err)
		}

		cmd = exec.Command("gofmt")
		cmd.Env = env
		cmd.Stderr = os.Stderr
		outName := fmt.Sprintf("defs_windows_%s.go", arch)
		out, err := os.Create(outName)
		if err != nil {
			log.Fatalf("Couldn't open file %s: %s", outName, err)
		}
		cmd.Stdout = out
		in, err := cmd.StdinPipe()
		if err != nil {
			log.Fatal("Couldn't create input pipe for gofmt: ", err)
		}
		err = cmd.Start()
		if err != nil {
			log.Fatal("Couldn't start gofmt: ", err)
		}

		_, err = fmt.Fprintf(in, header, strings.Join(append([]string{filepath.Base(os.Args[0])}, os.Args[1:]...), " "))
		if err != nil {
			log.Fatal("Couldn't write header to gofmt: ", err)
		}

		for {
			line, err := generated.ReadBytes('\n')
			if err != nil {
				break
			}
			// remove godefs comments
			if bytes.HasPrefix(line, []byte("//")) {
				continue
			}
			_, err = in.Write(line)
			if err != nil {
				log.Fatal("Couldn't write line to gofmt: ", err)
			}
		}
		in.Close()
		err = cmd.Wait()
		if err != nil {
			log.Fatal("gofmt failed: ", err)
		}
		out.Close()
	}
}
