// Copyright 2022 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package main

import (
	"bytes"
	"fmt"
	"go/format"
	"go/types"
	"sort"

	"github.com/ethereum/go-ethereum/rlp/internal/rlpstruct"
	"golang.org/x/tools/go/packages"
)

// buildContext keeps the data needed for make*Op.
type buildContext struct {
	topType *types.Named // the type we're creating methods for

	encoderIface *types.Interface
	decoderIface *types.Interface
	rawValueType *types.Named

	typeToStructCache map[types.Type]*rlpstruct.Type
}

func newBuildContext(packageRLP *types.Package) *buildContext {
	enc := packageRLP.Scope().Lookup("Encoder").Type().Underlying()
	dec := packageRLP.Scope().Lookup("Decoder").Type().Underlying()
	rawv := packageRLP.Scope().Lookup("RawValue").Type()
	return &buildContext{
		typeToStructCache: make(map[types.Type]*rlpstruct.Type),
		encoderIface:      enc.(*types.Interface),
		decoderIface:      dec.(*types.Interface),
		rawValueType:      rawv.(*types.Named),
	}
}

func (bctx *buildContext) isEncoder(typ types.Type) bool {
	return types.Implements(typ, bctx.encoderIface)
}

func (bctx *buildContext) isDecoder(typ types.Type) bool {
	return types.Implements(typ, bctx.decoderIface)
}

// typeToStructType converts typ to rlpstruct.Type.
func (bctx *buildContext) typeToStructType(typ types.Type) *rlpstruct.Type {
	if prev := bctx.typeToStructCache[typ]; prev != nil {
		return prev // short-circuit for recursive types.
	}

	// Resolve named types to their underlying type, but keep the name.
	name := types.TypeString(typ, nil)
	for {
		utype := typ.Underlying()
		if utype == typ {
			break
		}
		typ = utype
	}

	// Create the type and store it in cache.
	t := &rlpstruct.Type{
		Name:      name,
		Kind:      typeReflectKind(typ),
		IsEncoder: bctx.isEncoder(typ),
		IsDecoder: bctx.isDecoder(typ),
	}
	bctx.typeToStructCache[typ] = t

	// Assign element type.
	switch typ.(type) {
	case *types.Array, *types.Slice, *types.Pointer:
		etype := typ.(interface{ Elem() types.Type }).Elem()
		t.Elem = bctx.typeToStructType(etype)
	}
	return t
}

// genContext is passed to the gen* methods of op when generating
// the output code. It tracks packages to be imported by the output
// file and assigns unique names of temporary variables.
type genContext struct {
	inPackage   *types.Package
	imports     map[string]genImportPackage
	tempCounter int
}

type genImportPackage struct {
	alias string
	pkg   *types.Package
}

func newGenContext(inPackage *types.Package) *genContext {
	return &genContext{
		inPackage:   inPackage,
		imports:     make(map[string]genImportPackage),
		tempCounter: 0,
	}
}

func (ctx *genContext) temp() string {
	v := fmt.Sprintf("_tmp%d", ctx.tempCounter)
	ctx.tempCounter++
	return v
}

func (ctx *genContext) resetTemp() {
	ctx.tempCounter = 0
}

func (ctx *genContext) addImportPath(path string) {
	pkg, err := ctx.loadPackage(path)
	if err != nil {
		panic(fmt.Sprintf("can't load package %q: %v", path, err))
	}
	ctx.addImport(pkg)
}

func (ctx *genContext) addImport(pkg *types.Package) string {
	if pkg.Path() == ctx.inPackage.Path() {
		return "" // avoid importing the package that we're generating in
	}
	if p, exists := ctx.imports[pkg.Path()]; exists {
		return p.alias
	}
	var (
		baseName = pkg.Name()
		alias    = baseName
		counter  = 1
	)
	// If the base name conflicts with an existing import, add a numeric suffix.
	for ctx.hasAlias(alias) {
		alias = fmt.Sprintf("%s%d", baseName, counter)
		counter++
	}
	ctx.imports[pkg.Path()] = genImportPackage{alias, pkg}
	return alias
}

// hasAlias checks if an alias is already in use
func (ctx *genContext) hasAlias(alias string) bool {
	for _, p := range ctx.imports {
		if p.alias == alias {
			return true
		}
	}
	return false
}

// loadPackage attempts to load package information
func (ctx *genContext) loadPackage(path string) (*types.Package, error) {
	cfg := &packages.Config{Mode: packages.NeedName}
	pkgs, err := packages.Load(cfg, path)
	if err != nil {
		return nil, err
	}
	if len(pkgs) == 0 {
		return nil, fmt.Errorf("no package found for path %s", path)
	}
	return types.NewPackage(path, pkgs[0].Name), nil
}

// qualify is the types.Qualifier used for printing types
func (ctx *genContext) qualify(pkg *types.Package) string {
	if pkg.Path() == ctx.inPackage.Path() {
		return ""
	}
	return ctx.addImport(pkg)
}

// importsList returns all packages that need to be imported
func (ctx *genContext) importsList() []string {
	imp := make([]string, 0, len(ctx.imports))
	for path, p := range ctx.imports {
		if p.alias == p.pkg.Name() {
			imp = append(imp, fmt.Sprintf("%q", path))
		} else {
			imp = append(imp, fmt.Sprintf("%s %q", p.alias, path))
		}
	}
	sort.Strings(imp)
	return imp
}

type op interface {
	// genWrite creates the encoder. The generated code should write v,
	// which is any Go expression, to the rlp.EncoderBuffer 'w'.
	genWrite(ctx *genContext, v string) string

	// genDecode creates the decoder. The generated code should read
	// a value from the rlp.Stream 'dec' and store it to dst.
	genDecode(ctx *genContext) (string, string)
}

// basicOp handles basic types bool, uint*, string.
type basicOp struct {
	typ           types.Type
	writeMethod   string     // EncoderBuffer writer method name
	writeArgType  types.Type // parameter type of writeMethod
	decMethod     string
	decResultType types.Type // return type of decMethod
	decUseBitSize bool       // if true, result bit size is appended to decMethod
}

func (*buildContext) makeBasicOp(typ *types.Basic) (op, error) {
	op := basicOp{typ: typ}
	kind := typ.Kind()
	switch {
	case kind == types.Bool:
		op.writeMethod = "WriteBool"
		op.writeArgType = types.Typ[types.Bool]
		op.decMethod = "Bool"
		op.decResultType = types.Typ[types.Bool]
	case kind >= types.Uint8 && kind <= types.Uint64:
		op.writeMethod = "WriteUint64"
		op.writeArgType = types.Typ[types.Uint64]
		op.decMethod = "Uint"
		op.decResultType = typ
		op.decUseBitSize = true
	case kind == types.String:
		op.writeMethod = "WriteString"
		op.writeArgType = types.Typ[types.String]
		op.decMethod = "String"
		op.decResultType = types.Typ[types.String]
	default:
		return nil, fmt.Errorf("unhandled basic type: %v", typ)
	}
	return op, nil
}

func (*buildContext) makeByteSliceOp(typ *types.Slice) op {
	if !isByte(typ.Elem()) {
		panic("non-byte slice type in makeByteSliceOp")
	}
	bslice := types.NewSlice(types.Typ[types.Uint8])
	return basicOp{
		typ:           typ,
		writeMethod:   "WriteBytes",
		writeArgType:  bslice,
		decMethod:     "Bytes",
		decResultType: bslice,
	}
}

func (bctx *buildContext) makeRawValueOp() op {
	bslice := types.NewSlice(types.Typ[types.Uint8])
	return basicOp{
		typ:           bctx.rawValueType,
		writeMethod:   "Write",
		writeArgType:  bslice,
		decMethod:     "Raw",
		decResultType: bslice,
	}
}

func (op basicOp) writeNeedsConversion() bool {
	return !types.AssignableTo(op.typ, op.writeArgType)
}

func (op basicOp) decodeNeedsConversion() bool {
	return !types.AssignableTo(op.decResultType, op.typ)
}

func (op basicOp) genWrite(ctx *genContext, v string) string {
	if op.writeNeedsConversion() {
		v = fmt.Sprintf("%s(%s)", op.writeArgType, v)
	}
	return fmt.Sprintf("w.%s(%s)\n", op.writeMethod, v)
}

func (op basicOp) genDecode(ctx *genContext) (string, string) {
	var (
		resultV = ctx.temp()
		result  = resultV
		method  = op.decMethod
	)
	if op.decUseBitSize {
		// Note: For now, this only works for platform-independent integer
		// sizes. makeBasicOp forbids the platform-dependent types.
		var sizes types.StdSizes
		method = fmt.Sprintf("%s%d", op.decMethod, sizes.Sizeof(op.typ)*8)
	}

	// Call the decoder method.
	var b bytes.Buffer
	fmt.Fprintf(&b, "%s, err := dec.%s()\n", resultV, method)
	fmt.Fprintf(&b, "if err != nil { return err }\n")
	if op.decodeNeedsConversion() {
		conv := ctx.temp()
		fmt.Fprintf(&b, "%s := %s(%s)\n", conv, types.TypeString(op.typ, ctx.qualify), resultV)
		result = conv
	}
	return result, b.String()
}

// byteArrayOp handles [...]byte.
type byteArrayOp struct {
	typ  types.Type
	name types.Type // name != typ for named byte array types (e.g. common.Address)
}

func (bctx *buildContext) makeByteArrayOp(name *types.Named, typ *types.Array) byteArrayOp {
	nt := types.Type(name)
	if name == nil {
		nt = typ
	}
	return byteArrayOp{typ, nt}
}

func (op byteArrayOp) genWrite(ctx *genContext, v string) string {
	return fmt.Sprintf("w.WriteBytes(%s[:])\n", v)
}

func (op byteArrayOp) genDecode(ctx *genContext) (string, string) {
	var resultV = ctx.temp()

	var b bytes.Buffer
	fmt.Fprintf(&b, "var %s %s\n", resultV, types.TypeString(op.name, ctx.qualify))
	fmt.Fprintf(&b, "if err := dec.ReadBytes(%s[:]); err != nil { return err }\n", resultV)
	return resultV, b.String()
}

// bigIntOp handles big.Int.
// This exists because big.Int has it's own decoder operation on rlp.Stream,
// but the decode method returns *big.Int, so it needs to be dereferenced.
type bigIntOp struct {
	pointer bool
}

func (op bigIntOp) genWrite(ctx *genContext, v string) string {
	var b bytes.Buffer

	fmt.Fprintf(&b, "if %s.Sign() == -1 {\n", v)
	fmt.Fprintf(&b, "  return rlp.ErrNegativeBigInt\n")
	fmt.Fprintf(&b, "}\n")
	dst := v
	if !op.pointer {
		dst = "&" + v
	}
	fmt.Fprintf(&b, "w.WriteBigInt(%s)\n", dst)

	// Wrap with nil check.
	if op.pointer {
		code := b.String()
		b.Reset()
		fmt.Fprintf(&b, "if %s == nil {\n", v)
		fmt.Fprintf(&b, "  w.Write(rlp.EmptyString)")
		fmt.Fprintf(&b, "} else {\n")
		fmt.Fprint(&b, code)
		fmt.Fprintf(&b, "}\n")
	}

	return b.String()
}

func (op bigIntOp) genDecode(ctx *genContext) (string, string) {
	var resultV = ctx.temp()

	var b bytes.Buffer
	fmt.Fprintf(&b, "%s, err := dec.BigInt()\n", resultV)
	fmt.Fprintf(&b, "if err != nil { return err }\n")

	result := resultV
	if !op.pointer {
		result = "(*" + resultV + ")"
	}
	return result, b.String()
}

// uint256Op handles "github.com/holiman/uint256".Int
type uint256Op struct {
	pointer bool
}

func (op uint256Op) genWrite(ctx *genContext, v string) string {
	var b bytes.Buffer

	dst := v
	if !op.pointer {
		dst = "&" + v
	}
	fmt.Fprintf(&b, "w.WriteUint256(%s)\n", dst)

	// Wrap with nil check.
	if op.pointer {
		code := b.String()
		b.Reset()
		fmt.Fprintf(&b, "if %s == nil {\n", v)
		fmt.Fprintf(&b, "  w.Write(rlp.EmptyString)")
		fmt.Fprintf(&b, "} else {\n")
		fmt.Fprint(&b, code)
		fmt.Fprintf(&b, "}\n")
	}

	return b.String()
}

func (op uint256Op) genDecode(ctx *genContext) (string, string) {
	ctx.addImportPath("github.com/holiman/uint256")

	var b bytes.Buffer
	resultV := ctx.temp()
	fmt.Fprintf(&b, "var %s uint256.Int\n", resultV)
	fmt.Fprintf(&b, "if err := dec.ReadUint256(&%s); err != nil { return err }\n", resultV)

	result := resultV
	if op.pointer {
		result = "&" + resultV
	}
	return result, b.String()
}

// encoderDecoderOp handles rlp.Encoder and rlp.Decoder.
// In order to be used with this, the type must implement both interfaces.
// This restriction may be lifted in the future by creating separate ops for
// encoding and decoding.
type encoderDecoderOp struct {
	typ types.Type
}

func (op encoderDecoderOp) genWrite(ctx *genContext, v string) string {
	return fmt.Sprintf("if err := %s.EncodeRLP(w); err != nil { return err }\n", v)
}

func (op encoderDecoderOp) genDecode(ctx *genContext) (string, string) {
	// DecodeRLP must have pointer receiver, and this is verified in makeOp.
	etyp := op.typ.(*types.Pointer).Elem()
	var resultV = ctx.temp()

	var b bytes.Buffer
	fmt.Fprintf(&b, "%s := new(%s)\n", resultV, types.TypeString(etyp, ctx.qualify))
	fmt.Fprintf(&b, "if err := %s.DecodeRLP(dec); err != nil { return err }\n", resultV)
	return resultV, b.String()
}

// ptrOp handles pointer types.
type ptrOp struct {
	elemTyp  types.Type
	elem     op
	nilOK    bool
	nilValue rlpstruct.NilKind
}

func (bctx *buildContext) makePtrOp(elemTyp types.Type, tags rlpstruct.Tags) (op, error) {
	elemOp, err := bctx.makeOp(nil, elemTyp, rlpstruct.Tags{})
	if err != nil {
		return nil, err
	}
	op := ptrOp{elemTyp: elemTyp, elem: elemOp}

	// Determine nil value.
	if tags.NilOK {
		op.nilOK = true
		op.nilValue = tags.NilKind
	} else {
		styp := bctx.typeToStructType(elemTyp)
		op.nilValue = styp.DefaultNilValue()
	}
	return op, nil
}

func (op ptrOp) genWrite(ctx *genContext, v string) string {
	// Note: in writer functions, accesses to v are read-only, i.e. v is any Go
	// expression. To make all accesses work through the pointer, we substitute
	// v with (*v). This is required for most accesses including `v`, `call(v)`,
	// and `v[index]` on slices.
	//
	// For `v.field` and `v[:]` on arrays, the dereference operation is not required.
	var vv string
	_, isStruct := op.elem.(structOp)
	_, isByteArray := op.elem.(byteArrayOp)
	if isStruct || isByteArray {
		vv = v
	} else {
		vv = fmt.Sprintf("(*%s)", v)
	}

	var b bytes.Buffer
	fmt.Fprintf(&b, "if %s == nil {\n", v)
	fmt.Fprintf(&b, "  w.Write([]byte{0x%X})\n", op.nilValue)
	fmt.Fprintf(&b, "} else {\n")
	fmt.Fprintf(&b, "  %s", op.elem.genWrite(ctx, vv))
	fmt.Fprintf(&b, "}\n")
	return b.String()
}

func (op ptrOp) genDecode(ctx *genContext) (string, string) {
	result, code := op.elem.genDecode(ctx)
	if !op.nilOK {
		// If nil pointers are not allowed, we can just decode the element.
		return "&" + result, code
	}

	// nil is allowed, so check the kind and size first.
	// If size is zero and kind matches the nilKind of the type,
	// the value decodes as a nil pointer.
	var (
		resultV  = ctx.temp()
		kindV    = ctx.temp()
		sizeV    = ctx.temp()
		wantKind string
	)
	if op.nilValue == rlpstruct.NilKindList {
		wantKind = "rlp.List"
	} else {
		wantKind = "rlp.String"
	}
	var b bytes.Buffer
	fmt.Fprintf(&b, "var %s %s\n", resultV, types.TypeString(types.NewPointer(op.elemTyp), ctx.qualify))
	fmt.Fprintf(&b, "if %s, %s, err := dec.Kind(); err != nil {\n", kindV, sizeV)
	fmt.Fprintf(&b, "  return err\n")
	fmt.Fprintf(&b, "} else if %s != 0 || %s != %s {\n", sizeV, kindV, wantKind)
	fmt.Fprint(&b, code)
	fmt.Fprintf(&b, "  %s = &%s\n", resultV, result)
	fmt.Fprintf(&b, "}\n")
	return resultV, b.String()
}

// structOp handles struct types.
type structOp struct {
	named          *types.Named
	typ            *types.Struct
	fields         []*structField
	optionalFields []*structField
}

type structField struct {
	name string
	typ  types.Type
	elem op
}

func (bctx *buildContext) makeStructOp(named *types.Named, typ *types.Struct) (op, error) {
	// Convert fields to []rlpstruct.Field.
	var allStructFields []rlpstruct.Field
	for i := 0; i < typ.NumFields(); i++ {
		f := typ.Field(i)
		allStructFields = append(allStructFields, rlpstruct.Field{
			Name:     f.Name(),
			Exported: f.Exported(),
			Index:    i,
			Tag:      typ.Tag(i),
			Type:     *bctx.typeToStructType(f.Type()),
		})
	}

	// Filter/validate fields.
	fields, tags, err := rlpstruct.ProcessFields(allStructFields)
	if err != nil {
		return nil, err
	}

	// Create field ops.
	var op = structOp{named: named, typ: typ}
	for i, field := range fields {
		// Advanced struct tags are not supported yet.
		tag := tags[i]
		if err := checkUnsupportedTags(field.Name, tag); err != nil {
			return nil, err
		}
		typ := typ.Field(field.Index).Type()
		elem, err := bctx.makeOp(nil, typ, tags[i])
		if err != nil {
			return nil, fmt.Errorf("field %s: %v", field.Name, err)
		}
		f := &structField{name: field.Name, typ: typ, elem: elem}
		if tag.Optional {
			op.optionalFields = append(op.optionalFields, f)
		} else {
			op.fields = append(op.fields, f)
		}
	}
	return op, nil
}

func checkUnsupportedTags(field string, tag rlpstruct.Tags) error {
	if tag.Tail {
		return fmt.Errorf(`field %s has unsupported struct tag "tail"`, field)
	}
	return nil
}

func (op structOp) genWrite(ctx *genContext, v string) string {
	var b bytes.Buffer
	var listMarker = ctx.temp()
	fmt.Fprintf(&b, "%s := w.List()\n", listMarker)
	for _, field := range op.fields {
		selector := v + "." + field.name
		fmt.Fprint(&b, field.elem.genWrite(ctx, selector))
	}
	op.writeOptionalFields(&b, ctx, v)
	fmt.Fprintf(&b, "w.ListEnd(%s)\n", listMarker)
	return b.String()
}

func (op structOp) writeOptionalFields(b *bytes.Buffer, ctx *genContext, v string) {
	if len(op.optionalFields) == 0 {
		return
	}
	// First check zero-ness of all optional fields.
	var zeroV = make([]string, len(op.optionalFields))
	for i, field := range op.optionalFields {
		selector := v + "." + field.name
		zeroV[i] = ctx.temp()
		fmt.Fprintf(b, "%s := %s\n", zeroV[i], nonZeroCheck(selector, field.typ, ctx.qualify))
	}
	// Now write the fields.
	for i, field := range op.optionalFields {
		selector := v + "." + field.name
		cond := ""
		for j := i; j < len(op.optionalFields); j++ {
			if j > i {
				cond += " || "
			}
			cond += zeroV[j]
		}
		fmt.Fprintf(b, "if %s {\n", cond)
		fmt.Fprint(b, field.elem.genWrite(ctx, selector))
		fmt.Fprintf(b, "}\n")
	}
}

func (op structOp) genDecode(ctx *genContext) (string, string) {
	// Get the string representation of the type.
	// Here, named types are handled separately because the output
	// would contain a copy of the struct definition otherwise.
	var typeName string
	if op.named != nil {
		typeName = types.TypeString(op.named, ctx.qualify)
	} else {
		typeName = types.TypeString(op.typ, ctx.qualify)
	}

	// Create struct object.
	var resultV = ctx.temp()
	var b bytes.Buffer
	fmt.Fprintf(&b, "var %s %s\n", resultV, typeName)

	// Decode fields.
	fmt.Fprintf(&b, "{\n")
	fmt.Fprintf(&b, "if _, err := dec.List(); err != nil { return err }\n")
	for _, field := range op.fields {
		result, code := field.elem.genDecode(ctx)
		fmt.Fprintf(&b, "// %s:\n", field.name)
		fmt.Fprint(&b, code)
		fmt.Fprintf(&b, "%s.%s = %s\n", resultV, field.name, result)
	}
	op.decodeOptionalFields(&b, ctx, resultV)
	fmt.Fprintf(&b, "if err := dec.ListEnd(); err != nil { return err }\n")
	fmt.Fprintf(&b, "}\n")
	return resultV, b.String()
}

func (op structOp) decodeOptionalFields(b *bytes.Buffer, ctx *genContext, resultV string) {
	var suffix bytes.Buffer
	for _, field := range op.optionalFields {
		result, code := field.elem.genDecode(ctx)
		fmt.Fprintf(b, "// %s:\n", field.name)
		fmt.Fprintf(b, "if dec.MoreDataInList() {\n")
		fmt.Fprint(b, code)
		fmt.Fprintf(b, "%s.%s = %s\n", resultV, field.name, result)
		fmt.Fprintf(&suffix, "}\n")
	}
	suffix.WriteTo(b)
}

// sliceOp handles slice types.
type sliceOp struct {
	typ    *types.Slice
	elemOp op
}

func (bctx *buildContext) makeSliceOp(typ *types.Slice) (op, error) {
	elemOp, err := bctx.makeOp(nil, typ.Elem(), rlpstruct.Tags{})
	if err != nil {
		return nil, err
	}
	return sliceOp{typ: typ, elemOp: elemOp}, nil
}

func (op sliceOp) genWrite(ctx *genContext, v string) string {
	var (
		listMarker = ctx.temp() // holds return value of w.List()
		iterElemV  = ctx.temp() // iteration variable
		elemCode   = op.elemOp.genWrite(ctx, iterElemV)
	)

	var b bytes.Buffer
	fmt.Fprintf(&b, "%s := w.List()\n", listMarker)
	fmt.Fprintf(&b, "for _, %s := range %s {\n", iterElemV, v)
	fmt.Fprint(&b, elemCode)
	fmt.Fprintf(&b, "}\n")
	fmt.Fprintf(&b, "w.ListEnd(%s)\n", listMarker)
	return b.String()
}

func (op sliceOp) genDecode(ctx *genContext) (string, string) {
	var sliceV = ctx.temp() // holds the output slice
	elemResult, elemCode := op.elemOp.genDecode(ctx)

	var b bytes.Buffer
	fmt.Fprintf(&b, "var %s %s\n", sliceV, types.TypeString(op.typ, ctx.qualify))
	fmt.Fprintf(&b, "if _, err := dec.List(); err != nil { return err }\n")
	fmt.Fprintf(&b, "for dec.MoreDataInList() {\n")
	fmt.Fprintf(&b, "  %s", elemCode)
	fmt.Fprintf(&b, "  %s = append(%s, %s)\n", sliceV, sliceV, elemResult)
	fmt.Fprintf(&b, "}\n")
	fmt.Fprintf(&b, "if err := dec.ListEnd(); err != nil { return err }\n")
	return sliceV, b.String()
}

func (bctx *buildContext) makeOp(name *types.Named, typ types.Type, tags rlpstruct.Tags) (op, error) {
	switch typ := typ.(type) {
	case *types.Named:
		if isBigInt(typ) {
			return bigIntOp{}, nil
		}
		if isUint256(typ) {
			return uint256Op{}, nil
		}
		if typ == bctx.rawValueType {
			return bctx.makeRawValueOp(), nil
		}
		if bctx.isDecoder(typ) {
			return nil, fmt.Errorf("type %v implements rlp.Decoder with non-pointer receiver", typ)
		}
		// TODO: same check for encoder?
		return bctx.makeOp(typ, typ.Underlying(), tags)
	case *types.Pointer:
		if isBigInt(typ.Elem()) {
			return bigIntOp{pointer: true}, nil
		}
		if isUint256(typ.Elem()) {
			return uint256Op{pointer: true}, nil
		}
		// Encoder/Decoder interfaces.
		if bctx.isEncoder(typ) {
			if bctx.isDecoder(typ) {
				return encoderDecoderOp{typ}, nil
			}
			return nil, fmt.Errorf("type %v implements rlp.Encoder but not rlp.Decoder", typ)
		}
		if bctx.isDecoder(typ) {
			return nil, fmt.Errorf("type %v implements rlp.Decoder but not rlp.Encoder", typ)
		}
		// Default pointer handling.
		return bctx.makePtrOp(typ.Elem(), tags)
	case *types.Basic:
		return bctx.makeBasicOp(typ)
	case *types.Struct:
		return bctx.makeStructOp(name, typ)
	case *types.Slice:
		etyp := typ.Elem()
		if isByte(etyp) && !bctx.isEncoder(etyp) {
			return bctx.makeByteSliceOp(typ), nil
		}
		return bctx.makeSliceOp(typ)
	case *types.Array:
		etyp := typ.Elem()
		if isByte(etyp) && !bctx.isEncoder(etyp) {
			return bctx.makeByteArrayOp(name, typ), nil
		}
		return nil, fmt.Errorf("unhandled array type: %v", typ)
	default:
		return nil, fmt.Errorf("unhandled type: %v", typ)
	}
}

// generateDecoder generates the DecodeRLP method on 'typ'.
func generateDecoder(ctx *genContext, typ string, op op) []byte {
	ctx.resetTemp()
	ctx.addImportPath(pathOfPackageRLP)

	result, code := op.genDecode(ctx)
	var b bytes.Buffer
	fmt.Fprintf(&b, "func (obj *%s) DecodeRLP(dec *rlp.Stream) error {\n", typ)
	fmt.Fprint(&b, code)
	fmt.Fprintf(&b, "  *obj = %s\n", result)
	fmt.Fprintf(&b, "  return nil\n")
	fmt.Fprintf(&b, "}\n")
	return b.Bytes()
}

// generateEncoder generates the EncodeRLP method on 'typ'.
func generateEncoder(ctx *genContext, typ string, op op) []byte {
	ctx.resetTemp()
	ctx.addImportPath("io")
	ctx.addImportPath(pathOfPackageRLP)

	var b bytes.Buffer
	fmt.Fprintf(&b, "func (obj *%s) EncodeRLP(_w io.Writer) error {\n", typ)
	fmt.Fprintf(&b, "  w := rlp.NewEncoderBuffer(_w)\n")
	fmt.Fprint(&b, op.genWrite(ctx, "obj"))
	fmt.Fprintf(&b, "  return w.Flush()\n")
	fmt.Fprintf(&b, "}\n")
	return b.Bytes()
}

func (bctx *buildContext) generate(typ *types.Named, encoder, decoder bool) ([]byte, error) {
	bctx.topType = typ

	pkg := typ.Obj().Pkg()
	op, err := bctx.makeOp(nil, typ, rlpstruct.Tags{})
	if err != nil {
		return nil, err
	}

	var (
		ctx       = newGenContext(pkg)
		encSource []byte
		decSource []byte
	)
	if encoder {
		encSource = generateEncoder(ctx, typ.Obj().Name(), op)
	}
	if decoder {
		decSource = generateDecoder(ctx, typ.Obj().Name(), op)
	}

	var b bytes.Buffer
	fmt.Fprintf(&b, "package %s\n\n", pkg.Name())
	for _, imp := range ctx.importsList() {
		fmt.Fprintf(&b, "import %s\n", imp)
	}
	if encoder {
		fmt.Fprintln(&b)
		b.Write(encSource)
	}
	if decoder {
		fmt.Fprintln(&b)
		b.Write(decSource)
	}

	source := b.Bytes()
	// fmt.Println(string(source))
	return format.Source(source)
}
