package main

import (
	"bytes"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/google/blueprint/parser"
)

var (
	result	= make(map[string]string)
	defaults = make(map[string]string)
	Root = ""
)

var (
	exitCode = 0
)

func report(err error) {
	fmt.Fprintln(os.Stderr, err)
	exitCode = 2
}

func usage() {
	usageViolation("")
}

func usageViolation(violation string) {
	fmt.Fprintln(os.Stderr, violation)
	fmt.Fprintln(os.Stderr, "usage: fuzzparser [flags] [path ...]")
	flag.PrintDefaults()
	os.Exit(2)
}

func processFile(filename string, out io.Writer) error {
	f, err := os.Open(filename)
	if err != nil {
		return err
	}
	defer f.Close()

	return processReader(filename, f, out)
}

func processReader(filename string, in io.Reader, out io.Writer) error {
	src, err := ioutil.ReadAll(in)
	if err != nil {
		return err
	}

	r := bytes.NewBuffer(src)
	file, errs := parser.ParseAndEval(filename, r, parser.NewScope(nil))

	modules := findModules(file)
	// First collect all the defaults into a dictionary
	for _, mod := range modules {
	    if mod.Type == "cc_defaults" {
	    	default_name := ""
	    	for _, prop := range mod.Map.Properties {
				if prop.Name == "name" {
					value := prop.Value.String()
					default_name = value[1:strings.Index(value, "@")-1]
				} else if prop.Name == "static_libs" || prop.Name == "shared_libs" {
					value := prop.Value.String()
					for strings.Index(value, "\"") > -1 {
						value = value[strings.Index(value, "\"")+1:]
						lib_name := value[:strings.Index(value, "\"")]
						if _ , ok := defaults[default_name]; ok {
							defaults[default_name] += "," + lib_name
						} else {
							defaults[default_name] += lib_name
						}
						value = value[strings.Index(value, "\"")+1:]
					}
				} else if prop.Name == "defaults" {
					// Get the defaults of the default
					value := prop.Value.String()
					for strings.Index(value, "\"") > -1 {
						value = value[strings.Index(value, "\"")+1:]
						sub_default_name := value[:strings.Index(value, "\"")]
						if _ , ok := defaults[default_name]; ok {
							defaults[default_name] += "," + defaults[sub_default_name]
						} else {
							defaults[default_name] += defaults[sub_default_name]
						}
						value = value[strings.Index(value, "\"")+1:]
					}
				} else if prop.Name == "target" {
					value := prop.Value.String()
					if default_name == "binder_fuzz_defaults" {
						fmt.Printf("---> target value for %s: %s\n", default_name ,value)
					}
					for strings.Index(value, "\"") > -1 {
						value = value[strings.Index(value, "\"")+1:]
						lib := value[:strings.Index(value, "\"")]
						if _ , ok := defaults[default_name]; ok {
							defaults[default_name] += "," + lib
						} else {
							defaults[default_name] += lib
						}
						value = value[strings.Index(value, "\"")+1:]
					}
				}
			}
		}
    }

	for _, mod := range modules {
		if mod.Type == "cc_fuzz" {
			fuzzer_name := ""
			for _, prop := range mod.Map.Properties {
				// First get the name of the fuzzer
				if prop.Name == "name" {
					value := prop.Value.String()
					fuzzer_name = value[1:strings.Index(value, "@")-1]
				} else if prop.Name == "defaults" {
					value := prop.Value.String()
					if strings.Index(value, "@") == 0 {
						value = value[1:]
					}
					default_name := value[strings.Index(value, "[")+2: strings.Index(value, "@")-1]
					if _, ok := result[fuzzer_name]; ok {
						result[fuzzer_name] += "," + defaults[default_name]
					} else {
						result[fuzzer_name] += defaults[default_name]
					}
				} else if prop.Name == "static_libs" || prop.Name == "shared_libs" {
					value := prop.Value.String()
					for strings.Index(value, "\"") > -1 {
						value = value[strings.Index(value, "\"")+1:]
						lib_name := value[:strings.Index(value, "\"")]
						if _ , ok := result[fuzzer_name]; ok {
							result[fuzzer_name] += "," + lib_name
						} else {
							result[fuzzer_name] += lib_name
						}
						value = value[strings.Index(value, "\"")+1:]
					}
				}
			}
	    }
	}

	if len(errs) > 0 {
		for _, err := range errs {
			fmt.Fprintln(os.Stderr, err)
		}
		return fmt.Errorf("%d parsing errors", len(errs))
	}

    return err
}

func findModules(file *parser.File) (modules []*parser.Module) {
    if file != nil {
		for _, def := range file.Defs {
			if module, ok := def.(*parser.Module); ok {
				modules = append(modules, module)
			}
		}
	}
	return modules
}

func walkDir(path string) {
	visitFile := func(path string, f os.FileInfo, err error) error {
		if err == nil && f.Name() == "Android.bp" {
			err = processFile(path, os.Stdout)
		}
		if err != nil {
			fmt.Printf("ERROR")
			report(err)
		}
		return nil
	}
    fmt.Printf("Parsing %s recursively...\n", path)
	filepath.Walk(path, visitFile)
}

func main() {
	flag.Usage = usage
	flag.Parse()

	for i := 0; i < flag.NArg(); i++ {
		Root := flag.Arg(i)
		fmt.Printf("Root %s\n", Root)
		switch dir, err := os.Stat(Root); {
		case err != nil:
			report(err)
		case dir.IsDir():
			walkDir(Root)
		default:
			if err := processFile(Root, os.Stdout); err != nil {
				report(err)
			}
		}
	}

	fmt.Printf("-------------------------------------\n")
	fmt.Printf("Fuzzer name -------Library name------\n")
	if len(result) > 0 {
		for k, v := range result {
			if len(v) == 0 {
				v = "NOT FOUND"
			}
			fmt.Printf("%s:%s\n", k, v)
		}
	}

	os.Exit(exitCode)
}