package main

import (
	"bufio"
	"fmt"
	"os"
	"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.
//
// A benchmark has to write something to measure a write, so this is the one
// guided flow in the range that is not purely read-only. What it writes is one
// temporary file of its own per folder, and it removes each one again on every
// path including errors. It never reads, renames, overwrites or deletes
// anything that was already there. The guided run also uses a smaller test
// file than the command line does, because it runs the whole thing once per
// drive and nobody double-clicks a program to wait.
func runGuided() {
	in := bufio.NewScanner(os.Stdin)

	fmt.Println()
	fmt.Println("  DriveForge")
	fmt.Println("  Compare your drives and see which one is actually faster.")
	fmt.Println()
	fmt.Println("  Name a folder on each drive you want to compare. DriveForge gives")
	fmt.Println("  each one the identical test — write a temporary file, time it, read")
	fmt.Println("  it back, time that — and ranks them fastest first, so you can see")
	fmt.Println("  what your USB stick or external drive really costs you.")
	fmt.Println()
	fmt.Println("  Its own test files are deleted afterwards. Your existing files are")
	fmt.Println("  never read, moved, changed or removed.")
	fmt.Println()

	targets := collectTargets(in)
	if len(targets) == 0 {
		// stdin closed on us mid-question; there is nothing left to do.
		return
	}

	fmt.Println()
	fmt.Printf("  Testing %d folder(s) with a %s file each. This takes a few seconds\n",
		len(targets), guidedTestSize)
	fmt.Println("  per drive. Every test file is removed when I am done.")
	fmt.Println()
	runBench(append(targets, "--size", guidedTestSize))

	fmt.Println()
	fmt.Println("  Done, and the test files are gone.")
	fmt.Println("  The command-line version can use a larger test file for a more")
	fmt.Println("  realistic number: driveforge help")
	pause(in)
}

// collectTargets asks for the folders to compare, one at a time, and returns
// only ones that exist and can actually be written to. It returns nil if stdin
// closes, which is the caller's signal to stop.
//
// Asking repeatedly rather than for a list in one go is deliberate: Windows
// paths are full of spaces, so a "separate them with a space" instruction is a
// trap, and a folder dragged from Explorer arrives as one line on its own.
func collectTargets(in *bufio.Scanner) []string {
	var targets []string
	suggested := suggestedFolder()

	for {
		if len(targets) == 0 {
			fmt.Println("  Which folder shall I test first?")
			fmt.Println("  (the speed measured is the speed of the drive that folder is on)")
			if suggested != "" {
				fmt.Printf("  (press Enter for %s)\n", suggested)
			}
		} else {
			fmt.Println()
			fmt.Println("  A folder on another drive to compare it against?")
			fmt.Println("  (press Enter on its own when you have named them all)")
		}
		fmt.Print("  > ")

		if !in.Scan() {
			return nil
		}
		answer := strings.TrimSpace(in.Text())
		answer = strings.Trim(answer, `"`)

		if answer == "" {
			if len(targets) > 0 {
				return targets // finished naming drives
			}
			if suggested != "" {
				answer = suggested
			} else {
				fmt.Println()
				fmt.Println("  I need a folder to test. 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 !writable(answer):
			// A target that cannot be written to is reported as FAILED, and if
			// every target fails the program stops. Find out with one tiny
			// file first, so the reader gets a plain sentence and another go.
			fmt.Println()
			fmt.Printf("  I am not allowed to write in %q, so I cannot time a write there.\n", answer)
			fmt.Println("  Try a folder inside your own documents, or a drive you own.")
			fmt.Println()
			continue
		}

		targets = append(targets, answer)
	}
}

// guidedTestSize is deliberately smaller than the command-line default of
// 256MB. The whole benchmark runs once per drive, and somebody who
// double-clicked wants a comparison now, not in five minutes.
const guidedTestSize = "64MB"

// suggestedFolder offers somewhere to test that is certain to exist and is
// certain to be writable, so the reader can get a real number by pressing one
// key.
func suggestedFolder() string {
	home, err := os.UserHomeDir()
	if err != nil {
		return ""
	}
	return home
}

// writable reports whether we can create a file in dir. The benchmark needs
// exactly this, and asking cheaply here turns a hard stop into a question.
func writable(dir string) bool {
	f, err := os.CreateTemp(dir, ".driveforge-probe-*")
	if err != nil {
		return false
	}
	name := f.Name()
	f.Close()
	os.Remove(name)
	return true
}

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