package main

import (
	"bufio"
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"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.
//
// MoveGuard's one command deletes source files once their copies verify, so
// guided mode never runs it. It runs the report-only half instead: the same
// planJobs() walk the move uses, printed as a list of what a move WOULD take,
// with nothing copied and nothing deleted.
func runGuided() {
	in := bufio.NewScanner(os.Stdin)

	fmt.Println()
	fmt.Println("  MoveGuard")
	fmt.Println("  Move files somewhere else without ever risking a silent loss.")
	fmt.Println()
	fmt.Println("  A real move copies the file, reads the new copy back off the disk,")
	fmt.Println("  checks it is identical to the original byte for byte, and only then")
	fmt.Println("  removes the original. If that check ever fails, the original is left")
	fmt.Println("  exactly where it was.")
	fmt.Println()
	fmt.Println("  This window only shows you what a move would take. It copies nothing")
	fmt.Println("  and deletes nothing.")
	fmt.Println()

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

	fmt.Println()
	fmt.Println("  Nothing was moved, copied or deleted.")
	fmt.Println("  To carry the move out, type this at a command prompt, with the")
	fmt.Println("  folder you want the files to end up in:")
	fmt.Printf("    moveguard move \"%s\" \"D:\\somewhere-else\"\n", folder)
	fmt.Println("  There is more detail in: moveguard --help")
	pause(in)
}

// askFolder asks what would be moved and keeps asking until the answer names
// something 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 were you thinking of moving?")
		if suggested != "" {
			fmt.Printf("  (press Enter for %s)\n", suggested)
		}
		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 look at. Try again, or close this window.")
			fmt.Println()
			continue
		}

		if _, err := os.Stat(answer); 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
		}
		return answer, true
	}
}

// previewMove lists the files a move would take, using the very same walk the
// move itself uses so the list cannot drift from reality. Read-only: it stats
// files and prints; it opens nothing for writing.
func previewMove(src string) {
	fmt.Println()
	fmt.Printf("  Looking at %s\n", src)
	fmt.Println()

	jobs, err := planJobs(src, "")
	if err != nil {
		fmt.Println("  I could not read all of that folder. It may be in use, or you")
		fmt.Println("  may not have permission to look inside it.")
		fmt.Printf("    (%v)\n", err)
		return
	}
	if len(jobs) == 0 {
		fmt.Println("  There are no files in there, so a move would have nothing to do.")
		return
	}

	type row struct {
		rel  string
		size int64
	}
	rows := make([]row, 0, len(jobs))
	var total int64
	var unreadable int
	for _, j := range jobs {
		info, err := os.Stat(j.srcPath)
		if err != nil {
			unreadable++
			continue
		}
		rows = append(rows, row{rel: filepath.ToSlash(j.relPath), size: info.Size()})
		total += info.Size()
	}
	sort.Slice(rows, func(i, j int) bool { return rows[i].size > rows[j].size })

	fmt.Printf("  %d file(s), %s in total. Largest first:\n\n", len(rows), humanBytes(total))
	const show = 20
	for i, r := range rows {
		if i == show {
			fmt.Printf("  ... and %d more\n", len(rows)-show)
			break
		}
		fmt.Printf("  %10s  %s\n", humanBytes(r.size), r.rel)
	}
	if unreadable > 0 {
		fmt.Printf("\n  %d file(s) could not be read and would be reported as errors.\n", unreadable)
	}
	fmt.Println()
	fmt.Println("  Every one of those would be copied, read back, checked against the")
	fmt.Println("  original, and only then removed from here.")
}

// suggestedFolder offers a folder 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 ""
	}
	// Downloads is the folder people most often want emptied onto another disk.
	for _, name := range []string{"Downloads", "Documents"} {
		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()
}
