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 paths
// a backup actually needs and stay on screen until the reader is done.
//
// RescueVault can copy files and it can restore a snapshot back out over
// whatever is already there, so the guided session runs the PREVIEW only: the
// dry run that reads the source, compares it against the vault and reports
// what a backup would do. It creates no snapshot, no directory and no file.
// Making a real backup stays a deliberate act at the command line.
//
// 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("  RescueVault")
	fmt.Println("  See what your next backup would actually save.")
	fmt.Println()
	fmt.Println("  RescueVault keeps dated snapshots of a folder. A file that has not")
	fmt.Println("  changed since the last snapshot is shared with it rather than stored")
	fmt.Println("  twice, so a second backup of the same folder costs almost no space.")
	fmt.Println()
	fmt.Println("  This window shows you the preview: which files are new, which changed,")
	fmt.Println("  which are already safe, and how much would really be written. Nothing")
	fmt.Println("  is copied, created or deleted while you are in here.")
	fmt.Println()

	source, ok := askFolder(in,
		"  Which folder do you want to protect?",
		suggestedFolder())
	if !ok {
		return
	}

	vault, ok := askVault(in, suggestedVault())
	if !ok {
		return
	}

	fmt.Println()
	fmt.Println("  Reading. On a big folder this can take a minute.")
	fmt.Println()
	cmdBackup([]string{source, vault})

	fmt.Println()
	fmt.Println("  That was a preview. Nothing was written.")
	fmt.Println("  When you want the snapshot for real, run RescueVault from a command")
	fmt.Println("  prompt: rescuevault --help lists every command, including how to list")
	fmt.Println("  the snapshots you already have and how to restore one.")
	pause(in)
}

// askFolder asks for a directory that must already exist, and keeps asking
// until it gets one.
func askFolder(in *bufio.Scanner, question, suggested string) (string, bool) {
	for {
		fmt.Println(question)
		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 "", false
		}
		answer := cleanPath(in.Text())
		if answer == "" {
			answer = suggested
		}
		if answer == "" {
			fmt.Println()
			fmt.Println("  I need a folder. 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
		}
		return answer, true
	}
}

// askVault asks where the snapshots live. Unlike the source folder this one is
// allowed not to exist yet: a vault that is not there simply means no previous
// snapshot to compare against, which the preview reports plainly.
func askVault(in *bufio.Scanner, suggested string) (string, bool) {
	for {
		fmt.Println()
		fmt.Println("  And where do the backups live? That is the vault folder —")
		fmt.Println("  an external disk or a second drive is the usual choice.")
		if suggested != "" {
			fmt.Printf("  (press Enter for %s)\n", suggested)
		}
		fmt.Print("  > ")

		if !in.Scan() {
			return "", false
		}
		answer := cleanPath(in.Text())
		if answer == "" {
			answer = suggested
		}
		if answer == "" {
			fmt.Println()
			fmt.Println("  I need a folder for the vault. Try again, or close this window.")
			continue
		}

		info, err := os.Stat(answer)
		switch {
		case err != nil:
			fmt.Println()
			fmt.Printf("  There is nothing at %q yet, so I will treat this as a brand new\n", answer)
			fmt.Println("  vault: the preview will show every file as a first-time copy.")
			return answer, true
		case !info.IsDir():
			fmt.Println()
			fmt.Printf("  %q is a file, not a folder. The vault needs to be a folder.\n", answer)
			continue
		}
		return answer, true
	}
}

// cleanPath tidies up what a person actually types or drops on the window:
// stray spaces, and the quotes Explorer wraps around a dragged path that
// contains a space.
func cleanPath(s string) string {
	return strings.Trim(strings.TrimSpace(s), `"`)
}

// suggestedFolder offers somewhere worth protecting 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 ""
	}
	// The irreplaceable things are usually the documents and the photographs,
	// so offer those before falling back to the whole home directory.
	for _, name := range []string{"Documents", "Pictures"} {
		candidate := filepath.Join(home, name)
		if info, err := os.Stat(candidate); err == nil && info.IsDir() {
			return candidate
		}
	}
	return home
}

// suggestedVault names a plausible home for the snapshots. It deliberately
// does NOT have to exist — askVault copes with that, and nothing is created.
func suggestedVault() string {
	home, err := os.UserHomeDir()
	if err != nil {
		return ""
	}
	return filepath.Join(home, "RescueVault")
}

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