// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package bind

import (
	"bytes"
	"fmt"
	"go/ast"
	"go/token"
	"go/types"
	"io"
	"regexp"
	"strings"
	"unicode"
	"unicode/utf8"
)

type (
	ErrorList []error

	// varMode describes the lifetime of an argument or
	// return value. Modes are used to guide the conversion
	// of string and byte slice values across the language
	// barrier. The same conversion mode must be used for
	// both the conversion before a foreign call and the
	// corresponding conversion after the call.
	// See the mode* constants for a description of
	// each mode.
	varMode int
)

const (
	// modeTransient are for function arguments that
	// are not used after the function returns.
	// Transient byte slices don't need copying
	// when passed across the language barrier.
	modeTransient varMode = iota
	// modeRetained are for returned values and for function
	// arguments that are used after the function returns.
	// Retained byte slices need an intermediate copy.
	modeRetained
)

func (list ErrorList) Error() string {
	buf := new(bytes.Buffer)
	for i, err := range list {
		if i > 0 {
			buf.WriteRune('\n')
		}
		io.WriteString(buf, err.Error())
	}
	return buf.String()
}

// interfaceInfo comes from Init and collects the auxiliary information
// needed to generate bindings for an exported Go interface in a bound
// package.
type interfaceInfo struct {
	obj     *types.TypeName
	t       *types.Interface
	summary ifaceSummary
}

// structInfo comes from Init and collects the auxiliary information
// needed to generate bindings for an exported Go struct in a bound
// package.
type structInfo struct {
	obj *types.TypeName
	t   *types.Struct
}

// Generator contains the common Go package information
// needed for the specific Go, Java, ObjC generators.
//
// After setting Printer, Fset, AllPkg, Pkg, the Init
// method is used to initialize the auxiliary information
// about the package to be generated, Pkg.
type Generator struct {
	*Printer
	Fset   *token.FileSet
	AllPkg []*types.Package
	Files  []*ast.File
	Pkg    *types.Package
	err    ErrorList

	// fields set by init.
	pkgName   string
	pkgPrefix string
	funcs     []*types.Func
	constants []*types.Const
	vars      []*types.Var

	interfaces []interfaceInfo
	structs    []structInfo
	otherNames []*types.TypeName
	// allIntf contains interfaces from all bound packages.
	allIntf []interfaceInfo

	docs pkgDocs
}

// A pkgDocs maps the name of each exported package-level declaration to its extracted documentation.
type pkgDocs map[string]*pkgDoc

type pkgDoc struct {
	doc string
	// Struct or interface fields and methods.
	members map[string]string
}

// pkgPrefix returns a prefix that disambiguates symbol names for binding
// multiple packages.
//
// TODO(elias.naur): Avoid (and test) name clashes from multiple packages
// with the same name. Perhaps use the index from the order the package is
// generated.
func pkgPrefix(pkg *types.Package) string {
	// The error type has no package
	if pkg == nil {
		return ""
	}
	return pkg.Name()
}

func (g *Generator) Init() {
	if g.Pkg != nil {
		g.pkgName = g.Pkg.Name()
	}
	g.pkgPrefix = pkgPrefix(g.Pkg)

	if g.Pkg != nil {
		g.parseDocs()
		scope := g.Pkg.Scope()
		hasExported := false
		for _, name := range scope.Names() {
			obj := scope.Lookup(name)
			if !obj.Exported() {
				continue
			}
			hasExported = true
			switch obj := obj.(type) {
			case *types.Func:
				if isCallable(obj) {
					g.funcs = append(g.funcs, obj)
				}
			case *types.TypeName:
				named, ok := obj.Type().(*types.Named)
				if !ok {
					continue
				}
				switch t := named.Underlying().(type) {
				case *types.Struct:
					g.structs = append(g.structs, structInfo{obj, t})
				case *types.Interface:
					g.interfaces = append(g.interfaces, interfaceInfo{obj, t, makeIfaceSummary(t)})
				default:
					g.otherNames = append(g.otherNames, obj)
				}
			case *types.Const:
				g.constants = append(g.constants, obj)
			case *types.Var:
				g.vars = append(g.vars, obj)
			default:
				g.errorf("unsupported exported type for %s: %T", obj.Name(), obj)
			}
		}
		if !hasExported {
			g.errorf("no exported names in the package %q", g.Pkg.Path())
		}
	} else {
		// Bind the single supported type from the universe scope, error.
		errType := types.Universe.Lookup("error").(*types.TypeName)
		t := errType.Type().Underlying().(*types.Interface)
		g.interfaces = append(g.interfaces, interfaceInfo{errType, t, makeIfaceSummary(t)})
	}
	for _, p := range g.AllPkg {
		scope := p.Scope()
		for _, name := range scope.Names() {
			obj := scope.Lookup(name)
			if !obj.Exported() {
				continue
			}
			if obj, ok := obj.(*types.TypeName); ok {
				named, ok := obj.Type().(*types.Named)
				if !ok {
					continue
				}
				if t, ok := named.Underlying().(*types.Interface); ok {
					g.allIntf = append(g.allIntf, interfaceInfo{obj, t, makeIfaceSummary(t)})
				}
			}
		}
	}
}

// parseDocs extracts documentation from a package in a form useful for lookups.
func (g *Generator) parseDocs() {
	d := make(pkgDocs)
	for _, f := range g.Files {
		for _, decl := range f.Decls {
			switch decl := decl.(type) {
			case *ast.GenDecl:
				for _, spec := range decl.Specs {
					switch spec := spec.(type) {
					case *ast.TypeSpec:
						d.addType(spec, decl.Doc)
					case *ast.ValueSpec:
						d.addValue(spec, decl.Doc)
					}
				}
			case *ast.FuncDecl:
				d.addFunc(decl)
			}
		}
	}
	g.docs = d
}

func (d pkgDocs) addValue(t *ast.ValueSpec, outerDoc *ast.CommentGroup) {
	for _, n := range t.Names {
		if !ast.IsExported(n.Name) {
			continue
		}
		doc := t.Doc
		if doc == nil {
			doc = outerDoc
		}
		if doc != nil {
			d[n.Name] = &pkgDoc{doc: doc.Text()}
		}
	}
}

func (d pkgDocs) addFunc(f *ast.FuncDecl) {
	doc := f.Doc
	if doc == nil {
		return
	}
	fn := f.Name.Name
	if !ast.IsExported(fn) {
		return
	}
	if r := f.Recv; r != nil {
		// f is a method.
		n := typeName(r.List[0].Type)
		pd, exists := d[n]
		if !exists {
			pd = &pkgDoc{members: make(map[string]string)}
			d[n] = pd
		}
		pd.members[fn] = doc.Text()
	} else {
		// f is a function.
		d[fn] = &pkgDoc{doc: doc.Text()}
	}
}

func (d pkgDocs) addType(t *ast.TypeSpec, outerDoc *ast.CommentGroup) {
	if !ast.IsExported(t.Name.Name) {
		return
	}
	doc := t.Doc
	if doc == nil {
		doc = outerDoc
	}
	pd := d[t.Name.Name]
	pd = &pkgDoc{members: make(map[string]string)}
	d[t.Name.Name] = pd
	if doc != nil {
		pd.doc = doc.Text()
	}
	var fields *ast.FieldList
	switch t := t.Type.(type) {
	case *ast.StructType:
		fields = t.Fields
	case *ast.InterfaceType:
		fields = t.Methods
	}
	if fields != nil {
		for _, field := range fields.List {
			if field.Doc != nil {
				if field.Names == nil {
					// Anonymous field. Extract name from its type.
					if n := typeName(field.Type); ast.IsExported(n) {
						pd.members[n] = field.Doc.Text()
					}
				}
				for _, n := range field.Names {
					if ast.IsExported(n.Name) {
						pd.members[n.Name] = field.Doc.Text()
					}
				}
			}
		}
	}
}

// typeName returns the type name T for expressions on the
// T, *T, **T (etc.) form.
func typeName(t ast.Expr) string {
	switch t := t.(type) {
	case *ast.StarExpr:
		return typeName(t.X)
	case *ast.Ident:
		return t.Name
	case *ast.SelectorExpr:
		return t.Sel.Name
	default:
		return ""
	}
}

func (d *pkgDoc) Doc() string {
	if d == nil {
		return ""
	}
	return d.doc
}

func (d *pkgDoc) Member(n string) string {
	if d == nil {
		return ""
	}
	return d.members[n]
}

// constructorType returns the type T for a function of the forms:
//
// func NewT...(...) *T
// func NewT...(...) (*T, error)
func (g *Generator) constructorType(f *types.Func) *types.TypeName {
	sig := f.Type().(*types.Signature)
	res := sig.Results()
	if res.Len() != 1 && !(res.Len() == 2 && isErrorType(res.At(1).Type())) {
		return nil
	}
	rt := res.At(0).Type()
	pt, ok := rt.(*types.Pointer)
	if !ok {
		return nil
	}
	nt, ok := pt.Elem().(*types.Named)
	if !ok {
		return nil
	}
	obj := nt.Obj()
	if !strings.HasPrefix(f.Name(), "New"+obj.Name()) {
		return nil
	}
	return obj
}

func toCFlag(v bool) int {
	if v {
		return 1
	}
	return 0
}

func (g *Generator) errorf(format string, args ...interface{}) {
	g.err = append(g.err, fmt.Errorf(format, args...))
}

// cgoType returns the name of a Cgo type suitable for converting a value of
// the given type.
func (g *Generator) cgoType(t types.Type) string {
	switch t := t.(type) {
	case *types.Basic:
		switch t.Kind() {
		case types.Bool, types.UntypedBool:
			return "char"
		case types.Int:
			return "nint"
		case types.Int8:
			return "int8_t"
		case types.Int16:
			return "int16_t"
		case types.Int32, types.UntypedRune: // types.Rune
			return "int32_t"
		case types.Int64, types.UntypedInt:
			return "int64_t"
		case types.Uint8: // types.Byte
			return "uint8_t"
		// TODO(crawshaw): case types.Uint, types.Uint16, types.Uint32, types.Uint64:
		case types.Float32:
			return "float"
		case types.Float64, types.UntypedFloat:
			return "double"
		case types.String:
			return "nstring"
		default:
			g.errorf("unsupported basic type: %s", t)
		}
	case *types.Slice:
		switch e := t.Elem().(type) {
		case *types.Basic:
			switch e.Kind() {
			case types.Uint8: // Byte.
				return "nbyteslice"
			default:
				g.errorf("unsupported slice type: %s", t)
			}
		default:
			g.errorf("unsupported slice type: %s", t)
		}
	case *types.Pointer:
		if _, ok := t.Elem().(*types.Named); ok {
			return g.cgoType(t.Elem())
		}
		g.errorf("unsupported pointer to type: %s", t)
	case *types.Named:
		return "int32_t"
	default:
		g.errorf("unsupported type: %s", t)
	}
	return "TODO"
}

func (g *Generator) genInterfaceMethodSignature(m *types.Func, iName string, header bool, g_paramName func(*types.Tuple, int) string) {
	sig := m.Type().(*types.Signature)
	params := sig.Params()
	res := sig.Results()

	if res.Len() == 0 {
		g.Printf("void ")
	} else {
		if res.Len() == 1 {
			g.Printf("%s ", g.cgoType(res.At(0).Type()))
		} else {
			if header {
				g.Printf("typedef struct cproxy%s_%s_%s_return {\n", g.pkgPrefix, iName, m.Name())
				g.Indent()
				for i := 0; i < res.Len(); i++ {
					t := res.At(i).Type()
					g.Printf("%s r%d;\n", g.cgoType(t), i)
				}
				g.Outdent()
				g.Printf("} cproxy%s_%s_%s_return;\n", g.pkgPrefix, iName, m.Name())
			}
			g.Printf("struct cproxy%s_%s_%s_return ", g.pkgPrefix, iName, m.Name())
		}
	}
	g.Printf("cproxy%s_%s_%s(int32_t refnum", g.pkgPrefix, iName, m.Name())
	for i := 0; i < params.Len(); i++ {
		t := params.At(i).Type()
		g.Printf(", %s %s", g.cgoType(t), g_paramName(params, i))
	}
	g.Printf(")")
	if header {
		g.Printf(";\n")
	} else {
		g.Printf(" {\n")
	}
}

func (g *Generator) validPkg(pkg *types.Package) bool {
	for _, p := range g.AllPkg {
		if p == pkg {
			return true
		}
	}
	return false
}

// isSigSupported reports whether the generators can handle a given
// function signature.
func (g *Generator) isSigSupported(t types.Type) bool {
	sig := t.(*types.Signature)
	params := sig.Params()
	for i := 0; i < params.Len(); i++ {
		if !g.isSupported(params.At(i).Type()) {
			return false
		}
	}
	res := sig.Results()
	for i := 0; i < res.Len(); i++ {
		if !g.isSupported(res.At(i).Type()) {
			return false
		}
	}
	return true
}

// isSupported reports whether the generators can handle the type.
func (g *Generator) isSupported(t types.Type) bool {
	if isErrorType(t) || isWrapperType(t) {
		return true
	}
	switch t := t.(type) {
	case *types.Basic:
		switch t.Kind() {
		case types.Bool, types.UntypedBool,
			types.Int,
			types.Int8, types.Uint8, // types.Byte
			types.Int16,
			types.Int32, types.UntypedRune, // types.Rune
			types.Int64, types.UntypedInt,
			types.Float32,
			types.Float64, types.UntypedFloat,
			types.String, types.UntypedString:
			return true
		}
		return false
	case *types.Slice:
		switch e := t.Elem().(type) {
		case *types.Basic:
			return e.Kind() == types.Uint8
		}
	case *types.Pointer:
		switch t := t.Elem().(type) {
		case *types.Named:
			return g.validPkg(t.Obj().Pkg())
		}
	case *types.Named:
		switch t.Underlying().(type) {
		case *types.Interface, *types.Pointer:
			return g.validPkg(t.Obj().Pkg())
		}
	}
	return false
}

var paramRE = regexp.MustCompile(`^p[0-9]*$`)

// basicParamName replaces incompatible name with a p0-pN name.
// Missing names, or existing names of the form p[0-9] are incompatible.
func basicParamName(params *types.Tuple, pos int) string {
	name := params.At(pos).Name()
	if name == "" || name[0] == '_' || paramRE.MatchString(name) {
		name = fmt.Sprintf("p%d", pos)
	}
	return name
}

func lowerFirst(s string) string {
	if s == "" {
		return ""
	}

	var conv []rune
	for len(s) > 0 {
		r, n := utf8.DecodeRuneInString(s)
		if !unicode.IsUpper(r) {
			if l := len(conv); l > 1 {
				conv[l-1] = unicode.ToUpper(conv[l-1])
			}
			return string(conv) + s
		}
		conv = append(conv, unicode.ToLower(r))
		s = s[n:]
	}
	return string(conv)
}

// newNameSanitizer returns a functions that replaces all dashes and dots
// with underscores, as well as avoiding reserved words by suffixing such
// identifiers with underscores.
func newNameSanitizer(res []string) func(s string) string {
	reserved := make(map[string]bool)
	for _, word := range res {
		reserved[word] = true
	}
	symbols := strings.NewReplacer(
		"-", "_",
		".", "_",
	)
	return func(s string) string {
		if reserved[s] {
			return s + "_"
		}
		return symbols.Replace(s)
	}
}
