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 one
// question the program actually needs and stay on screen until the reader is
// done.
//
// 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.
//
// Guided mode runs "inspect", which reads the folder and writes nothing at
// all — not even a manifest. Sealing a folder produces a file, so it stays on
// the command line where the reader chooses where it goes.
func runGuided() {
	in := bufio.NewScanner(os.Stdin)

	fmt.Println()
	fmt.Println("  DeviceDriver")
	fmt.Println("  Show me what is inside a folder of Windows drivers.")
	fmt.Println()
	fmt.Println("  It opens every .inf file it finds and reports who made each driver")
	fmt.Println("  package, what kind of device it is for, its version and date, and")
	fmt.Println("  every hardware ID it claims — plus any files sitting in the folder")
	fmt.Println("  that no driver package accounts for.")
	fmt.Println()
	fmt.Println("  It only reads. Nothing is installed, changed or deleted, and your")
	fmt.Println("  live Windows driver store is never touched.")
	fmt.Println()

	suggested := suggestedFolder()
	for {
		fmt.Println("  Which driver folder shall I look at?")
		fmt.Println("  (a folder of extracted drivers, or a copy of a driver store)")
		if suggested != "" {
			fmt.Printf("  (press Enter for %s)\n", suggested)
		}
		fmt.Print("  > ")

		if !in.Scan() {
			// stdin closed on us; there is nothing sensible left to ask.
			return
		}
		answer := strings.TrimSpace(in.Text())
		answer = strings.Trim(answer, `"`)
		if answer == "" {
			answer = suggested
		}
		if answer == "" {
			fmt.Println()
			fmt.Println("  I need a folder to look at. 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 a folder from Explorer onto this window to")
			fmt.Println("  paste its location, then press Enter.")
			fmt.Println()
			continue
		case !info.IsDir():
			fmt.Println()
			fmt.Printf("  %q is a file, not a folder. Give me the folder it sits in.\n", answer)
			fmt.Println()
			continue
		case !hasAnyFile(answer):
			// inspect treats an empty folder as a hard error and stops. Say so
			// in plain words first, so the reader gets another go instead.
			fmt.Println()
			fmt.Printf("  %q has no files in it, so there is nothing to inspect.\n", answer)
			fmt.Println("  Try the folder your drivers were unpacked into.")
			fmt.Println()
			continue
		}

		fmt.Println()
		fmt.Println("  Reading and hashing every file. On a big folder this takes a minute.")
		fmt.Println()
		cmdInspect([]string{answer})
		break
	}

	fmt.Println()
	fmt.Println("  Done. That was a read-only look; nothing was written.")
	fmt.Println("  To record this folder's exact contents so you can prove later")
	fmt.Println("  that nothing changed, see: devicedriver --help")
	pause(in)
}

// suggestedFolder offers somewhere worth looking that is certain to exist, so
// the reader can get a useful answer by pressing one key.
//
// There is no folder of drivers every machine is guaranteed to have, and the
// live Windows driver store is far too large to hash on a whim, so this
// returns nothing rather than something wrong. An empty suggestion is handled
// by the prompt: it simply asks for a path instead of offering a default.
func suggestedFolder() string {
	home, err := os.UserHomeDir()
	if err != nil {
		return ""
	}
	for _, rel := range []string{
		"Drivers",
		filepath.Join("Downloads", "Drivers"),
		filepath.Join("Documents", "Drivers"),
	} {
		candidate := filepath.Join(home, rel)
		if info, err := os.Stat(candidate); err == nil && info.IsDir() {
			return candidate
		}
	}
	return ""
}

// hasAnyFile reports whether the tree under root holds at least one regular
// file. inspect refuses an empty store, and refusing it here instead lets the
// reader try somewhere else rather than watch the program stop.
func hasAnyFile(root string) bool {
	found := false
	filepath.Walk(root, func(_ string, info os.FileInfo, err error) error {
		if err != nil || info == nil {
			return nil
		}
		if info.Mode().IsRegular() {
			found = true
			return filepath.SkipAll
		}
		return nil
	})
	return found
}

// 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()
}
