package main

import (
	"bufio"
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"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 takes the same measurements "collect" takes and shows them on
// screen. It deliberately does not write the report file: a double-clicked
// program should not leave a file behind in a folder nobody chose.
func runGuided() {
	in := bufio.NewScanner(os.Stdin)

	fmt.Println()
	fmt.Println("  PC Health")
	fmt.Println("  Take a snapshot of this computer: what it is, and how much room the")
	fmt.Println("  folders you care about are taking up.")
	fmt.Println()
	fmt.Println("  Run it on every machine you look after and the snapshots can be")
	fmt.Println("  brought together into one table, so the one machine that is filling")
	fmt.Println("  up stands out from the other thirty-nine.")
	fmt.Println()
	fmt.Println("  It only reads and measures. Nothing is changed or removed.")
	fmt.Println()

	folder, ok := askFolder(in)
	if !ok {
		return
	}

	fmt.Println()
	fmt.Println("  Measuring. Adding up a large folder takes a minute.")
	fmt.Println()
	showSnapshot(folder)

	fmt.Println()
	fmt.Println("  Nothing was written and nothing was changed.")
	fmt.Println("  The command-line version saves this as a file, and can merge the")
	fmt.Println("  files from a whole fleet into one table: pchealth --help")
	pause(in)
}

// askFolder asks which folder to measure and keeps asking until the answer is
// a folder that really exists. It reports false only when stdin closes, at
// which point there is nothing sensible left to ask.
func askFolder(in *bufio.Scanner) (string, bool) {
	suggested := suggestedFolder()
	for {
		fmt.Println("  Which folder shall I measure?")
		if suggested != "" {
			fmt.Printf("  (press Enter for %s)\n", suggested)
		}
		fmt.Println("  Tip: you can drag a folder from Explorer onto this window.")
		fmt.Print("  > ")

		if !in.Scan() {
			return "", false
		}
		answer := strings.TrimSpace(in.Text())
		answer = strings.Trim(answer, `"`)
		if answer == "" {
			answer = suggested
		}
		if answer == "" {
			fmt.Println()
			fmt.Println("  I need a folder to measure. 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("  Check the spelling, or drag the folder onto this window and")
			fmt.Println("  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
		}
		return answer, true
	}
}

// showSnapshot takes exactly the measurements "collect" takes — the same
// collect() call, the same walk — and prints them in plain words instead of
// JSON. Nothing is written to disk.
func showSnapshot(folder string) {
	rep, err := collect(machineName(), []string{folder})
	if err != nil {
		fmt.Println("  I could not finish measuring that folder:")
		fmt.Printf("    %v\n", err)
		return
	}

	fmt.Printf("  Machine        : %s\n", rep.Machine)
	fmt.Printf("  System         : %s on %s\n", rep.OS, rep.Arch)
	fmt.Printf("  Processors     : %d\n", rep.NumCPU)
	fmt.Printf("  Taken          : %s\n", rep.Timestamp)
	fmt.Println()
	for _, p := range rep.Paths {
		fmt.Printf("  %s\n", p.Path)
		fmt.Printf("    %s in %s file(s) across %s folder(s)\n",
			humanBytes(p.Bytes), comma(p.Files), comma(p.Dirs))
		if p.Errors > 0 {
			fmt.Printf("    %d item(s) could not be read and were left out of the total\n", p.Errors)
		}
	}
	fmt.Println()
	fmt.Printf("  Total measured : %s\n", humanBytes(rep.TotalBytes))
}

// machineName is the label this snapshot is filed under. The computer's own
// name is what an administrator recognises, so it is used when it is available.
func machineName() string {
	host, err := os.Hostname()
	if err != nil || strings.TrimSpace(host) == "" {
		return "this-" + runtime.GOOS + "-pc"
	}
	return host
}

// suggestedFolder offers somewhere worth measuring that is certain to exist, so
// the reader can get a useful answer by pressing one key.
func suggestedFolder() string {
	home, err := os.UserHomeDir()
	if err != nil {
		return ""
	}
	// A person's own files are the part of a machine that actually grows.
	for _, name := range []string{"Documents", "Downloads"} {
		candidate := filepath.Join(home, name)
		if info, err := os.Stat(candidate); err == nil && info.IsDir() {
			return candidate
		}
	}
	return home
}

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