package main

// The "implements" tool uses code analysis and the conventions of the go-ceph
// project to produce a report comparing what go-ceph implements and what
// exists in the C ceph APIs. Only (lib)cephfs, (lib)rados, and (lib)rbd are
// and the equilvaent go-ceph packages are supported.
//
// Examples:
//   # generate a full report on cephfs with a function listing
//   ./implements --list ./cephfs
//
//   # generate a brief summary on all packages
//   ./implements ./cephfs ./rados ./rbd
//
//   # generate a comprehensive report on rbd in json
//   ./implements --list --json rbd

import (
	"flag"
	"implements/internal/implements"
	"log"
	"os"
)

var (
	verbose    bool
	list       bool
	reportJSON bool
	outputJSON string
	outputText string

	// verbose logger
	logger = log.New(os.Stderr, "(implements/verbose) ", log.LstdFlags)
)

func abort(msg string) {
	log.Fatalf("error: %v\n", msg)
}

func init() {
	flag.BoolVar(&verbose, "verbose", false, "be more verbose (for debugging)")
	flag.BoolVar(&list, "list", false, "list functions")
	flag.BoolVar(&reportJSON, "json", false,
		"use JSON output format (this is a shortcut for '--report-json -')")

	flag.StringVar(&outputJSON, "report-json", "", "filename for JSON report")
	flag.StringVar(&outputText, "report-text", "", "filename for plain-text report")
}

// TODO: this is a stub implementation that doesn't do anything.
// the intent is to eventually be able to tell a go-ceph sub-package
// like "rbd" or "cephfs/admin" apart from a real path like "/home/foo/go-ceph/rbd".
func splitPkg(s string) (string, string) {
	return "", s
}

func main() {
	flag.Parse()
	args := flag.Args()
	if len(args) < 1 {
		abort("missing package(s) to analyze")
	}
	if verbose {
		implements.SetLogger(logger)
	}

	rpts := []implements.Reporter{}
	// always annotate for now, leave the option of disabling it someday if it
	// gets costly
	o := implements.ReportOptions{
		List:     list,
		Annotate: true,
	}
	switch {
	case reportJSON:
		rpts = append(rpts, implements.NewJSONReport(o, os.Stdout))
	case outputJSON == "-":
		rpts = append(rpts, implements.NewJSONReport(o, os.Stdout))
	case outputJSON != "":
		f, err := os.Create(outputJSON)
		if err != nil {
			abort(err.Error())
		}
		defer func() { _ = f.Close() }()
		rpts = append(rpts, implements.NewJSONReport(o, f))
	}
	switch {
	case outputText == "-":
		rpts = append(rpts, implements.NewTextReport(o, os.Stdout))
	case outputText != "":
		f, err := os.Create(outputText)
		if err != nil {
			abort(err.Error())
		}
		defer func() { _ = f.Close() }()
		rpts = append(rpts, implements.NewTextReport(o, f))
	}
	if len(rpts) == 0 {
		// no report types were explicitly selected. Use text report to stdout.
		rpts = append(rpts, implements.NewTextReport(o, os.Stdout))
	}

	for _, pkgref := range args[0:] {
		source, pkg := splitPkg(pkgref)
		checkCLang := false
		switch pkg {
		case "cephfs", "rados", "rbd", "rados/striper":
			checkCLang = true
			if verbose {
				logger.Printf("Processing package (with C): %s\n", pkg)
			}
		default:
			if verbose {
				logger.Printf("Processing package: %s\n", pkg)
			}
		}
		if source == "" {
			source = "."
		}
		ii := implements.NewInspector()
		if checkCLang {
			if err := implements.CephCFunctions(pkg, ii); err != nil {
				abort(err.Error())
			}
		}
		if err := implements.CephGoFunctions(source, pkg, ii); err != nil {
			abort(err.Error())
		}
		for _, r := range rpts {
			if err := r.Report(pkg, ii); err != nil {
				abort(err.Error())
			}
		}
	}
	for _, r := range rpts {
		_ = r.Done()
	}
}
