//go:build ignore

package main

import (
	"bytes"
	"flag"
	"fmt"
	"go/ast"
	"log"
	"slices"
	"strings"

	"codeberg.org/miekg/dns/internal/generate"
)

var skip = []string{"NSEC", "NSEC3", "CSYNC"}

var hdr = `
// Code generated by "go run len_generate.go"; DO NOT EDIT.

package rdata

import (
	"encoding/base64"
	"net"
)

`

const out = "zlen.go"

func main() {
	flag.Parse()
	specs, err := generate.StructTypeSpecs("rdata.go")
	if err != nil {
		log.Fatalf("Failed to generate %s: %v", out, err)
	}
	b := &bytes.Buffer{}
	b.WriteString(hdr)

	for _, spec := range specs {
		rrname := spec.Name.Name
		if slices.Contains(skip, rrname) {
			continue
		}

		fmt.Fprintf(b, "func (rd %s) Len() int {\n", rrname)
		strct := spec.Type.(*ast.StructType)
		if generate.IsEmbedded(strct) {
			fmt.Fprintf(b, "return rd.%s.Len()\n}\n", strct.Fields.List[0].Type)
			continue
		}

		fmt.Fprintf(b, "l := 0\n")
		for _, field := range strct.Fields.List {
			if len(field.Names) == 0 {
				continue
			}

			fieldname := field.Names[0].String()

			// fieldtype is either slice or the actual singular type name.
			fieldtype := ""
			if id, ok := field.Type.(*ast.Ident); ok {
				fieldtype = id.Name
			}
			if _, ok := field.Type.(*ast.ArrayType); ok {
				fieldtype = "slice"
			}

			tag := ""
			if field.Tag != nil {
				tag = strings.Trim(field.Tag.Value, "`")
			}

			o := func(s string) { fmt.Fprintf(b, s, fieldname) }

			if fieldtype == "slice" {
				switch tag {
				case `dns:"-"`:
					// ignored
				case `dns:"cname"`:
					o("for i := range rd.%s { l += len(rd.%[1]s[i])+1 }\n")
				case `dns:"name"`:
					o("for i := range rd.%s { l += len(rd.%[1]s[i])+1 }\n")
				case `dns:"txt"`:
					o("for i := range rd.%s { l += len(rd.%[1]s[i]) + 1 }\n")
				case `dns:"pairs"`:
					o("for i := range rd.%s { l += rd.%[1]s[i].Len() }\n")
				case `dns:"infos"`:
					o("for i := range rd.%s { l += rd.%[1]s[i].Len() }\n")
				default:
					log.Fatalln(rrname, fieldname, tag)
				}
				continue
			}

			switch {
			case tag == `dns:"-"`:
				// ignored
			case tag == `dns:"cname"`:
				fallthrough
			case tag == `dns:"mname"`:
				fallthrough
			case tag == `dns:"name"`:
				o("l += len(rd.%s)+1\n")
			case strings.HasPrefix(tag, `dns:"size-base64`):
				fallthrough
			case tag == `dns:"base64"`:
				o("l += base64.StdEncoding.DecodedLen(len(rd.%s))\n")
			case strings.HasPrefix(tag, `dns:"size-hex:`): // this has an extra field where the length is stored
				o("l += len(rd.%s)/2\n")
			case tag == `dns:"hex"`:
				o("l += len(rd.%s)/2\n")
			case tag == `dns:"any"`:
				o("l += len(rd.%s)\n")
			case tag == `dns:"node"`:
				o("l += 8 // %s\n")
			case tag == `dns:"a"`:
				o("l += net.IPv4len // %s\n")
			case tag == `dns:"aaaa"`:
				o("l += net.IPv6len // %s\n")
			case tag == `dns:"uint48"`:
				o("l += 6 // %s\n")
			case tag == "":
				switch fieldtype {
				case "uint8":
					o("l++ // %s\n")
				case "uint16":
					o("l += 2 // %s\n")
				case "uint32":
					o("l += 4 // %s\n")
				case "uint64":
					o("l += 8 // %s\n")
				case "string":
					o("l += len(rd.%s) + 1\n")
				default:
					log.Fatalf("No tag or basic type: %s: %q, %s", rrname, fieldname, tag)
				}
			default:
				log.Fatalf("No tag or basic type: %s: %q, %s", rrname, fieldname, tag)
			}
		}
		fmt.Fprint(b, "return l }\n\n")
	}

	generate.Write(b, out)
}
