package main

import (
	"bufio"
	"fmt"
	"os"
	"path/filepath"
	"strings"
)

// runGuided is what happens when somebody double-clicks the program instead of
// typing its name at a prompt.
//
// Without this, Explorer opens a console, main() finds no arguments, prints
// the usage text to stderr and exits — and Windows destroys the window in the
// same instant. From the other side of the screen that is indistinguishable
// from a crash. So when we know we were double-clicked, we ask the two
// questions the program actually needs and stay on screen until the reader is
// done.
//
// The guided session runs "scan" and only ever "scan". EraseProof's other job
// is to overwrite files with random data and remove them; that is irreversible
// by design and has no quarantine and no undo. Nobody should be led into it by
// a program they double-clicked to see what it was. Erasing stays on the
// command line, where it takes a deliberate flag.
//
// This path is entered ONLY when there are no arguments and both ends of the
// program are a real console. Any scripted or piped use takes exactly the same
// code path it always did.
func runGuided() {
	in := bufio.NewScanner(os.Stdin)

	fmt.Println()
	fmt.Println("  EraseProof")
	fmt.Println("  See which files your erasure rules are picking up.")
	fmt.Println()
	fmt.Println("  Your rules live in a settings file. Each rule names a folder, the")
	fmt.Println("  kind of file it covers and how old it has to be. This shows you")
	fmt.Println("  exactly which files a rule matches right now, and how much they")
	fmt.Println("  come to.")
	fmt.Println()
	fmt.Println("  It only looks. Nothing is erased, and nothing is written down.")
	fmt.Println()

	path, policies := askPolicyFile(in)
	if path == "" {
		return
	}
	name := askPolicyName(in, policies)
	if name == "" {
		return
	}

	fmt.Println()
	fmt.Println("  Looking. On a folder with a lot of files this can take a moment.")
	fmt.Println()
	cmdScan([]string{"--policy", path, name})

	fmt.Println()
	fmt.Println("  Done. Everything above is still exactly where it was.")
	fmt.Println("  The command-line version is the one that erases, and it keeps a")
	fmt.Println("  permanent record of every file it destroys: eraseproof --help")
	pause(in)
}

// askPolicyFile asks for the settings file and keeps asking until it finds one
// it can actually read and that has at least one rule in it. It returns the
// path and the rules; an empty path means stdin closed and there is nobody
// left to ask.
func askPolicyFile(in *bufio.Scanner) (string, []policy) {
	suggested := suggestedPolicyFile()
	for {
		fmt.Println("  Where is your settings file?")
		if suggested != "" {
			fmt.Printf("  (press Enter for %s)\n", suggested)
		}
		fmt.Print("  > ")

		if !in.Scan() {
			return "", nil
		}
		answer := strings.TrimSpace(in.Text())
		answer = strings.Trim(answer, `"`)
		if answer == "" {
			answer = suggested
		}
		if answer == "" {
			fmt.Println()
			fmt.Println("  I need the settings file that holds your rules. It is the one")
			fmt.Println("  you pass to --policy, and it ends in .json.")
			fmt.Println("  Try again, or close this window.")
			fmt.Println()
			continue
		}

		info, err := os.Stat(answer)
		switch {
		case err != nil:
			fmt.Println()
			fmt.Printf("  I cannot find %q.\n", answer)
			fmt.Println("  Tip: you can drag the file, or the folder holding it, from")
			fmt.Println("  Explorer onto this window to paste its location, then press")
			fmt.Println("  Enter.")
			fmt.Println()
			continue
		case info.IsDir():
			fmt.Println()
			fmt.Printf("  %q is a folder. I need the settings file inside it.\n", answer)
			fmt.Println()
			continue
		}

		policies, err := loadPolicies(answer)
		if err != nil {
			fmt.Println()
			fmt.Printf("  I could not make sense of %q.\n", answer)
			fmt.Println("  It should be the JSON settings file that lists your rules.")
			fmt.Println()
			continue
		}
		if len(policies) == 0 {
			fmt.Println()
			fmt.Printf("  %q has no rules in it yet, so there is nothing to look at.\n", answer)
			fmt.Println()
			continue
		}
		return answer, policies
	}
}

// askPolicyName asks which rule to look at, showing the ones that exist so the
// reader is choosing from a list rather than guessing a name.
func askPolicyName(in *bufio.Scanner, policies []policy) string {
	first := policies[0].Name
	for {
		fmt.Println()
		fmt.Println("  Which rule shall I check?")
		for _, p := range policies {
			fmt.Printf("    %-20s %s\n", p.Name, p.TargetDir)
		}
		fmt.Printf("  (press Enter for %s)\n", first)
		fmt.Print("  > ")

		if !in.Scan() {
			return ""
		}
		answer := strings.TrimSpace(in.Text())
		answer = strings.Trim(answer, `"`)
		if answer == "" {
			answer = first
		}
		for _, p := range policies {
			if p.Name == answer {
				return answer
			}
		}
		fmt.Println()
		fmt.Printf("  There is no rule called %q in this file.\n", answer)
		fmt.Println("  Type one of the names above, or close this window.")
	}
}

// suggestedPolicyFile offers a settings file the reader can accept with one
// keypress, but only when one is really there: offering a name that does not
// exist would send the very first keypress straight into an error.
func suggestedPolicyFile() string {
	var dirs []string
	if wd, err := os.Getwd(); err == nil {
		dirs = append(dirs, wd)
	}
	if home, err := os.UserHomeDir(); err == nil {
		dirs = append(dirs, home)
	}
	for _, dir := range dirs {
		for _, name := range []string{"eraseproof-policies.json", "policies.json"} {
			candidate := filepath.Join(dir, name)
			if info, err := os.Stat(candidate); err == nil && !info.IsDir() {
				return candidate
			}
		}
	}
	return ""
}

// pause keeps the console window open. Explorer closes it the moment the
// process exits, so without this the reader never sees the output.
func pause(in *bufio.Scanner) {
	fmt.Println()
	fmt.Print("  Press Enter to close this window. ")
	in.Scan()
}
