package btf

import (
	"errors"
	"fmt"
	"strings"
)

var errNestedTooDeep = errors.New("nested too deep")

// GoFormatter converts a Type to Go syntax.
//
// A zero GoFormatter is valid to use.
type GoFormatter struct {
	w strings.Builder

	// Types present in this map are referred to using the given name if they
	// are encountered when outputting another type.
	Names map[Type]string

	// Identifier is called for each field of struct-like types. By default the
	// field name is used as is.
	Identifier func(string) string

	// EnumIdentifier is called for each element of an enum. By default the
	// name of the enum type is concatenated with Identifier(element).
	EnumIdentifier func(name, element string) string
}

// TypeDeclaration generates a Go type declaration for a BTF type.
func (gf *GoFormatter) TypeDeclaration(name string, typ Type) (string, error) {
	gf.w.Reset()
	if err := gf.writeTypeDecl(name, typ); err != nil {
		return "", err
	}
	return gf.w.String(), nil
}

func (gf *GoFormatter) identifier(s string) string {
	if gf.Identifier != nil {
		return gf.Identifier(s)
	}

	return s
}

func (gf *GoFormatter) enumIdentifier(name, element string) string {
	if gf.EnumIdentifier != nil {
		return gf.EnumIdentifier(name, element)
	}

	return name + gf.identifier(element)
}

// writeTypeDecl outputs a declaration of the given type.
//
// It encodes https://golang.org/ref/spec#Type_declarations:
//
//	type foo struct { bar uint32; }
//	type bar int32
func (gf *GoFormatter) writeTypeDecl(name string, typ Type) error {
	if name == "" {
		return fmt.Errorf("need a name for type %s", typ)
	}

	typ = skipQualifiers(typ)
	fmt.Fprintf(&gf.w, "type %s ", name)
	if err := gf.writeTypeLit(typ, 0); err != nil {
		return err
	}

	e, ok := typ.(*Enum)
	if !ok || len(e.Values) == 0 {
		return nil
	}

	gf.w.WriteString("; const ( ")
	for _, ev := range e.Values {
		id := gf.enumIdentifier(name, ev.Name)
		var value any
		if e.Signed {
			value = int64(ev.Value)
		} else {
			value = ev.Value
		}
		fmt.Fprintf(&gf.w, "%s %s = %d; ", id, name, value)
	}
	gf.w.WriteString(")")

	return nil
}

// writeType outputs the name of a named type or a literal describing the type.
//
// It encodes https://golang.org/ref/spec#Types.
//
//	foo                  (if foo is a named type)
//	uint32
func (gf *GoFormatter) writeType(typ Type, depth int) error {
	typ = skipQualifiers(typ)

	name := gf.Names[typ]
	if name != "" {
		gf.w.WriteString(name)
		return nil
	}

	return gf.writeTypeLit(typ, depth)
}

// writeTypeLit outputs a literal describing the type.
//
// The function ignores named types.
//
// It encodes https://golang.org/ref/spec#TypeLit.
//
//	struct { bar uint32; }
//	uint32
func (gf *GoFormatter) writeTypeLit(typ Type, depth int) error {
	depth++
	if depth > maxResolveDepth {
		return errNestedTooDeep
	}

	var err error
	switch v := skipQualifiers(typ).(type) {
	case *Int:
		err = gf.writeIntLit(v)

	case *Enum:
		if !v.Signed {
			gf.w.WriteRune('u')
		}
		switch v.Size {
		case 1:
			gf.w.WriteString("int8")
		case 2:
			gf.w.WriteString("int16")
		case 4:
			gf.w.WriteString("int32")
		case 8:
			gf.w.WriteString("int64")
		default:
			err = fmt.Errorf("invalid enum size %d", v.Size)
		}

	case *Typedef:
		err = gf.writeType(v.Type, depth)

	case *Array:
		fmt.Fprintf(&gf.w, "[%d]", v.Nelems)
		err = gf.writeType(v.Type, depth)

	case *Struct:
		err = gf.writeStructLit(v.Size, v.Members, depth)

	case *Union:
		// Always choose the first member to represent the union in Go.
		err = gf.writeStructLit(v.Size, v.Members[:1], depth)

	case *Datasec:
		err = gf.writeDatasecLit(v, depth)

	default:
		return fmt.Errorf("type %T: %w", v, ErrNotSupported)
	}

	if err != nil {
		return fmt.Errorf("%s: %w", typ, err)
	}

	return nil
}

func (gf *GoFormatter) writeIntLit(i *Int) error {
	bits := i.Size * 8
	switch i.Encoding {
	case Bool:
		if i.Size != 1 {
			return fmt.Errorf("bool with size %d", i.Size)
		}
		gf.w.WriteString("bool")
	case Char:
		if i.Size != 1 {
			return fmt.Errorf("char with size %d", i.Size)
		}
		// BTF doesn't have a way to specify the signedness of a char. Assume
		// we are dealing with unsigned, since this works nicely with []byte
		// in Go code.
		fallthrough
	case Unsigned, Signed:
		stem := "uint"
		if i.Encoding == Signed {
			stem = "int"
		}
		if i.Size > 8 {
			fmt.Fprintf(&gf.w, "[%d]byte /* %s%d */", i.Size, stem, i.Size*8)
		} else {
			fmt.Fprintf(&gf.w, "%s%d", stem, bits)
		}
	default:
		return fmt.Errorf("can't encode %s", i.Encoding)
	}
	return nil
}

func (gf *GoFormatter) writeStructLit(size uint32, members []Member, depth int) error {
	gf.w.WriteString("struct { ")

	prevOffset := uint32(0)
	skippedBitfield := false
	for i, m := range members {
		if m.BitfieldSize > 0 {
			skippedBitfield = true
			continue
		}

		offset := m.Offset.Bytes()
		if n := offset - prevOffset; skippedBitfield && n > 0 {
			fmt.Fprintf(&gf.w, "_ [%d]byte /* unsupported bitfield */; ", n)
		} else {
			gf.writePadding(n)
		}

		fieldSize, err := Sizeof(m.Type)
		if err != nil {
			return fmt.Errorf("field %d: %w", i, err)
		}

		prevOffset = offset + uint32(fieldSize)
		if prevOffset > size {
			return fmt.Errorf("field %d of size %d exceeds type size %d", i, fieldSize, size)
		}

		if err := gf.writeStructField(m, depth); err != nil {
			return fmt.Errorf("field %d: %w", i, err)
		}
	}

	gf.writePadding(size - prevOffset)
	gf.w.WriteString("}")
	return nil
}

func (gf *GoFormatter) writeStructField(m Member, depth int) error {
	if m.BitfieldSize > 0 {
		return fmt.Errorf("bitfields are not supported")
	}
	if m.Offset%8 != 0 {
		return fmt.Errorf("unsupported offset %d", m.Offset)
	}

	if m.Name == "" {
		// Special case a nested anonymous union like
		//     struct foo { union { int bar; int baz }; }
		// by replacing the whole union with its first member.
		union, ok := m.Type.(*Union)
		if !ok {
			return fmt.Errorf("anonymous fields are not supported")

		}

		if len(union.Members) == 0 {
			return errors.New("empty anonymous union")
		}

		depth++
		if depth > maxResolveDepth {
			return errNestedTooDeep
		}

		m := union.Members[0]
		size, err := Sizeof(m.Type)
		if err != nil {
			return err
		}

		if err := gf.writeStructField(m, depth); err != nil {
			return err
		}

		gf.writePadding(union.Size - uint32(size))
		return nil

	}

	fmt.Fprintf(&gf.w, "%s ", gf.identifier(m.Name))

	if err := gf.writeType(m.Type, depth); err != nil {
		return err
	}

	gf.w.WriteString("; ")
	return nil
}

func (gf *GoFormatter) writeDatasecLit(ds *Datasec, depth int) error {
	gf.w.WriteString("struct { ")

	prevOffset := uint32(0)
	for i, vsi := range ds.Vars {
		v, ok := vsi.Type.(*Var)
		if !ok {
			return fmt.Errorf("can't format %s as part of data section", vsi.Type)
		}

		if v.Linkage != GlobalVar {
			// Ignore static, extern, etc. for now.
			continue
		}

		if v.Name == "" {
			return fmt.Errorf("variable %d: empty name", i)
		}

		gf.writePadding(vsi.Offset - prevOffset)
		prevOffset = vsi.Offset + vsi.Size

		fmt.Fprintf(&gf.w, "%s ", gf.identifier(v.Name))

		if err := gf.writeType(v.Type, depth); err != nil {
			return fmt.Errorf("variable %d: %w", i, err)
		}

		gf.w.WriteString("; ")
	}

	gf.writePadding(ds.Size - prevOffset)
	gf.w.WriteString("}")
	return nil
}

func (gf *GoFormatter) writePadding(bytes uint32) {
	if bytes > 0 {
		fmt.Fprintf(&gf.w, "_ [%d]byte; ", bytes)
	}
}

func skipQualifiers(typ Type) Type {
	result := typ
	for depth := 0; depth <= maxResolveDepth; depth++ {
		switch v := (result).(type) {
		case qualifier:
			result = v.qualify()
		default:
			return result
		}
	}
	return &cycle{typ}
}
