package main

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

// Guided mode measures for one second per subsystem instead of the usual two,
// and caps the optional disk test at 64 MiB. Somebody who double-clicked the
// program is watching a window: the whole thing has to be over in a few
// seconds, not a few minutes. A scripted run is unaffected and still defaults
// to the longer, steadier budget.
const (
	guidedSeconds   = 1.0
	guidedMemMiB    = 64
	guidedDiskCapMi = 64
)

// guidedConfig is the measurement budget guided mode uses. diskDir is empty
// unless the reader asks for the disk test by name, so the default run writes
// nothing at all, anywhere.
func guidedConfig(diskDir string) config {
	return config{
		seconds:    guidedSeconds,
		diskDir:    diskDir,
		memBufMiB:  guidedMemMiB,
		diskCapMiB: guidedDiskCapMi,
	}
}

// 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.
func runGuided() {
	in := bufio.NewScanner(os.Stdin)

	fmt.Println()
	fmt.Println("  PerformanceDeck")
	fmt.Println("  Measure how fast this computer actually is.")
	fmt.Println()
	fmt.Println("  It gives the processor real work to do, one core at a time and then")
	fmt.Println("  all of them at once, measures how quickly memory can be moved about,")
	fmt.Println("  and turns the results into one score you can compare against the")
	fmt.Println("  same machine later.")
	fmt.Println()
	fmt.Println("  Your files are not read, changed or looked at.")
	fmt.Println()

	diskDir := askDiskFolder(in)

	fmt.Println()
	fmt.Println("  Measuring. This takes a few seconds — leave the machine alone while")
	fmt.Println("  it runs, or the numbers will be about whatever else is busy.")
	fmt.Println()

	// The benchmark's temp file is removed on the way out of a normal run; this
	// makes sure it is also removed if the reader loses patience and presses
	// Ctrl+C. main() installs this for every other path, but not for this one.
	installSignalCleanup()

	res, err := runSuite(guidedConfig(diskDir))
	if err != nil {
		cleanupTemps()
		fmt.Printf("  I could not finish the measurement: %v\n", err)
		if diskDir != "" {
			fmt.Println("  The disk test needs somewhere it is allowed to write. Try again")
			fmt.Println("  and press Enter to skip it.")
		}
		pause(in)
		return
	}
	printResult(os.Stdout, res)

	fmt.Println()
	if diskDir == "" {
		fmt.Println("  That score covers the processor and memory only, so compare it")
		fmt.Println("  against another score measured the same way.")
	}
	fmt.Println("  There is a command-line version too, which measures for longer, saves")
	fmt.Println("  the result, and tells you later whether this machine has slowed down:")
	fmt.Println("  performancedeck help")
	pause(in)
}

// askDiskFolder offers the one extra thing the benchmark can measure and cannot
// guess: a folder to time disk writes in. Pressing Enter skips it, which is the
// default precisely because the disk test is the only part of this program that
// writes anything.
func askDiskFolder(in *bufio.Scanner) string {
	fmt.Println("  I can also time how fast a disk writes. That part needs a folder to")
	fmt.Printf("  work in: it puts one temporary file there, up to %d MiB, and always\n", guidedDiskCapMi)
	fmt.Println("  deletes it again. None of your own files are touched either way.")
	fmt.Println()

	for {
		fmt.Println("  Which folder shall I time the disk in?")
		fmt.Println("  (press Enter to skip the disk test and measure processor and memory only)")
		fmt.Print("  > ")

		if !in.Scan() {
			// stdin closed on us; carry on with the part that needs no answer.
			return ""
		}
		answer := strings.TrimSpace(in.Text())
		answer = strings.Trim(answer, `"`)
		if answer == "" {
			return ""
		}
		if strings.EqualFold(answer, "no") || strings.EqualFold(answer, "skip") {
			return ""
		}

		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("  or press Enter to skip the disk test.")
			fmt.Println()
			continue
		}

		abs, err := filepath.Abs(answer)
		if err != nil {
			return answer
		}
		return abs
	}
}

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