// 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 main

import (
	"go/build"
	"log"
	"os"
	"path"
	"path/filepath"
	"strings"
)

// Dirs is a structure for scanning the directory tree.
// Its Next method returns the next Go source directory it finds.
// Although it can be used to scan the tree multiple times, it
// only walks the tree once, caching the data it finds.
type Dirs struct {
	scan   chan string // directories generated by walk.
	paths  []string    // Cache of known paths.
	offset int         // Counter for Next.
}

var dirs Dirs

func init() {
	dirs.paths = make([]string, 0, 1000)
	dirs.scan = make(chan string)
	go dirs.walk()
}

// Reset puts the scan back at the beginning.
func (d *Dirs) Reset() {
	d.offset = 0
}

// Next returns the next directory in the scan. The boolean
// is false when the scan is done.
func (d *Dirs) Next() (string, bool) {
	if d.offset < len(d.paths) {
		path := d.paths[d.offset]
		d.offset++
		return path, true
	}
	path, ok := <-d.scan
	if !ok {
		return "", false
	}
	d.paths = append(d.paths, path)
	d.offset++
	return path, ok
}

// walk walks the trees in GOROOT and GOPATH.
func (d *Dirs) walk() {
	d.bfsWalkRoot(build.Default.GOROOT)
	for _, root := range splitGopath() {
		d.bfsWalkRoot(root)
	}
	close(d.scan)
}

// bfsWalkRoot walks a single directory hierarchy in breadth-first lexical order.
// Each Go source directory it finds is delivered on d.scan.
func (d *Dirs) bfsWalkRoot(root string) {
	root = path.Join(root, "src")

	// this is the queue of directories to examine in this pass.
	this := []string{}
	// next is the queue of directories to examine in the next pass.
	next := []string{root}

	for len(next) > 0 {
		this, next = next, this[0:0]
		for _, dir := range this {
			fd, err := os.Open(dir)
			if err != nil {
				log.Printf("error opening %s: %v", dir, err)
				return // TODO? There may be entry before the error.
			}
			entries, err := fd.Readdir(0)
			fd.Close()
			if err != nil {
				log.Printf("error reading %s: %v", dir, err)
				return // TODO? There may be entry before the error.
			}
			hasGoFiles := false
			for _, entry := range entries {
				name := entry.Name()
				// For plain files, remember if this directory contains any .go
				// source files, but ignore them otherwise.
				if !entry.IsDir() {
					if !hasGoFiles && strings.HasSuffix(name, ".go") {
						hasGoFiles = true
					}
					continue
				}
				// Entry is a directory.
				// No .git or other dot nonsense please.
				if strings.HasPrefix(name, ".") {
					continue
				}
				// Remember this (fully qualified) directory for the next pass.
				next = append(next, filepath.Join(dir, name))
			}
			if hasGoFiles {
				// It's a candidate.
				d.scan <- dir
			}
		}

	}
}
