//go:build ignore
// +build ignore

// const_generate.go parses pkcs11t.h and generates zconst.go.
// zconst.go is meant to be checked into git.

package main

import (
	"bufio"
	"bytes"
	"fmt"
	"go/format"
	"log"
	"os"
	"strings"
)

func main() {
	file, err := os.Open("pkcs11t.h")
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()

	out := &bytes.Buffer{}
	fmt.Fprintf(out, header)

	scanner := bufio.NewScanner(file)
	fmt.Fprintln(out, "const (")
	comment := ""
	prevpre := ""
	count := 0
	for scanner.Scan() {
		// Fairly simple parsing, any line starting with '#define' will output
		// $2 = $3 and drop any UL (unsigned long) suffixes.
		// Some care is taken to add any comments and make the outputted file
		// have some decent godoc.
		fields := strings.Fields(scanner.Text())
		if len(fields) < 1 {
			continue
		}
		if fields[0] == "/*" || fields[0] == "*" {
			comment += "//" + scanner.Text()[2:]
			comment = strings.TrimSuffix(comment, "*/")
			comment += "\n"
			continue
		}

		if len(fields) < 3 {
			continue
		}

		if fields[0] != "#define" {
			comment = ""
			continue
		}

		if fields[1] == "_PKCS11T_H_" { // clear accumulated comments from the top of the file
			comment = ""
		}

		// fields[1] (const name) needs to be 3 chars, starting with CK
		if !strings.HasPrefix(fields[1], "CK") {
			continue
		}

		if x := fields[1][:3]; x != prevpre { // prefix change, insert a newline
			fmt.Fprintln(out)
			prevpre = x
		}

		var value string
		switch fields[1] {
		case "CK_TRUE":
			value = "true"
		case "CK_FALSE":
			value = "false"
		default:
			value = strings.TrimSuffix(fields[2], "UL")
			// special case for things like: (CKF_ARRAY_ATTRIBUTE|0x00000211UL)
			if strings.HasSuffix(value, "UL)") {
				value = strings.Replace(value, "UL)", ")", 1)
			}
			// CK_UNAVAILABLE_INFORMATION is encoded as (~0) (with UL) removed, this needs to be ^uint(0) in Go.
			// Special case that here.
			if value == "(~0)" {
				value = "^uint(0)"
			}
		}

		if comment != "" {
			fmt.Fprintln(out) // newline before comment
			fmt.Fprint(out, comment)
			comment = ""
		}

		// check for /* ... */ comments
		linecomment := ""
		if len(fields) >= 6 && fields[3] == "/*" {
			linecomment = "// " + strings.Join(fields[4:], " ")
			linecomment = strings.TrimSuffix(linecomment, "*/") // there is not always a space before */ so fields might not have all elements
		}

		fmt.Fprintln(out, fields[1], " = ", value, linecomment)
		count++
	}

	if err := scanner.Err(); err != nil {
		log.Fatal(err)
	}
	fmt.Fprintln(out, ")")
	res, err := format.Source(out.Bytes())
	if err != nil {
		fmt.Fprintf(os.Stderr, out.String())
		log.Fatal(err)
	}
	f, err := os.Create("zconst.go")
	if err != nil {
		log.Fatal(err)
	}
	f.Write(res)

	// Used to double check what we generate. This prints (for 2.40 spec):
	//
	// "2022/01/05 12:50:28 Wrote 756 constants to zconst.go"
	//
	// A grep confirms this correct:
	//
	// % grep '^#define CK' pkcs11t.h |wc
	//     756    2362   38807
	//
	// Also see const_test.go where we test this.
	log.Printf("Wrote %d constants to zconst.go", count)

}

const header = `// Copyright 2013 Miek Gieben. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Code generated by "go run const_generate.go"; DO NOT EDIT.

package pkcs11

`
