// +build ignore

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

// Command mkindex creates the file "pkgindex.go" containing an index of the Go
// standard library. The file is intended to be built as part of the imports
// package, so that the package may be used in environments where a GOROOT is
// not available (such as App Engine).
package imports

import (
	"bytes"
	"fmt"
	"go/ast"
	"go/build"
	"go/format"
	"go/parser"
	"go/token"
	"io/ioutil"
	"log"
	"os"
	"path"
	"path/filepath"
	"strings"
)

var (
	pkgIndex = make(map[string][]pkg)
	exports  = make(map[string]map[string]bool)
)

func main() {
	// Don't use GOPATH.
	ctx := build.Default
	ctx.GOPATH = ""

	// Populate pkgIndex global from GOROOT.
	for _, path := range ctx.SrcDirs() {
		f, err := os.Open(path)
		if err != nil {
			log.Print(err)
			continue
		}
		children, err := f.Readdir(-1)
		f.Close()
		if err != nil {
			log.Print(err)
			continue
		}
		for _, child := range children {
			if child.IsDir() {
				loadPkg(path, child.Name())
			}
		}
	}
	// Populate exports global.
	for _, ps := range pkgIndex {
		for _, p := range ps {
			e := loadExports(p.dir)
			if e != nil {
				exports[p.dir] = e
			}
		}
	}

	// Construct source file.
	var buf bytes.Buffer
	fmt.Fprint(&buf, pkgIndexHead)
	fmt.Fprintf(&buf, "var pkgIndexMaster = %#v\n", pkgIndex)
	fmt.Fprintf(&buf, "var exportsMaster = %#v\n", exports)
	src := buf.Bytes()

	// Replace main.pkg type name with pkg.
	src = bytes.Replace(src, []byte("main.pkg"), []byte("pkg"), -1)
	// Replace actual GOROOT with "/go".
	src = bytes.Replace(src, []byte(ctx.GOROOT), []byte("/go"), -1)
	// Add some line wrapping.
	src = bytes.Replace(src, []byte("}, "), []byte("},\n"), -1)
	src = bytes.Replace(src, []byte("true, "), []byte("true,\n"), -1)

	var err error
	src, err = format.Source(src)
	if err != nil {
		log.Fatal(err)
	}

	// Write out source file.
	err = ioutil.WriteFile("pkgindex.go", src, 0644)
	if err != nil {
		log.Fatal(err)
	}
}

const pkgIndexHead = `package imports

func init() {
	pkgIndexOnce.Do(func() {
		pkgIndex.m = pkgIndexMaster
	})
	loadExports = func(dir string) map[string]bool {
		return exportsMaster[dir]
	}
}
`

type pkg struct {
	importpath string // full pkg import path, e.g. "net/http"
	dir        string // absolute file path to pkg directory e.g. "/usr/lib/go/src/fmt"
}

var fset = token.NewFileSet()

func loadPkg(root, importpath string) {
	shortName := path.Base(importpath)
	if shortName == "testdata" {
		return
	}

	dir := filepath.Join(root, importpath)
	pkgIndex[shortName] = append(pkgIndex[shortName], pkg{
		importpath: importpath,
		dir:        dir,
	})

	pkgDir, err := os.Open(dir)
	if err != nil {
		return
	}
	children, err := pkgDir.Readdir(-1)
	pkgDir.Close()
	if err != nil {
		return
	}
	for _, child := range children {
		name := child.Name()
		if name == "" {
			continue
		}
		if c := name[0]; c == '.' || ('0' <= c && c <= '9') {
			continue
		}
		if child.IsDir() {
			loadPkg(root, filepath.Join(importpath, name))
		}
	}
}

func loadExports(dir string) map[string]bool {
	exports := make(map[string]bool)
	buildPkg, err := build.ImportDir(dir, 0)
	if err != nil {
		if strings.Contains(err.Error(), "no buildable Go source files in") {
			return nil
		}
		log.Printf("could not import %q: %v", dir, err)
		return nil
	}
	for _, file := range buildPkg.GoFiles {
		f, err := parser.ParseFile(fset, filepath.Join(dir, file), nil, 0)
		if err != nil {
			log.Printf("could not parse %q: %v", file, err)
			continue
		}
		for name := range f.Scope.Objects {
			if ast.IsExported(name) {
				exports[name] = true
			}
		}
	}
	return exports
}
