// Copyright 2014 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.

// This file implements writing of types. The functionality is lifted
// directly from go/types, but now contains various modifications for
// nicer output.
//
// TODO(gri) back-port once we have a fixed interface and once the
// go/types API is not frozen anymore for the 1.3 release; and remove
// this implementation if possible.

package main

import (
	"go/types"
	"slices"
)

func (p *printer) writeType(this *types.Package, typ types.Type) {
	p.writeTypeInternal(this, typ, make([]types.Type, 8))
}

// From go/types - leave for now to ease back-porting this code.
const GcCompatibilityMode = false

func (p *printer) writeTypeInternal(this *types.Package, typ types.Type, visited []types.Type) {
	// Theoretically, this is a quadratic lookup algorithm, but in
	// practice deeply nested composite types with unnamed component
	// types are uncommon. This code is likely more efficient than
	// using a map.
	if slices.Contains(visited, typ) {
		p.printf("○%T", typ) // cycle to typ
		return
	}
	visited = append(visited, typ)

	switch t := typ.(type) {
	case nil:
		p.print("<nil>")

	case *types.Basic:
		if t.Kind() == types.UnsafePointer {
			p.print("unsafe.")
		}
		if GcCompatibilityMode {
			// forget the alias names
			switch t.Kind() {
			case types.Byte:
				t = types.Typ[types.Uint8]
			case types.Rune:
				t = types.Typ[types.Int32]
			}
		}
		p.print(t.Name())

	case *types.Array:
		p.printf("[%d]", t.Len())
		p.writeTypeInternal(this, t.Elem(), visited)

	case *types.Slice:
		p.print("[]")
		p.writeTypeInternal(this, t.Elem(), visited)

	case *types.Struct:
		n := t.NumFields()
		if n == 0 {
			p.print("struct{}")
			return
		}

		p.print("struct {\n")
		p.indent++
		for i := range n {
			f := t.Field(i)
			if !f.Anonymous() {
				p.printf("%s ", f.Name())
			}
			p.writeTypeInternal(this, f.Type(), visited)
			if tag := t.Tag(i); tag != "" {
				p.printf(" %q", tag)
			}
			p.print("\n")
		}
		p.indent--
		p.print("}")

	case *types.Pointer:
		p.print("*")
		p.writeTypeInternal(this, t.Elem(), visited)

	case *types.Tuple:
		p.writeTuple(this, t, false, visited)

	case *types.Signature:
		p.print("func")
		p.writeSignatureInternal(this, t, visited)

	case *types.Interface:
		// We write the source-level methods and embedded types rather
		// than the actual method set since resolved method signatures
		// may have non-printable cycles if parameters have anonymous
		// interface types that (directly or indirectly) embed the
		// current interface. For instance, consider the result type
		// of m:
		//
		//     type T interface{
		//         m() interface{ T }
		//     }
		//
		n := t.NumMethods()
		if n == 0 {
			p.print("any")
			return
		}

		p.print("interface {\n")
		p.indent++
		if GcCompatibilityMode {
			// print flattened interface
			// (useful to compare against gc-generated interfaces)
			for i := range n {
				m := t.Method(i)
				p.print(m.Name())
				p.writeSignatureInternal(this, m.Type().(*types.Signature), visited)
				p.print("\n")
			}
		} else {
			// print explicit interface methods and embedded types
			for i, n := 0, t.NumExplicitMethods(); i < n; i++ {
				m := t.ExplicitMethod(i)
				p.print(m.Name())
				p.writeSignatureInternal(this, m.Type().(*types.Signature), visited)
				p.print("\n")
			}
			for i, n := 0, t.NumEmbeddeds(); i < n; i++ {
				typ := t.EmbeddedType(i)
				p.writeTypeInternal(this, typ, visited)
				p.print("\n")
			}
		}
		p.indent--
		p.print("}")

	case *types.Map:
		p.print("map[")
		p.writeTypeInternal(this, t.Key(), visited)
		p.print("]")
		p.writeTypeInternal(this, t.Elem(), visited)

	case *types.Chan:
		var s string
		var parens bool
		switch t.Dir() {
		case types.SendRecv:
			s = "chan "
			// chan (<-chan T) requires parentheses
			if c, _ := t.Elem().(*types.Chan); c != nil && c.Dir() == types.RecvOnly {
				parens = true
			}
		case types.SendOnly:
			s = "chan<- "
		case types.RecvOnly:
			s = "<-chan "
		default:
			panic("unreachable")
		}
		p.print(s)
		if parens {
			p.print("(")
		}
		p.writeTypeInternal(this, t.Elem(), visited)
		if parens {
			p.print(")")
		}

	case *types.Alias:
		// TODO(adonovan): display something aliasy.
		p.writeTypeInternal(this, types.Unalias(t), visited)

	case *types.Named:
		s := "<Named w/o object>"
		if obj := t.Obj(); obj != nil {
			if pkg := obj.Pkg(); pkg != nil {
				if pkg != this {
					p.print(pkg.Path())
					p.print(".")
				}
				// TODO(gri): function-local named types should be displayed
				// differently from named types at package level to avoid
				// ambiguity.
			}
			s = obj.Name()
		}
		p.print(s)

	default:
		// For externally defined implementations of Type.
		p.print(t.String())
	}
}

func (p *printer) writeTuple(this *types.Package, tup *types.Tuple, variadic bool, visited []types.Type) {
	p.print("(")
	for i, n := 0, tup.Len(); i < n; i++ {
		if i > 0 {
			p.print(", ")
		}
		v := tup.At(i)
		if name := v.Name(); name != "" {
			p.print(name)
			p.print(" ")
		}
		typ := v.Type()
		if variadic && i == n-1 {
			p.print("...")
			typ = typ.(*types.Slice).Elem()
		}
		p.writeTypeInternal(this, typ, visited)
	}
	p.print(")")
}

func (p *printer) writeSignature(this *types.Package, sig *types.Signature) {
	p.writeSignatureInternal(this, sig, make([]types.Type, 8))
}

func (p *printer) writeSignatureInternal(this *types.Package, sig *types.Signature, visited []types.Type) {
	p.writeTuple(this, sig.Params(), sig.Variadic(), visited)

	res := sig.Results()
	n := res.Len()
	if n == 0 {
		// no result
		return
	}

	p.print(" ")
	if n == 1 && res.At(0).Name() == "" {
		// single unnamed result
		p.writeTypeInternal(this, res.At(0).Type(), visited)
		return
	}

	// multiple or named result(s)
	p.writeTuple(this, res, false, visited)
}
