package typegen

import (
	"fmt"
	"io"
	"math/big"
	"reflect"
	"sort"
	"strconv"
	"strings"
	"text/template"

	cid "github.com/ipfs/go-cid"
)

const MaxLength = 8192

const ByteArrayMaxLen = 2 << 20

const MaxLenTag = "maxlen"
const NoUsrMaxLen = -1

var (
	cidType      = reflect.TypeOf(cid.Cid{})
	bigIntType   = reflect.TypeOf(big.Int{})
	deferredType = reflect.TypeOf(Deferred{})
)

// Gen is a configurable code generator for CBOR types. Use this instead of the convenience
// functions to have more control over the generated code.
type Gen struct {
	MaxArrayLength  int // Default: 8192 (MaxLength)
	MaxByteLength   int // Default: 2<<20 (ByteArrayMaxLen)
	MaxStringLength int // Default: 8192 (MaxLength)
}

func (g Gen) maxArrayLength() int {
	if g.MaxArrayLength == 0 {
		return MaxLength
	}
	return g.MaxArrayLength
}

func (g Gen) maxByteLength() int {
	if g.MaxByteLength == 0 {
		return ByteArrayMaxLen
	}
	return g.MaxByteLength
}

func (g Gen) maxStringLength() int {
	if g.MaxStringLength == 0 {
		return MaxLength
	}
	return g.MaxStringLength
}

func (g Gen) doTemplate(w io.Writer, info interface{}, templ string) error {
	t := template.Must(template.New("").
		Funcs(template.FuncMap{
			"MajorType": func(wname string, tname string, val string) string {
				return fmt.Sprintf(`if err := %s.WriteMajorTypeHeader(%s, uint64(%s)); err != nil {
	return err
}`, wname, tname, val)
			},
			"ReadHeader": func(rdr string) string {
				return fmt.Sprintf(`%s.ReadHeader()`, rdr)
			},
			"MaxLen": func(val int, defaultType string) string {
				if val <= 0 {
					switch defaultType {
					case "Bytes":
						val = g.maxByteLength()
					case "Array":
						val = g.maxArrayLength()
					case "String":
						val = g.maxStringLength()
					default:
						panic(fmt.Sprintf("error: unknown property [%s]", defaultType))
					}
				}
				return fmt.Sprintf("%d", val)
			},
			"Deref": func(sp *string) string {
				return *sp
			},
		}).Parse(templ))

	return t.Execute(w, info)
}

// PrintHeaderAndUtilityMethods is a convenience wrapper around Gen.PrintHeaderAndUtilityMethods
// using default options.
func PrintHeaderAndUtilityMethods(w io.Writer, pkg string, typeInfos []*GenTypeInfo) error {
	return Gen{}.PrintHeaderAndUtilityMethods(w, pkg, typeInfos)
}

func (g Gen) PrintHeaderAndUtilityMethods(w io.Writer, pkg string, typeInfos []*GenTypeInfo) error {
	var imports []Import
	for _, gti := range typeInfos {
		imports = append(imports, gti.Imports()...)
	}

	imports = append(imports, defaultImports...)
	imports = dedupImports(imports)

	data := struct {
		Package string
		Imports []Import
	}{pkg, imports}
	return g.doTemplate(w, data, `// Code generated by github.com/whyrusleeping/cbor-gen. DO NOT EDIT.

package {{ .Package }}

import (
	"fmt"
	"io"
	"math"
	"sort"

{{ range .Imports }}{{ .Name }} "{{ .PkgPath }}"
{{ end }}
)


var _ = xerrors.Errorf
var _ = cid.Undef
var _ = math.E
var _ = sort.Sort

`)
}

// FieldNameSelf is the name of the field that is the marshal target itself.
// This is used in non-struct types which are handled like transparent structs.
const FieldNameSelf = "."

type Field struct {
	Name    string
	MapKey  string
	Pointer bool
	Type    reflect.Type
	Pkg     string
	Const   *string

	OmitEmpty   bool
	PreserveNil bool
	IterLabel   string

	MaxLen int
}

func typeName(pkg string, t reflect.Type) string {
	switch t.Kind() {
	case reflect.Array:
		return fmt.Sprintf("[%d]%s", t.Len(), typeName(pkg, t.Elem()))
	case reflect.Slice:
		return "[]" + typeName(pkg, t.Elem())
	case reflect.Ptr:
		return "*" + typeName(pkg, t.Elem())
	case reflect.Map:
		return "map[" + typeName(pkg, t.Key()) + "]" + typeName(pkg, t.Elem())
	default:
		pkgPath := t.PkgPath()
		if pkgPath == "" {
			// It's a built-in.
			return t.String()
		} else if pkgPath == pkg {
			return t.Name()
		}
		return fmt.Sprintf("%s.%s", resolvePkgName(pkgPath, t.String()), t.Name())
	}
}

func (f Field) TypeName() string {
	return typeName(f.Pkg, f.Type)
}

func (f Field) ElemName() string {
	return typeName(f.Pkg, f.Type.Elem())
}

func (f Field) IsArray() bool {
	return f.Type.Kind() == reflect.Array
}

func (f Field) EmptyVal() (string, error) {
	return emptyValForField(f)
}

func (f Field) Len() int {
	return f.Type.Len()
}

type GenTypeInfo struct {
	Name        string
	Fields      []Field
	Transparent bool
}

func (gti *GenTypeInfo) Imports() []Import {
	var imports []Import
	for _, f := range gti.Fields {
		switch f.Type.Kind() {
		case reflect.Struct:
			if !f.Pointer && f.Type != bigIntType {
				continue
			}
			if f.Type == cidType {
				continue
			}
		case reflect.Bool:
			continue
		}
		imports = append(imports, ImportsForType(f.Pkg, f.Type)...)
	}
	return imports
}

func nameIsExported(name string) bool {
	return strings.ToUpper(name[0:1]) == name[0:1]
}

func ParseTypeInfo(itype interface{}) (*GenTypeInfo, error) {
	t := reflect.TypeOf(itype)

	pkg := t.PkgPath()

	out := GenTypeInfo{
		Name:        t.Name(),
		Transparent: false,
	}

	if t.Kind() != reflect.Struct {
		return &GenTypeInfo{
			Name:        t.Name(),
			Transparent: true,
			Fields: []Field{
				{
					Name:        FieldNameSelf,
					MapKey:      "",
					Pointer:     t.Kind() == reflect.Ptr,
					Type:        t,
					Pkg:         pkg,
					Const:       nil,
					OmitEmpty:   false,
					PreserveNil: false,
					IterLabel:   "",
					MaxLen:      NoUsrMaxLen,
				},
			},
		}, nil
	}

	for i := 0; i < t.NumField(); i++ {
		if out.Transparent {
			return nil, fmt.Errorf("transparent structs must have exactly one field")
		}

		f := t.Field(i)
		if !nameIsExported(f.Name) {
			continue
		}

		ft := f.Type
		var pointer bool
		if ft.Kind() == reflect.Ptr {
			ft = ft.Elem()
			pointer = true
		}

		mapk := f.Name
		usrMaxLen := NoUsrMaxLen
		tagval := f.Tag.Get("cborgen")
		tags, err := tagparse(tagval)
		if err != nil {
			return nil, fmt.Errorf("invalid tag format: %w", err)
		}

		if _, ok := tags["ignore"]; ok {
			continue
		}

		if tags["name"] != "" {
			mapk = tags["name"]
		}
		if msize := tags["maxlen"]; msize != "" {
			val, err := strconv.Atoi(msize)
			if err != nil {
				return nil, fmt.Errorf("maxsize tag value was not valid: %w", err)
			}

			usrMaxLen = val
		}

		var constval *string
		if cv, hasconst := tags["const"]; hasconst {
			if ft.Kind() != reflect.String {
				return nil, fmt.Errorf("const vals are only supported for string types")
			}
			constval = &cv
		}

		_, transparent := tags["transparent"]
		if transparent && len(out.Fields) > 0 {
			return nil, fmt.Errorf("only one transparent field is allowed")
		}
		out.Transparent = transparent

		_, omitempty := tags["omitempty"]
		_, preservenil := tags["preservenil"]

		if preservenil && ft.Kind() != reflect.Slice {
			return nil, fmt.Errorf("%T.%s: preservenil is only supported on slice types", itype, f.Name)
		}

		out.Fields = append(out.Fields, Field{
			Name:        f.Name,
			MapKey:      mapk,
			Pointer:     pointer,
			Type:        ft,
			Pkg:         pkg,
			OmitEmpty:   omitempty,
			PreserveNil: preservenil,
			MaxLen:      usrMaxLen,
			Const:       constval,
		})
	}

	return &out, nil
}

func tagparse(v string) (map[string]string, error) {
	out := make(map[string]string)
	for _, elem := range strings.Split(v, ",") {
		elem = strings.TrimSpace(elem)
		if elem == "" {
			continue
		}

		if strings.Contains(elem, "=") {
			parts := strings.Split(elem, "=")
			if len(parts) != 2 {
				return nil, fmt.Errorf("struct tags with params must be of form X=Y")
			}

			out[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
		} else if elem == "omitempty" {
			out["omitempty"] = "true"
		} else if elem == "preservenil" {
			out["preservenil"] = "true"
		} else if elem == "ignore" || elem == "-" {
			out["ignore"] = "true"
		} else if elem == "transparent" {
			out["transparent"] = "true"
		} else {
			out["name"] = elem
		}

	}

	return out, nil
}

func (gti GenTypeInfo) TupleHeader() []byte {
	return CborEncodeMajorType(MajArray, uint64(len(gti.Fields)))
}

func (gti GenTypeInfo) TupleHeaderAsByteString() string {
	return MakeByteString(gti.TupleHeader())
}

func MakeByteString(h []byte) string {
	s := "[]byte{"
	for _, b := range h {
		s += fmt.Sprintf("%d,", b)
	}
	s += "}"
	return s
}

func (gti GenTypeInfo) MapHeader() []byte {
	return CborEncodeMajorType(MajMap, uint64(len(gti.Fields)))
}

func (gti GenTypeInfo) MapHeaderAsByteString() string {
	h := gti.MapHeader()
	s := "[]byte{"
	for _, b := range h {
		s += fmt.Sprintf("%d,", b)
	}
	s += "}"
	return s
}

func (g Gen) emitCborMarshalStringField(w io.Writer, f Field) error {
	if f.Pointer {
		return g.doTemplate(w, f, `
	if {{ .Name }} == nil {
		if _, err := cw.Write(cbg.CborNull); err != nil {
			return err
		}
	} else {
		if len(*{{ .Name }}) > {{ MaxLen .MaxLen "String" }} {
			return xerrors.Errorf("Value in field {{ .Name | js }} was too long")
		}

		{{ MajorType "cw" "cbg.MajTextString" (print "len(*" .Name ")") }}
		if _, err := cw.WriteString(string(*{{ .Name }})); err != nil {
			return err
		}
	}
`)
	}

	if f.Const != nil {
		return g.doTemplate(w, f, `
	{{ MajorType "cw" "cbg.MajTextString" (print "len(\""  (Deref .Const)  "\")") }}
	if _, err := cw.WriteString(string("{{ .Const }}")); err != nil {
		return err
	}
`)

	}

	return g.doTemplate(w, f, `
	if len({{ .Name }}) > {{ MaxLen .MaxLen "String" }} {
		return xerrors.Errorf("Value in field {{ .Name | js }} was too long")
	}

	{{ MajorType "cw" "cbg.MajTextString" (print "len(" .Name ")") }}
	if _, err := cw.WriteString(string({{ .Name }})); err != nil {
		return err
	}
`)
}

func (g Gen) emitCborMarshalStructField(w io.Writer, f Field) error {
	switch f.Type {
	case bigIntType:
		return g.doTemplate(w, f, `
	{
		if err := cw.CborWriteHeader(cbg.MajTag, 2); err != nil {
			return err
		}
		var b []byte
		if {{ .Name }} != nil {
			b = {{ .Name }}.Bytes()
		}

		if err := cw.CborWriteHeader(cbg.MajByteString, uint64(len(b))); err != nil {
			return err
		}
		if _, err := cw.Write(b); err != nil {
			return err
		}
	}
`)

	case cidType:
		return g.doTemplate(w, f, `
{{ if .Pointer }}
	if {{ .Name }} == nil {
		if _, err := cw.Write(cbg.CborNull); err != nil {
			return err
		}
	} else {
		if err := cbg.WriteCid(cw, *{{ .Name }}); err != nil {
			return xerrors.Errorf("failed to write cid field {{ .Name }}: %w", err)
		}
	}
{{ else }}
	if err := cbg.WriteCid(cw, {{ .Name }}); err != nil {
		return xerrors.Errorf("failed to write cid field {{ .Name }}: %w", err)
	}
{{ end }}
`)
	default:
		return g.doTemplate(w, f, `
	if err := {{ .Name }}.MarshalCBOR(cw); err != nil {
		return err
	}
`)
	}
}

func (g Gen) emitCborMarshalUint64Field(w io.Writer, f Field) error {
	return g.doTemplate(w, f, `
{{ if .Pointer }}
	if {{ .Name }} == nil {
		if _, err := cw.Write(cbg.CborNull); err != nil {
			return err
		}
	} else {
		{{ MajorType "cw" "cbg.MajUnsignedInt" (print "*" .Name) }}
	}
{{ else }}
	{{ MajorType "cw" "cbg.MajUnsignedInt" .Name }}
{{ end }}
`)
}

func (g Gen) emitCborMarshalUint8Field(w io.Writer, f Field) error {
	if f.Pointer {
		return fmt.Errorf("pointers to integers not supported")
	}
	return g.doTemplate(w, f, `
{{ MajorType "cw" "cbg.MajUnsignedInt" .Name }}
`)
}

func (g Gen) emitCborMarshalInt64Field(w io.Writer, f Field) error {
	// if negative
	// val = -1 - cbor
	// cbor = -val -1
	return g.doTemplate(w, f, `{{ if .Pointer }}
	if {{ .Name }} == nil {
		if _, err := cw.Write(cbg.CborNull); err != nil {
			return err
		}
	} else {
		if {{ (print "*" .Name) }} >= 0 {
		{{ MajorType "cw" "cbg.MajUnsignedInt" (print "*" .Name) }}
		} else {
		{{ MajorType "cw" "cbg.MajNegativeInt" (print "-*" .Name "-1") }}
		}
	}
{{ else }}
	if {{ .Name }} >= 0 {
	{{ MajorType "cw" "cbg.MajUnsignedInt" .Name }}
	} else {
	{{ MajorType "cw" "cbg.MajNegativeInt" (print "-" .Name "-1") }}
	}
{{ end }}
`)
}

func (g Gen) emitCborMarshalBoolField(w io.Writer, f Field) error {
	if f.Pointer {
		return g.doTemplate(w, f, `
	if {{ .Name }} == nil {
		if _, err := cw.Write(cbg.CborNull); err != nil {
			return err
		}
	} else {
		if err := cbg.WriteBool(w, *{{ .Name }}); err != nil {
			return err
		}
	}
`)
	} else {
		return g.doTemplate(w, f, `
	if err := cbg.WriteBool(w, {{ .Name }}); err != nil {
		return err
	}
`)
	}
}

func (g Gen) emitCborMarshalMapField(w io.Writer, f Field) error {
	err := g.doTemplate(w, f, `
{
	if len({{ .Name }}) > 4096 {
		return xerrors.Errorf("cannot marshal {{ .Name }} map too large")
	}

	{{ MajorType "cw" "cbg.MajMap" (print "len(" .Name ")") }}

	keys := make([]string, 0, len({{ .Name }}))
	for k := range {{ .Name }} {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	for _, k := range keys {
		v := {{ .Name }}[k]

`)
	if err != nil {
		return err
	}

	// Map key
	switch f.Type.Key().Kind() {
	case reflect.String:
		if err := g.emitCborMarshalStringField(w, Field{Name: "k"}); err != nil {
			return err
		}
	default:
		return fmt.Errorf("non-string map keys are not yet supported")
	}

	// Map value
	switch f.Type.Elem().Kind() {
	case reflect.String:
		if err := g.emitCborMarshalStringField(w, Field{Name: "v"}); err != nil {
			return err
		}
	case reflect.Ptr:
		if f.Type.Elem().Elem().Kind() != reflect.Struct {
			return fmt.Errorf("unsupported map elem ptr type: %s", f.Type.Elem())
		}

		fallthrough
	case reflect.Struct:
		if err := g.emitCborMarshalStructField(w, Field{Name: "v", Type: f.Type.Elem(), Pkg: f.Pkg}); err != nil {
			return err
		}
	default:
		return fmt.Errorf("currently unsupported map elem type: %s", f.Type.Elem())
	}

	return g.doTemplate(w, f, `
	}
	}
`)
}

func (g Gen) emitCborMarshalSliceField(w io.Writer, f Field) error {
	if f.Pointer {
		return fmt.Errorf("pointers to slices not supported")
	}
	e := f.Type.Elem()

	if e.Kind() == reflect.Uint8 {
		return g.doTemplate(w, f, `
	if len({{ .Name }}) > {{ MaxLen .MaxLen "Bytes" }} {
		return xerrors.Errorf("Byte array in field {{ .Name }} was too long")
	}

	{{ if .PreserveNil }}
	if {{ .Name }} == nil {
		_, err := w.Write(cbg.CborNull)
		if err != nil {
			return err
		}
	} else {
	{{ end }}
		{{ MajorType "cw" "cbg.MajByteString" (print "len(" .Name ")" ) }}

		if _, err := cw.Write({{ .Name }}); err != nil {
			return err
		}
	{{ if .PreserveNil }}
	}
	{{ end }}
`)
	}

	var pointer bool
	if e.Kind() == reflect.Ptr {
		e = e.Elem()
		pointer = true
	}

	err := g.doTemplate(w, f, `
	if len({{ .Name }}) > {{ MaxLen .MaxLen "Array" }} {
		return xerrors.Errorf("Slice value in field {{ .Name }} was too long")
	}

	{{ if .PreserveNil }}
	if {{ .Name }} == nil {
		_, err := w.Write(cbg.CborNull)
		if err != nil {
			return err
		}
	} else {
	{{ end }}
		{{ MajorType "cw" "cbg.MajArray" ( print "len(" .Name ")" ) }}
		for _, v := range {{ .Name }} {`)
	if err != nil {
		return err
	}

	subf := Field{Name: "v", Type: e, Pkg: f.Pkg, Pointer: pointer}
	switch e.Kind() {
	default:
		err = fmt.Errorf("do not yet support slices of %s yet", e.Kind())
	case reflect.Struct:
		err = g.emitCborMarshalStructField(w, subf)
	case reflect.Uint64:
		err = g.emitCborMarshalUint64Field(w, subf)
	case reflect.Uint8:
		err = g.emitCborMarshalUint8Field(w, subf)
	case reflect.Int64:
		err = g.emitCborMarshalInt64Field(w, subf)
	case reflect.Slice:
		err = g.emitCborMarshalSliceField(w, subf)
	case reflect.String:
		err = g.emitCborMarshalStringField(w, subf)
	}
	if err != nil {
		return err
	}

	// array end
	if err := g.doTemplate(w, f, `
	{{ if .PreserveNil }}
		}
	{{ end }}
	}
`); err != nil {
		return err
	}

	return nil
}

func (g Gen) emitCborMarshalArrayField(w io.Writer, f Field) error {
	if f.Pointer {
		return fmt.Errorf("pointers to arrays not supported")
	}
	e := f.Type.Elem()

	// Note: this re-slices the slice to deal with arrays.
	if e.Kind() == reflect.Uint8 {
		return g.doTemplate(w, f, `
	if len({{ .Name }}) > {{ MaxLen .MaxLen "Bytes" }} {
		return xerrors.Errorf("Byte array in field {{ .Name }} was too long")
	}

	{{ MajorType "cw" "cbg.MajByteString" (print "len(" .Name ")" ) }}

	if _, err := cw.Write({{ .Name }}[:]); err != nil {
		return err
	}
`)
	}

	var pointer bool
	if e.Kind() == reflect.Ptr {
		e = e.Elem()
		pointer = true
	}

	err := g.doTemplate(w, f, `
	if len({{ .Name }}) > {{ MaxLen .MaxLen "Array" }} {
		return xerrors.Errorf("Slice value in field {{ .Name }} was too long")
	}

	{{ MajorType "cw" "cbg.MajArray" ( print "len(" .Name ")" ) }}
	for _, v := range {{ .Name }} {`)
	if err != nil {
		return err
	}

	subf := Field{Name: "v", Type: e, Pkg: f.Pkg, Pointer: pointer}
	switch e.Kind() {
	default:
		err = fmt.Errorf("do not yet support arrays of %s yet", e.Kind())
	case reflect.Struct:
		err = g.emitCborMarshalStructField(w, subf)
	case reflect.Uint64:
		err = g.emitCborMarshalUint64Field(w, subf)
	case reflect.Uint8:
		err = g.emitCborMarshalUint8Field(w, subf)
	case reflect.Int64:
		err = g.emitCborMarshalInt64Field(w, subf)
	case reflect.Slice:
		err = g.emitCborMarshalSliceField(w, subf)
	case reflect.String:
		err = g.emitCborMarshalStringField(w, subf)
	}
	if err != nil {
		return err
	}

	// array end
	fmt.Fprintf(w, "\t}\n")
	return nil
}

func (g Gen) emitCborMarshalStructTuple(w io.Writer, gti *GenTypeInfo) (err error) {
	// 9 byte buffer to accommodate for the maximum header length (cbor varints are maximum 9 bytes_
	if gti.Transparent {
		err = g.doTemplate(w, gti, `
func (t *{{ .Name }}) MarshalCBOR(w io.Writer) error {
	cw := cbg.NewCborWriter(w)
`)
	} else {
		err = g.doTemplate(w, gti, `var lengthBuf{{ .Name }} = {{ .TupleHeaderAsByteString }}
func (t *{{ .Name }}) MarshalCBOR(w io.Writer) error {
	if t == nil {
		_, err := w.Write(cbg.CborNull)
		return err
	}

	cw := cbg.NewCborWriter(w)

	if _, err := cw.Write(lengthBuf{{ .Name }}); err != nil {
		return err
	}

`)
	}
	if err != nil {
		return err
	}

	for _, f := range gti.Fields {
		if f.Name == FieldNameSelf {
			f.Name = "(*t)"
		} else {
			f.Name = "t." + f.Name
		}
		fmt.Fprintf(w, "\n\t// %s (%s) (%s)", f.Name, f.Type, f.Type.Kind())

		switch f.Type.Kind() {
		case reflect.String:
			if err := g.emitCborMarshalStringField(w, f); err != nil {
				return err
			}
		case reflect.Struct:
			if err := g.emitCborMarshalStructField(w, f); err != nil {
				return err
			}
		case reflect.Uint64:
			if err := g.emitCborMarshalUint64Field(w, f); err != nil {
				return err
			}
		case reflect.Uint8:
			if err := g.emitCborMarshalUint8Field(w, f); err != nil {
				return err
			}
		case reflect.Int64:
			if err := g.emitCborMarshalInt64Field(w, f); err != nil {
				return err
			}
		case reflect.Array:
			if err := g.emitCborMarshalArrayField(w, f); err != nil {
				return err
			}
		case reflect.Slice:
			if err := g.emitCborMarshalSliceField(w, f); err != nil {
				return err
			}
		case reflect.Bool:
			if err := g.emitCborMarshalBoolField(w, f); err != nil {
				return err
			}
		case reflect.Map:
			if err := g.emitCborMarshalMapField(w, f); err != nil {
				return err
			}
		default:
			return fmt.Errorf("field %q of %q has unsupported kind %q", f.Name, gti.Name, f.Type.Kind())
		}
	}

	fmt.Fprintf(w, "\treturn nil\n}\n\n")
	return nil
}

func (g Gen) emitCborUnmarshalStringField(w io.Writer, f Field) error {
	if f.Pointer {
		return g.doTemplate(w, f, `
	{
		b, err := cr.ReadByte()
		if err != nil {
			return err
		}
		if b != cbg.CborNull[0] {
			if err := cr.UnreadByte(); err != nil {
				return err
			}

			sval, err := cbg.ReadStringWithMax(cr, {{ MaxLen 0 "String" }})
			if err != nil {
				return err
			}

			{{ .Name }} = (*{{ .TypeName }})(&sval)
		}
	}
`)
	}
	if f.Type == nil {
		f.Type = reflect.TypeOf("")
	}
	return g.doTemplate(w, f, `
	{
		sval, err := cbg.ReadStringWithMax(cr, {{  MaxLen 0 "String" }})
		if err != nil {
			return err
		}

		{{ .Name }} = {{ .TypeName }}(sval)
	}
`)
}

func (g Gen) emitCborUnmarshalStructField(w io.Writer, f Field) error {
	switch f.Type {
	case bigIntType:
		return g.doTemplate(w, f, `
	maj, extra, err = {{ ReadHeader "cr" }}
	if err != nil {
		return err
	}

	if maj != cbg.MajTag || extra != 2 {
		return fmt.Errorf("big ints should be cbor bignums")
	}

	maj, extra, err = {{ ReadHeader "cr" }}
	if err != nil {
		return err
	}

	if maj != cbg.MajByteString {
		return fmt.Errorf("big ints should be tagged cbor byte strings")
	}

	if extra > 256 {
		return fmt.Errorf("{{ .Name }}: cbor bignum was too large")
	}

	if extra > 0 {
		buf := make([]byte, extra)
		if _, err := io.ReadFull(cr, buf); err != nil {
			return err
		}
		{{ .Name }} = big.NewInt(0).SetBytes(buf)
	} else {
		{{ .Name }} = big.NewInt(0)
	}
`)
	case cidType:
		return g.doTemplate(w, f, `
	{
{{ if .Pointer }}
		b, err := cr.ReadByte()
		if err != nil {
			return err
		}
		if b != cbg.CborNull[0] {
			if err := cr.UnreadByte(); err != nil {
				return err
			}
{{ end }}
		c, err := cbg.ReadCid(cr)
		if err != nil {
			return xerrors.Errorf("failed to read cid field {{ .Name }}: %w", err)
		}
{{ if .Pointer }}
			{{ .Name }} = &c
		}
{{ else }}
		{{ .Name }} = c
{{ end }}
	}
`)
	case deferredType:
		return g.doTemplate(w, f, `
	{
{{ if .Pointer }}
		{{ .Name }} = new(cbg.Deferred)
{{ end }}
		if err := {{ .Name }}.UnmarshalCBOR(cr); err != nil {
			return xerrors.Errorf("failed to read deferred field: %w", err)
		}
	}
`)

	default:
		return g.doTemplate(w, f, `
	{
{{ if .Pointer }}
		b, err := cr.ReadByte()
		if err != nil {
			return err
		}
		if b != cbg.CborNull[0] {
			if err := cr.UnreadByte(); err != nil {
				return err
			}
			{{ .Name }} = new({{ .TypeName }})
			if err := {{ .Name }}.UnmarshalCBOR(cr); err != nil {
				return xerrors.Errorf("unmarshaling {{ .Name }} pointer: %w", err)
			}
		}
{{ else }}
		if err := {{ .Name }}.UnmarshalCBOR(cr); err != nil {
			return xerrors.Errorf("unmarshaling {{ .Name }}: %w", err)
		}
{{ end }}
	}
`)
	}
}

func (g Gen) emitCborUnmarshalInt64Field(w io.Writer, f Field) error {
	return g.doTemplate(w, f, `{
	{{ if .Pointer }}
	b, err := cr.ReadByte()
	if err != nil {
		return err
	}
	if b != cbg.CborNull[0] {
		if err := cr.UnreadByte(); err != nil {
			return err
		}
{{ end }}maj, extra, err := {{ ReadHeader "cr" }}
		if err != nil {
			return err
		}
		var extraI int64
		switch maj {
		case cbg.MajUnsignedInt:
			extraI = int64(extra)
			if extraI < 0 {
				return fmt.Errorf("int64 positive overflow")
			}
		case cbg.MajNegativeInt:
			extraI = int64(extra)
			if extraI < 0 {
				return fmt.Errorf("int64 negative overflow")
			}
			extraI = -1 - extraI
		default:
			return fmt.Errorf("wrong type for int64 field: %d", maj)
		}

{{ if .Pointer }}
		{{ .Name }} = (*{{ .TypeName }})(&extraI)
}
{{ else }}
		{{ .Name }} = {{ .TypeName }}(extraI)
{{ end }}}
`)
}

func (g Gen) emitCborUnmarshalUint64Field(w io.Writer, f Field) error {
	return g.doTemplate(w, f, `
	{
{{ if .Pointer }}
	b, err := cr.ReadByte()
	if err != nil {
		return err
	}
	if b != cbg.CborNull[0] {
		if err := cr.UnreadByte(); err != nil {
			return err
		}
		maj, extra, err = {{ ReadHeader "cr" }}
		if err != nil {
			return err
		}
		if maj != cbg.MajUnsignedInt {
			return fmt.Errorf("wrong type for uint64 field")
		}
		typed := {{ .TypeName }}(extra)
		{{ .Name }} = &typed
	}
{{ else }}
	maj, extra, err = {{ ReadHeader "cr" }}
	if err != nil {
		return err
	}
	if maj != cbg.MajUnsignedInt {
		return fmt.Errorf("wrong type for uint64 field")
	}
	{{ .Name }} = {{ .TypeName }}(extra)
{{ end }}
	}
`)
}

func (g Gen) emitCborUnmarshalUint8Field(w io.Writer, f Field) error {
	return g.doTemplate(w, f, `
	maj, extra, err = {{ ReadHeader "cr" }}
	if err != nil {
		return err
	}
	if maj != cbg.MajUnsignedInt {
		return fmt.Errorf("wrong type for uint8 field")
	}
	if extra > math.MaxUint8 {
		return fmt.Errorf("integer in input was too large for uint8 field")
	}
	{{ .Name }} = {{ .TypeName }}(extra)
`)
}

func (g Gen) emitCborUnmarshalBoolField(w io.Writer, f Field) error {
	if f.Pointer {
		return g.doTemplate(w, f, `
	{
		b, err := cr.ReadByte()
		if err != nil {
			return err
		}
		if b != cbg.CborNull[0] {
			if err := cr.UnreadByte(); err != nil {
				return err
			}

			maj, extra, err = {{ ReadHeader "cr" }}
			if err != nil {
				return err
			}
			if maj != cbg.MajOther {
				return fmt.Errorf("booleans must be major type 7")
			}

			var val bool
			switch extra {
			case 20:
				val = false
			case 21:
				val = true
			default:
				return fmt.Errorf("booleans are either major type 7, value 20 or 21 (got %d)", extra)
			}
			{{ .Name }} = &val
		}
	}
`)
	} else {
		return g.doTemplate(w, f, `
	maj, extra, err = {{ ReadHeader "cr" }}
	if err != nil {
		return err
	}
	if maj != cbg.MajOther {
		return fmt.Errorf("booleans must be major type 7")
	}
	switch extra {
	case 20:
		{{ .Name }} = false
	case 21:
		{{ .Name }} = true
	default:
		return fmt.Errorf("booleans are either major type 7, value 20 or 21 (got %d)", extra)
	}
`)
	}
}

func (g Gen) emitCborUnmarshalMapField(w io.Writer, f Field) error {
	err := g.doTemplate(w, f, `
	maj, extra, err = {{ ReadHeader "cr" }}
	if err != nil {
		return err
	}
	if maj != cbg.MajMap {
		return fmt.Errorf("expected a map (major type 5)")
	}
	if extra > 4096 {
		return fmt.Errorf("{{ .Name }}: map too large")
	}

	{{ .Name }} = make({{ .TypeName }}, extra)


	for i, l := 0, int(extra); i < l; i++ {
`)
	if err != nil {
		return err
	}

	switch f.Type.Key().Kind() {
	case reflect.String:
		if err := g.doTemplate(w, f, `
	var k string
`); err != nil {
			return err
		}
		if err := g.emitCborUnmarshalStringField(w, Field{Name: "k"}); err != nil {
			return err
		}
	default:
		return fmt.Errorf("maps with non-string keys are not yet supported")
	}

	var pointer bool
	t := f.Type.Elem()
	switch t.Kind() {
	case reflect.String:
		if err := g.doTemplate(w, f, `
	var v string
`); err != nil {
			return err
		}
		if err := g.emitCborUnmarshalStringField(w, Field{Name: "v"}); err != nil {
			return err
		}
		if err := g.doTemplate(w, f, `
	{{ .Name }}[k] = v
`); err != nil {
			return err
		}
	case reflect.Ptr:
		if t.Elem().Kind() != reflect.Struct {
			return fmt.Errorf("unsupported map elem ptr type: %s", t)
		}

		pointer = true
		fallthrough
	case reflect.Struct:
		subf := Field{Name: "v", Pointer: pointer, Type: t, Pkg: f.Pkg}
		if err := g.doTemplate(w, subf, `
	var v {{ .TypeName }}
`); err != nil {
			return err
		}

		if pointer {
			subf.Type = subf.Type.Elem()
		}
		if err := g.emitCborUnmarshalStructField(w, subf); err != nil {
			return err
		}
		if err := g.doTemplate(w, f, `
	{{ .Name }}[k] = v
`); err != nil {
			return err
		}
	default:
		return fmt.Errorf("currently only support maps of structs")
	}

	return g.doTemplate(w, f, `
	}
`)
}

func (g Gen) emitCborUnmarshalSliceField(w io.Writer, f Field) error {
	if f.IterLabel == "" {
		f.IterLabel = "i"
	}

	e := f.Type.Elem()
	var pointer bool
	if e.Kind() == reflect.Ptr {
		pointer = true
		e = e.Elem()
	}

	err := g.doTemplate(w, f, `
	{{ if .PreserveNil }}
	{
		b, err := cr.ReadByte()
		if err != nil {
			return err
		}
		if b != cbg.CborNull[0] {
			if err := cr.UnreadByte(); err != nil {
				return err
			}

	{{ end }}
			maj, extra, err = {{ ReadHeader "cr" }}
			if err != nil {
				return err
			}
`)
	if err != nil {
		return err
	}

	if e.Kind() == reflect.Uint8 {
		return g.doTemplate(w, f, `
			if extra > {{ MaxLen .MaxLen "Bytes" }} {
				return fmt.Errorf("{{ .Name }}: byte array too large (%d)", extra)
			}
			if maj != cbg.MajByteString {
				return fmt.Errorf("expected byte array")
			}
	{{ if .PreserveNil }}
			{{ .Name }} = make({{ .TypeName }}, extra)
	{{ else }}
			if extra > 0 {
				{{ .Name }} = make({{ .TypeName }}, extra)
			}
	{{ end }}
			if _, err := io.ReadFull(cr, {{ .Name }}); err != nil {
				return err
			}

	{{ if .PreserveNil }}
		}
	}
	{{ end }}
`)
	}

	if err := g.doTemplate(w, f, `
	if extra > {{ MaxLen .MaxLen "Array" }} {
		return fmt.Errorf("{{ .Name }}: array too large (%d)", extra)
	}
`); err != nil {
		return err
	}

	err = g.doTemplate(w, f, `
	if maj != cbg.MajArray {
		return fmt.Errorf("expected cbor array")
	}
	{{ if .PreserveNil }}
	{{ .Name }} = make({{ .TypeName }}, extra)
	{{ else }}
	if extra > 0 {
		{{ .Name }} = make({{ .TypeName }}, extra)
	}
	{{ end }}
	for {{ .IterLabel }} := 0; {{ .IterLabel }} < int(extra); {{ .IterLabel }}++ {
`)
	if err != nil {
		return err
	}

	fmt.Fprintf(w, "\t\t{\n\t\t\tvar maj byte\n\t\tvar extra uint64\n\t\tvar err error\n")
	fmt.Fprintf(w, "\t\t\t_ = maj\n\t\t_ = extra\n\t\t_ = err\n")

	switch e.Kind() {
	case reflect.Struct:
		subf := Field{
			Type:    e,
			Pkg:     f.Pkg,
			Pointer: pointer,
			Name:    f.Name + "[" + f.IterLabel + "]",
		}
		err := g.emitCborUnmarshalStructField(w, subf)
		if err != nil {
			return err
		}
	case reflect.Uint64:
		subf := Field{
			Type: e,
			Pkg:  f.Pkg,
			Name: f.Name + "[" + f.IterLabel + "]",
		}
		err := g.emitCborUnmarshalUint64Field(w, subf)
		if err != nil {
			return err
		}
	case reflect.Int64:
		subf := Field{
			Type: e,
			Pkg:  f.Pkg,
			Name: f.Name + "[" + f.IterLabel + "]",
		}
		err := g.emitCborUnmarshalInt64Field(w, subf)
		if err != nil {
			return err
		}
	case reflect.Array:
		nextIter := string([]byte{f.IterLabel[0] + 1})
		subf := Field{
			Name:      fmt.Sprintf("%s[%s]", f.Name, f.IterLabel),
			Type:      e,
			IterLabel: nextIter,
			Pkg:       f.Pkg,
		}
		if err := g.emitCborUnmarshalArrayField(w, subf); err != nil {
			return err
		}
	case reflect.Slice:
		nextIter := string([]byte{f.IterLabel[0] + 1})
		subf := Field{
			Name:      fmt.Sprintf("%s[%s]", f.Name, f.IterLabel),
			Type:      e,
			IterLabel: nextIter,
			Pkg:       f.Pkg,
		}
		if err := g.emitCborUnmarshalSliceField(w, subf); err != nil {
			return err
		}

	case reflect.String:
		subf := Field{
			Type: e,
			Pkg:  f.Pkg,
			Name: f.Name + "[" + f.IterLabel + "]",
		}
		err := g.emitCborUnmarshalStringField(w, subf)
		if err != nil {
			return err
		}

	default:
		return fmt.Errorf("do not yet support slices of %s yet", e.Elem())
	}

	if err := g.doTemplate(w, f, `
	{{ if .PreserveNil }}
				}
			}
	{{ end }}
		}
	}
	`); err != nil {
		return err
	}

	return nil
}

func (g Gen) emitCborUnmarshalArrayField(w io.Writer, f Field) error {
	if f.IterLabel == "" {
		f.IterLabel = "i"
	}

	e := f.Type.Elem()
	var pointer bool
	if e.Kind() == reflect.Ptr {
		pointer = true
		e = e.Elem()
	}

	err := g.doTemplate(w, f, `
	maj, extra, err = {{ ReadHeader "cr" }}
	if err != nil {
		return err
	}
`)
	if err != nil {
		return err
	}

	if e.Kind() == reflect.Uint8 {
		return g.doTemplate(w, f, `
	if extra > {{ MaxLen .MaxLen "Bytes" }} {
		return fmt.Errorf("{{ .Name }}: byte array too large (%d)", extra)
	}
	if maj != cbg.MajByteString {
		return fmt.Errorf("expected byte array")
	}
	if extra != {{ .Len }} {
		return fmt.Errorf("expected array to have {{ .Len }} elements")
	}

	{{ .Name }} = {{ .TypeName }}{}
	if _, err := io.ReadFull(cr, {{ .Name }}[:]); err != nil {
		return err
	}
`)
	}

	if err := g.doTemplate(w, f, `
	if extra > {{ MaxLen .MaxLen "Array" }} {
		return fmt.Errorf("{{ .Name }}: array too large (%d)", extra)
	}
`); err != nil {
		return err
	}

	err = g.doTemplate(w, f, `
	if maj != cbg.MajArray {
		return fmt.Errorf("expected cbor array")
	}
	if extra != {{ .Len }} {
		return fmt.Errorf("expected array to have {{ .Len }} elements")
	}

	{{ .Name }} = {{ .TypeName }}{}
	for {{ .IterLabel }} := 0; {{ .IterLabel }} < int(extra); {{ .IterLabel }}++ {
`)
	if err != nil {
		return err
	}

	fmt.Fprintf(w, "\t\t{\n\t\t\tvar maj byte\n\t\tvar extra uint64\n\t\tvar err error\n")
	fmt.Fprintf(w, "\t\t\t_ = maj\n\t\t_ = extra\n\t\t_ = err\n")

	switch e.Kind() {
	case reflect.Struct:
		subf := Field{
			Type:    e,
			Pkg:     f.Pkg,
			Pointer: pointer,
			Name:    f.Name + "[" + f.IterLabel + "]",
		}
		err := g.emitCborUnmarshalStructField(w, subf)
		if err != nil {
			return err
		}
	case reflect.Uint64:
		subf := Field{
			Type: e,
			Pkg:  f.Pkg,
			Name: f.Name + "[" + f.IterLabel + "]",
		}
		err := g.emitCborUnmarshalUint64Field(w, subf)
		if err != nil {
			return err
		}
	case reflect.Int64:
		subf := Field{
			Type: e,
			Pkg:  f.Pkg,
			Name: f.Name + "[" + f.IterLabel + "]",
		}
		err := g.emitCborUnmarshalInt64Field(w, subf)
		if err != nil {
			return err
		}
	case reflect.Array:
		nextIter := string([]byte{f.IterLabel[0] + 1})
		subf := Field{
			Name:      fmt.Sprintf("%s[%s]", f.Name, f.IterLabel),
			Type:      e,
			IterLabel: nextIter,
			Pkg:       f.Pkg,
		}
		if err := g.emitCborUnmarshalArrayField(w, subf); err != nil {
			return err
		}
	case reflect.Slice:
		nextIter := string([]byte{f.IterLabel[0] + 1})
		subf := Field{
			Name:      fmt.Sprintf("%s[%s]", f.Name, f.IterLabel),
			Type:      e,
			IterLabel: nextIter,
			Pkg:       f.Pkg,
		}
		if err := g.emitCborUnmarshalSliceField(w, subf); err != nil {
			return err
		}

	case reflect.String:
		subf := Field{
			Type: e,
			Pkg:  f.Pkg,
			Name: f.Name + "[" + f.IterLabel + "]",
		}
		err := g.emitCborUnmarshalStringField(w, subf)
		if err != nil {
			return err
		}

	default:
		return fmt.Errorf("do not yet support slices of %s yet", e.Elem())
	}
	fmt.Fprintf(w, "\t\t}\n")
	fmt.Fprintf(w, "\t}\n\n")

	return nil
}

func (g Gen) emitCborUnmarshalStructTuple(w io.Writer, gti *GenTypeInfo) (err error) {
	if gti.Transparent {
		err = g.doTemplate(w, gti, `
func (t *{{ .Name}}) UnmarshalCBOR(r io.Reader) (err error) {
	*t = {{.Name}}{}

	cr := cbg.NewCborReader(r)
	var maj byte
	var extra uint64
	_ = maj
	_ = extra
`)
	} else {
		err = g.doTemplate(w, gti, `
func (t *{{ .Name}}) UnmarshalCBOR(r io.Reader) (err error) {
	*t = {{.Name}}{}

	cr := cbg.NewCborReader(r)

	maj, extra, err := {{ ReadHeader "cr" }}
	if err != nil {
		return err
	}
	defer func() {
		if err == io.EOF {
			err = io.ErrUnexpectedEOF
		}
	}()

	if maj != cbg.MajArray {
		return fmt.Errorf("cbor input should be of type array")
	}

	if extra != {{ len .Fields }} {
		return fmt.Errorf("cbor input had wrong number of fields")
	}

`)
	}
	if err != nil {
		return err
	}

	for _, f := range gti.Fields {
		if f.Name == FieldNameSelf {
			f.Name = "(*t)" // self
		} else {
			f.Name = "t." + f.Name
		}
		fmt.Fprintf(w, "\t// %s (%s) (%s)\n", f.Name, f.Type, f.Type.Kind())

		switch f.Type.Kind() {
		case reflect.String:
			if err := g.emitCborUnmarshalStringField(w, f); err != nil {
				return err
			}
		case reflect.Struct:
			if err := g.emitCborUnmarshalStructField(w, f); err != nil {
				return err
			}
		case reflect.Uint64:
			if err := g.emitCborUnmarshalUint64Field(w, f); err != nil {
				return err
			}
		case reflect.Uint8:
			if err := g.emitCborUnmarshalUint8Field(w, f); err != nil {
				return err
			}
		case reflect.Int64:
			if err := g.emitCborUnmarshalInt64Field(w, f); err != nil {
				return err
			}
		case reflect.Array:
			if err := g.emitCborUnmarshalArrayField(w, f); err != nil {
				return err
			}
		case reflect.Slice:
			if err := g.emitCborUnmarshalSliceField(w, f); err != nil {
				return err
			}
		case reflect.Bool:
			if err := g.emitCborUnmarshalBoolField(w, f); err != nil {
				return err
			}
		case reflect.Map:
			if err := g.emitCborUnmarshalMapField(w, f); err != nil {
				return err
			}
		default:
			return fmt.Errorf("field %q of %q has unsupported kind %q", f.Name, gti.Name, f.Type.Kind())
		}
	}

	fmt.Fprintf(w, "\treturn nil\n}\n\n")

	return nil
}

// GenTupleEncodersForType is a convenience wrapper around Gen.GenTupleEncodersForType using
// default options.
func GenTupleEncodersForType(gti *GenTypeInfo, w io.Writer) error {
	return Gen{}.GenTupleEncodersForType(gti, w)
}

// Generates 'tuple representation' cbor encoders for the given type
func (g Gen) GenTupleEncodersForType(gti *GenTypeInfo, w io.Writer) error {
	if err := g.emitCborMarshalStructTuple(w, gti); err != nil {
		return err
	}

	if err := g.emitCborUnmarshalStructTuple(w, gti); err != nil {
		return err
	}

	return nil
}

func emptyValForField(f Field) (string, error) {
	if f.Pointer {
		return "nil", nil
	} else {
		switch f.Type.Kind() {
		case reflect.String:
			return "\"\"", nil
		case reflect.Slice:
			return "nil", nil
		default:
			return "", fmt.Errorf("omit empty not supported for %s", f.Type.Kind())
		}
	}
}

func (g Gen) emitCborMarshalStructMap(w io.Writer, gti *GenTypeInfo) error {
	var hasOmitEmpty bool
	for _, f := range gti.Fields {
		if f.OmitEmpty {
			hasOmitEmpty = true
		}
	}

	if gti.Transparent {
		return fmt.Errorf("transparent fields not supported in map mode, use tuple encoding (outcome should be the same)")
	}

	err := g.doTemplate(w, gti, `func (t *{{ .Name }}) MarshalCBOR(w io.Writer) error {
	if t == nil {
		_, err := w.Write(cbg.CborNull)
		return err
	}

	cw := cbg.NewCborWriter(w)
`)
	if err != nil {
		return err
	}

	if hasOmitEmpty {
		fmt.Fprintf(w, "fieldCount := %d\n", len(gti.Fields))
		for _, f := range gti.Fields {
			if f.OmitEmpty {
				err = g.doTemplate(w, f, `
	if t.{{ .Name }} == {{ .EmptyVal }} {
		fieldCount--
	}
`)
				if err != nil {
					return err
				}
			}
		}

		fmt.Fprintf(w, `
	if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil {
		return err
	}
`)
		if err != nil {
			return err
		}

	} else {

		err = g.doTemplate(w, gti, `
	if _, err := cw.Write({{ .MapHeaderAsByteString }}); err != nil {
		return err
	}
`)
		if err != nil {
			return err
		}
	}

	sort.Slice(gti.Fields, func(i, j int) bool {
		fi := gti.Fields[i]
		fj := gti.Fields[j]

		if len(fi.MapKey) < len(fj.MapKey) {
			return true
		}
		if len(fi.MapKey) > len(fj.MapKey) {
			return false
		}

		// TODO: is this properly canonical?
		return fi.MapKey < fj.MapKey
	})

	for _, f := range gti.Fields {
		fmt.Fprintf(w, "\n\t// t.%s (%s) (%s)", f.Name, f.Type, f.Type.Kind())

		if f.OmitEmpty {
			if err := g.doTemplate(w, f, "\nif t.{{.Name}} != {{ .EmptyVal }} {\n"); err != nil {
				return err
			}
		}

		if err := g.emitCborMarshalStringField(w, Field{
			Name: `"` + f.MapKey + `"`,
		}); err != nil {
			return err
		}

		f.Name = "t." + f.Name
		switch f.Type.Kind() {
		case reflect.String:
			if err := g.emitCborMarshalStringField(w, f); err != nil {
				return err
			}
		case reflect.Struct:
			if err := g.emitCborMarshalStructField(w, f); err != nil {
				return err
			}
		case reflect.Uint64:
			if err := g.emitCborMarshalUint64Field(w, f); err != nil {
				return err
			}
		case reflect.Int64:
			if err := g.emitCborMarshalInt64Field(w, f); err != nil {
				return err
			}
		case reflect.Uint8:
			if err := g.emitCborMarshalUint8Field(w, f); err != nil {
				return err
			}
		case reflect.Array:
			if err := g.emitCborMarshalArrayField(w, f); err != nil {
				return err
			}
		case reflect.Slice:
			if err := g.emitCborMarshalSliceField(w, f); err != nil {
				return err
			}
		case reflect.Bool:
			if err := g.emitCborMarshalBoolField(w, f); err != nil {
				return err
			}
		case reflect.Map:
			if err := g.emitCborMarshalMapField(w, f); err != nil {
				return err
			}
		default:
			return fmt.Errorf("field %q of %q has unsupported kind %q", f.Name, gti.Name, f.Type.Kind())
		}

		if f.OmitEmpty {
			if err := g.doTemplate(w, f, "}\n"); err != nil {
				return err
			}
		}
	}

	fmt.Fprintf(w, "\treturn nil\n}\n\n")
	return nil
}

func (g Gen) emitCborUnmarshalStructMap(w io.Writer, gti *GenTypeInfo) error {
	err := g.doTemplate(w, gti, `
func (t *{{ .Name}}) UnmarshalCBOR(r io.Reader) (err error) {
	*t = {{.Name}}{}

	cr := cbg.NewCborReader(r)

	maj, extra, err := {{ ReadHeader "cr" }}
	if err != nil {
		return err
	}
	defer func() {
		if err == io.EOF {
			err = io.ErrUnexpectedEOF
		}
	}()

	if maj != cbg.MajMap {
		return fmt.Errorf("cbor input should be of type map")
	}

	if extra > cbg.MaxLength {
		return fmt.Errorf("{{ .Name }}: map struct too large (%d)", extra)
	}

	var name string
	n := extra

	for i := uint64(0); i < n; i++ {
`)
	if err != nil {
		return err
	}

	if err := g.emitCborUnmarshalStringField(w, Field{Name: "name"}); err != nil {
		return err
	}

	err = g.doTemplate(w, gti, `
		switch name {
`)
	if err != nil {
		return err
	}

	for _, f := range gti.Fields {
		fmt.Fprintf(w, "// t.%s (%s) (%s)", f.Name, f.Type, f.Type.Kind())

		err := g.doTemplate(w, f, `
		case "{{ .MapKey }}":
`)
		if err != nil {
			return err
		}

		f.Name = "t." + f.Name

		switch f.Type.Kind() {
		case reflect.String:
			if err := g.emitCborUnmarshalStringField(w, f); err != nil {
				return err
			}
		case reflect.Struct:
			if err := g.emitCborUnmarshalStructField(w, f); err != nil {
				return err
			}
		case reflect.Uint64:
			if err := g.emitCborUnmarshalUint64Field(w, f); err != nil {
				return err
			}
		case reflect.Int64:
			if err := g.emitCborUnmarshalInt64Field(w, f); err != nil {
				return err
			}
		case reflect.Uint8:
			if err := g.emitCborUnmarshalUint8Field(w, f); err != nil {
				return err
			}
		case reflect.Array:
			if err := g.emitCborUnmarshalArrayField(w, f); err != nil {
				return err
			}
		case reflect.Slice:
			if err := g.emitCborUnmarshalSliceField(w, f); err != nil {
				return err
			}
		case reflect.Bool:
			if err := g.emitCborUnmarshalBoolField(w, f); err != nil {
				return err
			}
		case reflect.Map:
			if err := g.emitCborUnmarshalMapField(w, f); err != nil {
				return err
			}
		default:
			return fmt.Errorf("field %q of %q has unsupported kind %q", f.Name, gti.Name, f.Type.Kind())
		}
	}

	return g.doTemplate(w, gti, `
		default:
			// Field doesn't exist on this type, so ignore it
			cbg.ScanForLinks(r, func(cid.Cid){})
		}
	}

	return nil
}
`)
}

// GenMapEncodersForType is a convenience wrapper around Gen.GenMapEncodersForType using default
// options.
func GenMapEncodersForType(gti *GenTypeInfo, w io.Writer) error {
	return Gen{}.GenMapEncodersForType(gti, w)
}

// Generates 'tuple representation' cbor encoders for the given type
func (g Gen) GenMapEncodersForType(gti *GenTypeInfo, w io.Writer) error {
	if err := g.emitCborMarshalStructMap(w, gti); err != nil {
		return err
	}

	if err := g.emitCborUnmarshalStructMap(w, gti); err != nil {
		return err
	}

	return nil
}
