// Copyright 2009 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 ast

import "go/token"


func filterIdentList(list []*Ident) []*Ident {
	j := 0;
	for _, x := range list {
		if x.IsExported() {
			list[j] = x;
			j++;
		}
	}
	return list[0:j];
}


// isExportedType assumes that typ is a correct type.
func isExportedType(typ Expr) bool {
	switch t := typ.(type) {
	case *Ident:
		return t.IsExported();
	case *ParenExpr:
		return isExportedType(t.X);
	case *SelectorExpr:
		// assume t.X is a typename
		return t.Sel.IsExported();
	case *StarExpr:
		return isExportedType(t.X);
	}
	return false;
}


func filterFieldList(list []*Field, incomplete *bool) []*Field {
	j := 0;
	for _, f := range list {
		exported := false;
		if len(f.Names) == 0 {
			// anonymous field
			// (Note that a non-exported anonymous field
			// may still refer to a type with exported
			// fields, so this is not absolutely correct.
			// However, this cannot be done w/o complete
			// type information.)
			exported = isExportedType(f.Type);
		} else {
			n := len(f.Names);
			f.Names = filterIdentList(f.Names);
			if len(f.Names) < n {
				*incomplete = true;
			}
			exported = len(f.Names) > 0;
		}
		if exported {
			filterType(f.Type);
			list[j] = f;
			j++;
		}
	}
	if j < len(list) {
		*incomplete = true;
	}
	return list[0:j];
}


func filterParamList(list []*Field) {
	for _, f := range list {
		filterType(f.Type);
	}
}


var noPos token.Position

func filterType(typ Expr) {
	switch t := typ.(type) {
	case *ArrayType:
		filterType(t.Elt);
	case *StructType:
		t.Fields = filterFieldList(t.Fields, &t.Incomplete);
	case *FuncType:
		filterParamList(t.Params);
		filterParamList(t.Results);
	case *InterfaceType:
		t.Methods = filterFieldList(t.Methods, &t.Incomplete);
	case *MapType:
		filterType(t.Key);
		filterType(t.Value);
	case *ChanType:
		filterType(t.Value);
	}
}


func filterSpec(spec Spec) bool {
	switch s := spec.(type) {
	case *ValueSpec:
		s.Names = filterIdentList(s.Names);
		if len(s.Names) > 0 {
			filterType(s.Type);
			return true;
		}
	case *TypeSpec:
		// TODO(gri) consider stripping forward declarations
		//           of structs, interfaces, functions, and methods
		if s.Name.IsExported() {
			filterType(s.Type);
			return true;
		}
	}
	return false;
}


func filterSpecList(list []Spec) []Spec {
	j := 0;
	for _, s := range list {
		if filterSpec(s) {
			list[j] = s;
			j++;
		}
	}
	return list[0:j];
}


func filterDecl(decl Decl) bool {
	switch d := decl.(type) {
	case *GenDecl:
		d.Specs = filterSpecList(d.Specs);
		return len(d.Specs) > 0;
	case *FuncDecl:
		// TODO consider removing function declaration altogether if
		//      forward declaration (i.e., if d.Body == nil) because
		//      in that case the actual declaration will come later.
		d.Body = nil;	// strip body
		return d.Name.IsExported();
	}
	return false;
}


// FileExports trims the AST for a Go source file in place such that only
// exported nodes remain: all top-level identifiers which are not exported
// and their associated information (such as type, initial value, or function
// body) are removed. Non-exported fields and methods of exported types are
// stripped, and the function bodies of exported functions are set to nil.
// The File.comments list is not changed.
//
// FileExports returns true if there is an exported declaration; it returns
// false otherwise.
//
func FileExports(src *File) bool {
	j := 0;
	for _, d := range src.Decls {
		if filterDecl(d) {
			src.Decls[j] = d;
			j++;
		}
	}
	src.Decls = src.Decls[0:j];
	return j > 0;
}


// PackageExports trims the AST for a Go package in place such that only
// exported nodes remain. The pkg.Files list is not changed, so that file
// names and top-level package comments don't get lost.
//
// PackageExports returns true if there is an exported declaration; it
// returns false otherwise.
//
func PackageExports(pkg *Package) bool {
	hasExports := false;
	for _, f := range pkg.Files {
		if FileExports(f) {
			hasExports = true;
		}
	}
	return hasExports;
}


// separator is an empty //-style comment that is interspersed between
// different comment groups when they are concatenated into a single group
//
var separator = &Comment{noPos, []byte{'/', '/'}}


// MergePackageFiles creates a file AST by merging the ASTs of the
// files belonging to a package.
//
func MergePackageFiles(pkg *Package) *File {
	// Count the number of package comments and declarations across
	// all package files.
	ncomments := 0;
	ndecls := 0;
	for _, f := range pkg.Files {
		if f.Doc != nil {
			ncomments += len(f.Doc.List)+1;	// +1 for separator
		}
		ndecls += len(f.Decls);
	}

	// Collect package comments from all package files into a single
	// CommentGroup - the collected package documentation. The order
	// is unspecified. In general there should be only one file with
	// a package comment; but it's better to collect extra comments
	// than drop them on the floor.
	var doc *CommentGroup;
	if ncomments > 0 {
		list := make([]*Comment, ncomments - 1);	// -1: no separator before first group
		i := 0;
		for _, f := range pkg.Files {
			if f.Doc != nil {
				if i > 0 {
					// not the first group - add separator
					list[i] = separator;
					i++;
				}
				for _, c := range f.Doc.List {
					list[i] = c;
					i++;
				}
			}
		}
		doc = &CommentGroup{list, nil};
	}

	// Collect declarations from all package files.
	var decls []Decl;
	if ndecls > 0 {
		decls = make([]Decl, ndecls);
		i := 0;
		for _, f := range pkg.Files {
			for _, d := range f.Decls {
				decls[i] = d;
				i++;
			}
		}
	}

	// TODO(gri) Should collect comments as well. For that the comment
	//           list should be changed back into a []*CommentGroup,
	//           otherwise need to modify the existing linked list.
	return &File{doc, noPos, &Ident{noPos, pkg.Name}, decls, nil};
}
