package pkg

import (
	"flag"
	"strings"

	"gitlab.com/M0M097/pkg/lib/utils"
)

type command int

const ( // Command types, only one of these can be used at a time
	dependees command = iota
	listInstalled
	search
	showVersion
	tracking
	upgrade
	view
	viewBuild
)

type settings struct {
	update, yes, updateBlacklist, checkMD5Sum, respectGreylist, filterInstalled, noDeps bool
	root                                                                                string
	pkgNames                                                                            []string
	buildOptions                                                                        map[string][]string
	cmd                                                                                 command
}

func newSettings(args []string, DefaultPkgFile string) *settings {
	var (
		fs                 = flag.NewFlagSet(args[0], flag.ExitOnError)
		filterInstalledPtr = fs.Bool(FILTERINSTALLED_FLAG, false, "don't filter out installed packages")
		filePtr            = fs.String(FILE_FLAG, DefaultPkgFile, "path to the input file")
		listInstalledPtr   = fs.Bool(LIST_INSTALLED_FLAG, false, "list installed packages")
		md5Ptr             = fs.Bool(MD5_FLAG, false, "skip MD5Sum checks")
		noDepsPtr          = fs.Bool(NO_DEPS_FLAG, false, "don't install dependencies")
		rootPtr            = fs.String(ROOT_FLAG, "", "display dependees of the given package")
		skipGreylistPtr    = fs.Bool(RESPECTGREYLIST_FLAG, false, "ignore the greylist")
		searchPtr          = fs.Bool(SEARCH_FLAG, false, "search for a package")
		trackPtr           = fs.Bool(TRACK_FLAG, false, "track the given packages (show all dependencies)")
		updatePtr          = fs.Bool(UPDATE_FLAG, false, "update the repository")
		versionPtr         = fs.Bool(VERSION_FLAG, false, "display the version of pkg")
		viewPtr            = fs.Bool(VIEW_FLAG, false, "display information for the given packages")
		viewBuildPtr       = fs.Bool(VIEWBUILD_FLAG, false, "display build scripts for the given packages")
		yesPtr             = fs.Bool(YES_FLAG, false, "Skip all prompts")
	)

	fs.Parse(args[1:])

	if utils.Count(*versionPtr, *viewPtr, *viewBuildPtr, *trackPtr, *searchPtr, *listInstalledPtr) > 1 {
		panic("At most one of " + VIEW_FLAG + ", " + VIEWBUILD_FLAG + ", " + TRACK_FLAG +
			", " + LIST_INSTALLED_FLAG + " and " + SEARCH_FLAG + " can be used at a time")
	}

	var cmd command
	switch {
	case *listInstalledPtr:
		cmd = listInstalled
	case *rootPtr != "":
		cmd = dependees
	case *searchPtr:
		cmd = search
	case *versionPtr:
		cmd = showVersion
	case *trackPtr:
		cmd = tracking
	case *viewPtr:
		cmd = view
	case *viewBuildPtr:
		cmd = viewBuild
	default:
		cmd = upgrade
	}

	pkgNames, buildOptions := pkgNamesAndBuildOptions(fs.Args())
	pkgs_from_file := len(pkgNames) == 0
	switch {
	case *versionPtr: // We only need to display the version anyway
	case pkgs_from_file: // Read the list of packages to upgrade
		pkgNames, buildOptions = pkgNamesAndBuildOptions(utils.ReadWordsFromFile(*filePtr))
	case *filePtr != DefaultPkgFile:
		panic("Cannot use positional arguments and -f together")
	}

	return &settings{
		buildOptions:    buildOptions,
		cmd:             cmd,
		checkMD5Sum:     !*md5Ptr,
		noDeps:          *noDepsPtr,
		filterInstalled: !*filterInstalledPtr,
		respectGreylist: !*skipGreylistPtr,
		root:            *rootPtr,
		pkgNames:        pkgNames,
		updateBlacklist: (*filePtr == DefaultPkgFile) && pkgs_from_file && !*noDepsPtr,
		update:          *updatePtr,
		yes:             *yesPtr,
	}
}

// Getters
func (s *settings) BuildOptions() map[string][]string { return s.buildOptions }
func (s *settings) Cmd() command                      { return s.cmd }
func (s *settings) CheckMD5Sum() bool                 { return s.checkMD5Sum }
func (s *settings) NoDeps() bool                      { return s.noDeps }
func (s *settings) FilterInstalled() bool             { return s.filterInstalled }
func (s *settings) RespectGreylist() bool             { return s.respectGreylist }
func (s *settings) Root() string                      { return s.root }
func (s *settings) PkgNames() []string                { return s.pkgNames }
func (s *settings) UpdateBlacklist() bool             { return s.updateBlacklist }
func (s *settings) Update() bool                      { return s.update }
func (s *settings) Yes() bool                         { return s.yes }

// PkgNamesAndBuildOptions parses the package names and build options from a
// slice of strings. The input slice is expected to contain package names
// followed by build options in the format "name=option".
func pkgNamesAndBuildOptions(s []string) ([]string, map[string][]string) {
	buildOptions := make(map[string][]string)
	pkgNames := make([]string, 0)
	for i := 0; i < len(s); {
		name := s[i]
		pkgNames = append(pkgNames, name)
		for i++; i < len(s) && strings.Contains(s[i], "="); i++ {
			buildOptions[name] = append(buildOptions[name], s[i])
		}
	}
	return pkgNames, buildOptions
}
