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

package types

import (
	"bytes"
	"fmt"

	"github.com/wa-lang/wa/internal/ast"
	"github.com/wa-lang/wa/internal/token"
)

// This file implements the collection of an interface's methods
// without relying on partially computed types of methods or interfaces
// for interface types declared at the package level.
//
// Because interfaces must not embed themselves, directly or indirectly,
// the method set of a valid interface can always be computed independent
// of any cycles that might exist via method signatures (see also issue #18395).
//
// Except for blank method name and interface cycle errors, no errors
// are reported. Affected methods or embedded interfaces are silently
// dropped. Subsequent type-checking of the interface will check
// signatures and embedded interfaces and report errors at that time.
//
// Only infoFromTypeLit should be called directly from code outside this file
// to compute an ifaceInfo.

// ifaceInfo describes the method set for an interface.
// The zero value for an ifaceInfo is a ready-to-use ifaceInfo representing
// the empty interface.
type ifaceInfo struct {
	explicits int           // number of explicitly declared methods
	methods   []*methodInfo // all methods, starting with explicitly declared ones in source order
}

// emptyIfaceInfo represents the ifaceInfo for the empty interface.
var emptyIfaceInfo ifaceInfo

func (info *ifaceInfo) String() string {
	var buf bytes.Buffer
	fmt.Fprintf(&buf, "interface{")
	for i, m := range info.methods {
		if i > 0 {
			fmt.Fprint(&buf, " ")
		}
		fmt.Fprint(&buf, m)
	}
	fmt.Fprintf(&buf, "}")
	return buf.String()
}

// methodInfo represents an interface method.
// At least one of src or fun must be non-nil.
// (Methods declared in the current package have a non-nil scope
// and src, and eventually a non-nil fun field; imported and pre-
// declared methods have a nil scope and src, and only a non-nil
// fun field.)
type methodInfo struct {
	scope *Scope     // scope of interface method; or nil
	src   *ast.Field // syntax tree representation of interface method; or nil
	fun   *Func      // corresponding fully type-checked method type; or nil
}

func (info *methodInfo) String() string {
	if info.fun != nil {
		return info.fun.name
	}
	return info.src.Names[0].Name
}

func (info *methodInfo) Pos() token.Pos {
	if info.fun != nil {
		return info.fun.Pos()
	}
	return info.src.Pos()
}

func (info *methodInfo) id(pkg *Package) string {
	if info.fun != nil {
		return info.fun.Id()
	}
	return Id(pkg, info.src.Names[0].Name)
}

// A methodInfoSet maps method ids to methodInfos.
// It is used to determine duplicate declarations.
// (A methodInfo set is the equivalent of an objset
// but for methodInfos rather than Objects.)
type methodInfoSet map[string]*methodInfo

// insert attempts to insert an method m into the method set s.
// If s already contains an alternative method alt with
// the same name, insert leaves s unchanged and returns alt.
// Otherwise it inserts m and returns nil.
func (s *methodInfoSet) insert(pkg *Package, m *methodInfo) *methodInfo {
	id := m.id(pkg)
	if alt := (*s)[id]; alt != nil {
		return alt
	}
	if *s == nil {
		*s = make(methodInfoSet)
	}
	(*s)[id] = m
	return nil
}

// like Checker.declareInSet but for method infos.
func (check *Checker) declareInMethodSet(mset *methodInfoSet, pos token.Pos, m *methodInfo) bool {
	if alt := mset.insert(check.pkg, m); alt != nil {
		check.errorf(pos, "%s redeclared", m)
		check.reportAltMethod(alt)
		return false
	}
	return true
}

// like Checker.reportAltDecl but for method infos.
func (check *Checker) reportAltMethod(m *methodInfo) {
	if pos := m.Pos(); pos.IsValid() {
		// We use "other" rather than "previous" here because
		// the first declaration seen may not be textually
		// earlier in the source.
		check.errorf(pos, "\tother declaration of %s", m) // secondary error, \t indented
	}
}

// infoFromTypeLit computes the method set for the given interface iface
// declared in scope.
// If a corresponding type name exists (tname != nil), it is used for
// cycle detection and to cache the method set.
// The result is the method set, or nil if there is a cycle via embedded
// interfaces. A non-nil result doesn't mean that there were no errors,
// but they were either reported (e.g., blank methods), or will be found
// (again) when computing the interface's type.
// If tname is not nil it must be the last element in path.
func (check *Checker) infoFromTypeLit(scope *Scope, iface *ast.InterfaceType, tname *TypeName, path []*TypeName) (info *ifaceInfo) {
	assert(iface != nil)

	// lazy-allocate interfaces map
	if check.interfaces == nil {
		check.interfaces = make(map[*TypeName]*ifaceInfo)
	}

	if trace {
		check.trace(iface.Pos(), "-- collect methods for %v (path = %s, objPath = %s)", iface, pathString(path), objPathString(check.objPath))
		check.indent++
		defer func() {
			check.indent--
			check.trace(iface.Pos(), "=> %s", info)
		}()
	}

	// If the interface is named, check if we computed info already.
	//
	// This is not simply an optimization; we may run into stack
	// overflow with recursive interface declarations. Example:
	//
	//      type T interface {
	//              m() interface { T }
	//      }
	//
	// (Since recursive definitions can only be expressed via names,
	// it is sufficient to track named interfaces here.)
	//
	// While at it, use the same mechanism to detect cycles. (We still
	// have the path-based cycle check because we want to report the
	// entire cycle if present.)
	if tname != nil {
		assert(path[len(path)-1] == tname) // tname must be last path element
		var found bool
		if info, found = check.interfaces[tname]; found {
			if info == nil {
				// We have a cycle and use check.cycle to report it.
				// We are guaranteed that check.cycle also finds the
				// cycle because when infoFromTypeLit is called, any
				// tname that's already in check.interfaces was also
				// added to the path. (But the converse is not true:
				// A non-nil tname is always the last element in path.)
				ok := check.cycle(tname, path, true)
				assert(ok)
			}
			return
		}
		check.interfaces[tname] = nil // computation started but not complete
	}

	if iface.Methods.List == nil {
		// fast track for empty interface
		info = &emptyIfaceInfo
	} else {
		// (syntactically) non-empty interface
		info = new(ifaceInfo)

		// collect explicitly declared methods and embedded interfaces
		var mset methodInfoSet
		var embeddeds []*ifaceInfo
		var positions []token.Pos // entries correspond to positions of embeddeds; used for error reporting
		for _, f := range iface.Methods.List {
			if len(f.Names) > 0 {
				// We have a method with name f.Names[0].
				// (The parser ensures that there's only one method
				// and we don't care if a constructed AST has more.)

				// spec: "As with all method sets, in an interface type,
				// each method must have a unique non-blank name."
				if name := f.Names[0]; name.Name == "_" {
					check.errorf(name.Pos(), "invalid method name _")
					continue // ignore
				}

				m := &methodInfo{scope: scope, src: f}
				if check.declareInMethodSet(&mset, f.Pos(), m) {
					info.methods = append(info.methods, m)
				}
			} else {
				// We have an embedded interface and f.Type is its
				// (possibly qualified) embedded type name. Collect
				// it if it's a valid interface.
				var e *ifaceInfo
				switch ename := f.Type.(type) {
				case *ast.Ident:
					e = check.infoFromTypeName(scope, ename, path)
				case *ast.SelectorExpr:
					e = check.infoFromQualifiedTypeName(scope, ename)
				default:
					// The parser makes sure we only see one of the above.
					// Constructed ASTs may contain other (invalid) nodes;
					// we simply ignore them. The full type-checking pass
					// will report those as errors later.
				}
				if e != nil {
					embeddeds = append(embeddeds, e)
					positions = append(positions, f.Type.Pos())
				}
			}
		}
		info.explicits = len(info.methods)

		// collect methods of embedded interfaces
		for i, e := range embeddeds {
			pos := positions[i] // position of type name of embedded interface
			for _, m := range e.methods {
				if check.declareInMethodSet(&mset, pos, m) {
					info.methods = append(info.methods, m)
				}
			}
		}
	}

	// mark check.interfaces as complete
	assert(info != nil)
	if tname != nil {
		check.interfaces[tname] = info
	}

	return
}

// infoFromTypeName computes the method set for the given type name
// which must denote a type whose underlying type is an interface.
// The same result qualifications apply as for infoFromTypeLit.
// infoFromTypeName should only be called from infoFromTypeLit.
func (check *Checker) infoFromTypeName(scope *Scope, name *ast.Ident, path []*TypeName) *ifaceInfo {
	// A single call of infoFromTypeName handles a sequence of (possibly
	// recursive) type declarations connected via unqualified type names.
	// Each type declaration leading to another typename causes a "tail call"
	// (goto) of this function. The general scenario looks like this:
	//
	//      ...
	//      type Pn T        // previous declarations leading to T, path = [..., Pn]
	//      type T interface { T0; ... }  // T0 leads to call of infoFromTypeName
	//
	//      // infoFromTypeName(name = T0, path = [..., Pn, T])
	//      type T0 T1       // path = [..., Pn, T, T0]
	//      type T1 T2  <-+  // path = [..., Pn, T, T0, T1]
	//      type T2 ...   |  // path = [..., Pn, T, T0, T1, T2]
	//      type Tn T1  --+  // path = [..., Pn, T, T0, T1, T2, Tn] and T1 is in path => cycle
	//
	// infoFromTypeName returns nil when such a cycle is detected. But in
	// contrast to cycles involving interfaces, we must not report the
	// error for "type name only" cycles because they will be found again
	// during type-checking of embedded interfaces. Reporting those cycles
	// here would lead to double reporting. Cycles involving embedding are
	// not reported again later because type-checking of interfaces relies
	// on the ifaceInfos computed here which are cycle-free by design.
	//
	// Remember the path length to detect "type name only" cycles.
	start := len(path)

typenameLoop:
	// name must be a type name denoting a type whose underlying type is an interface
	_, obj := scope.LookupParent(name.Name, check.pos)
	if obj == nil {
		return nil
	}
	tname, _ := obj.(*TypeName)
	if tname == nil {
		return nil
	}

	// We have a type name. It may be predeclared (error type),
	// imported (dot import), or declared by a type declaration.
	// It may not be an interface (e.g., predeclared type int).
	// Resolve it by analyzing each possible case.

	// Abort but don't report an error if we have a "type name only"
	// cycle (see big function comment).
	if check.cycle(tname, path[start:], false) {
		return nil
	}

	// Abort and report an error if we have a general cycle.
	if check.cycle(tname, path, true) {
		return nil
	}

	path = append(path, tname)

	// If tname is a package-level type declaration, it must be
	// in the objMap. Follow the RHS of that declaration if so.
	// The RHS may be a literal type (likely case), or another
	// (possibly parenthesized and/or qualified) type name.
	// (The declaration may be an alias declaration, but it
	// doesn't matter for the purpose of determining the under-
	// lying interface.)
	if decl := check.objMap[tname]; decl != nil {
		switch typ := unparen(decl.typ).(type) {
		case *ast.Ident:
			// type tname T
			name = typ
			goto typenameLoop
		case *ast.SelectorExpr:
			// type tname p.T
			return check.infoFromQualifiedTypeName(decl.file, typ)
		case *ast.InterfaceType:
			// type tname interface{...}
			// If tname is fully type-checked at this point (tname.color() == black)
			// we could use infoFromType here. But in this case, the interface must
			// be in the check.interfaces cache as well, which will be hit when we
			// call infoFromTypeLit below, and which will be faster. It is important
			// that we use that previously computed interface because its methods
			// have the correct receiver type (for github.com/wa-lang/wa/internal/types clients). Thus, the
			// check.interfaces cache must be up-to-date across even across multiple
			// check.Files calls (was bug - see issue #29029).
			return check.infoFromTypeLit(decl.file, typ, tname, path)
		}
		// type tname X // and X is not an interface type
		return nil
	}

	// If tname is not a package-level declaration, in a well-typed
	// program it should be a predeclared (error type), imported (dot
	// import), or function local declaration. Either way, it should
	// have been fully declared before use, except if there is a direct
	// cycle, and direct cycles will be caught above. Also, the denoted
	// type should be an interface (e.g., int is not an interface).
	if typ := tname.typ; typ != nil {
		// typ should be an interface
		if ityp, _ := typ.Underlying().(*Interface); ityp != nil {
			return infoFromType(ityp)
		}
	}

	// In all other cases we have some error.
	return nil
}

// infoFromQualifiedTypeName computes the method set for the given qualified type name, or nil.
func (check *Checker) infoFromQualifiedTypeName(scope *Scope, qname *ast.SelectorExpr) *ifaceInfo {
	// see also Checker.selector
	name, _ := qname.X.(*ast.Ident)
	if name == nil {
		return nil
	}
	_, obj1 := scope.LookupParent(name.Name, check.pos)
	if obj1 == nil {
		return nil
	}
	pname, _ := obj1.(*PkgName)
	if pname == nil {
		return nil
	}
	assert(pname.pkg == check.pkg)
	obj2 := pname.imported.scope.Lookup(qname.Sel.Name)
	if obj2 == nil || !obj2.Exported() {
		return nil
	}
	tname, _ := obj2.(*TypeName)
	if tname == nil {
		return nil
	}
	ityp, _ := tname.typ.Underlying().(*Interface)
	if ityp == nil {
		return nil
	}
	return infoFromType(ityp)
}

// infoFromType computes the method set for the given interface type.
// The result is never nil.
func infoFromType(typ *Interface) *ifaceInfo {
	assert(typ.allMethods != nil) // typ must be completely set up

	// fast track for empty interface
	n := len(typ.allMethods)
	if n == 0 {
		return &emptyIfaceInfo
	}

	info := new(ifaceInfo)
	info.explicits = len(typ.methods)
	info.methods = make([]*methodInfo, n)

	// If there are no embedded interfaces, simply collect the
	// explicitly declared methods (optimization of common case).
	if len(typ.methods) == n {
		for i, m := range typ.methods {
			info.methods[i] = &methodInfo{fun: m}
		}
		return info
	}

	// Interface types have a separate list for explicitly declared methods
	// which shares its methods with the list of all (explicitly declared or
	// embedded) methods. Collect all methods in a set so we can separate
	// the embedded methods from the explicitly declared ones.
	all := make(map[*Func]bool, n)
	for _, m := range typ.allMethods {
		all[m] = true
	}
	assert(len(all) == n) // methods must be unique

	// collect explicitly declared methods
	info.methods = make([]*methodInfo, n)
	for i, m := range typ.methods {
		info.methods[i] = &methodInfo{fun: m}
		delete(all, m)
	}

	// collect remaining (embedded) methods
	i := len(typ.methods)
	for m := range all {
		info.methods[i] = &methodInfo{fun: m}
		i++
	}
	assert(i == n)

	return info
}
