package main

import (
	"encoding/json"
	"fmt"
	"os"
	"regexp"
	"runtime"
	"sort"
	"strconv"
	"strings"
	"text/template"

	"github.com/urfave/cli"

	"pagure.io/golist/pkg/util"
)

var (
	version = "0.10.1"
)

func main() {
	app := cli.NewApp()
	app.Name = "golist"
	app.Usage = "List Go project resources"
	app.Version = fmt.Sprintf("%s (built with %s)", version, runtime.Version())

	app.Flags = []cli.Flag{
		cli.StringSliceFlag{
			Name:  "ignore-dir, d",
			Usage: "Directory to ignore",
		},
		cli.StringSliceFlag{
			Name:  "ignore-tree, t",
			Usage: "Directory tree to ignore",
		},
		cli.StringSliceFlag{
			Name:  "ignore-regex, r",
			Usage: "Regex specified files/dirs to ignore",
		},
		cli.StringSliceFlag{
			Name:  "package-path",
			Usage: "Package entry point",
		},
		cli.BoolFlag{
			Name:  "all-deps",
			Usage: "List imported packages including stdlib",
		},
		cli.BoolFlag{
			Name:  "provided",
			Usage: "List provided packages",
		},
		cli.BoolFlag{
			Name:  "imported",
			Usage: "List imported packages",
		},
		cli.BoolFlag{
			Name:  "skip-self",
			Usage: "Skip imported packages with the same --package-path",
		},
		cli.BoolFlag{
			Name:  "tests",
			Usage: "Apply the listing options over tests only",
		},
		cli.BoolFlag{
			Name:  "with-tests",
			Usage: "Apply the listing options over both the main package and the tests",
		},
		cli.BoolFlag{
			Name:  "show-main",
			Usage: "Including main files in listings",
		},
		cli.BoolFlag{
			Name:  "to-install",
			Usage: "List all resources recognized as essential part of the Go project",
		},
		cli.StringSliceFlag{
			Name:  "include-extension, e",
			Usage: "Include all files with the extension in the recognized resources, e.g. .proto, .tmpl",
			Value: &cli.StringSlice{".proto", ".md", ".s"},
		},
		cli.BoolFlag{
			Name:  "json",
			Usage: "Output as JSON artefact",
		},
		cli.StringFlag{
			Name:  "template",
			Usage: "Template used to print output",
			Value: `{{.}}\n`,
		},
	}

	getTemplate := func(output_fmt string) (*template.Template, error) {
		str, err := strconv.Unquote(`"` + output_fmt + `"`)
		if err != nil {
			return nil, fmt.Errorf("Unparseable template string: %v", err)
		}
		return template.New("output").Parse(str)
	}

	app.Action = func(c *cli.Context) error {

		if len(c.StringSlice("package-path")) == 0 {
			return fmt.Errorf("--package-path is not set")
		}

		if !c.Bool("provided") && !c.Bool("imported") && !c.Bool("to-install") && !c.Bool("json") {
			return fmt.Errorf("At least one of --provided, --imported, --to-install or --json must be set")
		}

		tmpl, err := getTemplate(c.String("template"))
		if err != nil {
			return err
		}

		ignore := &util.Ignore{}

		for _, dir := range c.StringSlice("ignore-tree") {
			// skip all ignored dirs that are prefixes of the package-path
			if strings.HasPrefix(c.String("package-path"), dir) {
				continue
			}
			ignore.Trees = append(ignore.Trees, dir)
		}

		for _, dir := range c.StringSlice("ignore-dir") {
			// skip all ignored dirs that are prefixes of the package-path
			if strings.HasPrefix(c.String("package-path"), dir) && c.String("package-path") != dir {
				continue
			}
			ignore.Dirs = append(ignore.Dirs, dir)
		}

		for _, dir := range c.StringSlice("ignore-regex") {
			ignore.Regexes = append(ignore.Regexes, regexp.MustCompile(dir))
		}

		collector := util.NewPackageInfoCollector(ignore, c.StringSlice("include-extension"))
		for _, packagePath := range c.StringSlice("package-path") {
			if err := collector.CollectPackageInfos(packagePath); err != nil {
				return err
			}
		}

		if c.Bool("json") {
			// Collect everything
			artifact, _ := collector.BuildArtifact()
			str, _ := json.Marshal(artifact)
			fmt.Printf("%v\n", string(str))
			return nil
		}

		if c.Bool("provided") {
			if c.Bool("with-tests") && c.Bool("tests") {
				return fmt.Errorf("Both --with-tests and --tests cannot be set at the same time")
			}

			pkgs, err := collector.BuildPackageTree(c.Bool("show-main"), c.Bool("with-tests"), c.Bool("tests"))
			if err != nil {
				return err
			}
			sort.Strings(pkgs)
			for _, item := range pkgs {
				err := tmpl.Execute(os.Stdout, item)
				if err != nil {
					return err
				}
			}
			return nil
		}

		if c.Bool("imported") {
			if c.Bool("with-tests") && c.Bool("tests") {
				return fmt.Errorf("Both --with-tests and --tests cannot be set at the same time")
			}

			pkgs, err := collector.CollectProjectDeps(c.Bool("all-deps"), c.Bool("skip-self"), c.Bool("with-tests"), c.Bool("tests"))
			if err != nil {
				return err
			}
			sort.Strings(pkgs)
			for _, item := range pkgs {
				err := tmpl.Execute(os.Stdout, item)
				if err != nil {
					return err
				}
			}
			return nil
		}

		if c.Bool("to-install") {
			pkgs, err := collector.CollectInstalledResources()
			if err != nil {
				return err
			}
			sort.Strings(pkgs)
			for _, item := range pkgs {
				err := tmpl.Execute(os.Stdout, item)
				if err != nil {
					return err
				}
			}
			return nil
		}

		return nil
	}

	if err := app.Run(os.Args); err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		os.Exit(1)
	}

}
