package golinters

import (
	"context"
	"fmt"
	"go/build"
	"io/ioutil"
	"path/filepath"

	"github.com/golang/lint"
	"github.com/golangci/golangci-lint/pkg/result"
)

type Golint struct{}

func (Golint) Name() string {
	return "golint"
}

func (g Golint) Run(ctx context.Context, lintCtx *Context) (*result.Result, error) {
	var issues []result.Issue
	if lintCtx.Paths.IsDirsRun {
		for _, path := range lintCtx.Paths.Dirs {
			i, err := lintDir(path, lintCtx.RunCfg().Golint.MinConfidence)
			if err != nil {
				// TODO: skip and warn
				return nil, fmt.Errorf("can't lint dir %s: %s", path, err)
			}
			issues = append(issues, i...)
		}
	} else {
		i, err := lintFiles(lintCtx.RunCfg().Golint.MinConfidence, lintCtx.Paths.Files...)
		if err != nil {
			// TODO: skip and warn
			return nil, fmt.Errorf("can't lint files %s: %s", lintCtx.Paths.Files, err)
		}
		issues = append(issues, i...)
	}

	return &result.Result{
		Issues: issues,
	}, nil
}

func lintDir(dirname string, minConfidence float64) ([]result.Issue, error) {
	pkg, err := build.ImportDir(dirname, 0)
	if err != nil {
		if _, nogo := err.(*build.NoGoError); nogo {
			// Don't complain if the failure is due to no Go source files.
			return nil, nil
		}

		return nil, fmt.Errorf("can't import dir %s", dirname)
	}

	return lintImportedPackage(pkg, minConfidence)
}

func lintImportedPackage(pkg *build.Package, minConfidence float64) ([]result.Issue, error) {
	var files []string
	files = append(files, pkg.GoFiles...)
	files = append(files, pkg.CgoFiles...)
	files = append(files, pkg.TestGoFiles...)
	files = append(files, pkg.XTestGoFiles...)
	if pkg.Dir != "." {
		for i, f := range files {
			files[i] = filepath.Join(pkg.Dir, f)
		}
	}

	return lintFiles(minConfidence, files...)
}

func lintFiles(minConfidence float64, filenames ...string) ([]result.Issue, error) {
	files := make(map[string][]byte)
	for _, filename := range filenames {
		src, err := ioutil.ReadFile(filename)
		if err != nil {
			return nil, fmt.Errorf("can't read file %s: %s", filename, err)
		}
		files[filename] = src
	}

	l := new(lint.Linter)
	ps, err := l.LintFiles(files)
	if err != nil {
		return nil, fmt.Errorf("can't lint files %s: %s", filenames, err)
	}

	var issues []result.Issue
	for _, p := range ps {
		if p.Confidence >= minConfidence {
			issues = append(issues, result.Issue{
				File:       p.Position.Filename,
				LineNumber: p.Position.Line,
				Text:       p.Text,
			})
			// TODO: use p.Link and p.Category
		}
	}

	return issues, nil
}