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 questions
// 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.
//
// MirrorFlow can copy files and move them into its trash folder, so guided
// mode does the read-only comparison and nothing else: it never syncs, never
// writes a state file, and no flag that makes this program change anything
// appears anywhere in this file.
func runGuided() {
	in := bufio.NewScanner(os.Stdin)

	fmt.Println()
	fmt.Println("  MirrorFlow")
	fmt.Println("  Compare two folders that are meant to hold the same files and show")
	fmt.Println("  you exactly where they have drifted apart.")
	fmt.Println()
	fmt.Println("  Every file is compared by its contents, not by its date, so a file")
	fmt.Println("  that was merely re-saved is not mistaken for a changed one.")
	fmt.Println()
	fmt.Println("  This window only looks. Nothing is copied, moved or deleted.")
	fmt.Println()

	dirA, ok := askDir(in, "first", suggestedFolder())
	if !ok {
		return
	}
	dirB, ok := askDir(in, "second", "")
	if !ok {
		return
	}
	if dirA == dirB {
		fmt.Println()
		fmt.Println("  Those are the same folder, so there is nothing to compare.")
		fmt.Println("  Run me again with the two folders you want to line up.")
		pause(in)
		return
	}
	if isInside(dirA, dirB) || isInside(dirB, dirA) {
		fmt.Println()
		fmt.Println("  One of those folders is inside the other. MirrorFlow compares two")
		fmt.Println("  separate folders — pick two that do not overlap.")
		pause(in)
		return
	}

	fmt.Println()
	fmt.Println("  Working. Every file is read and fingerprinted, so a large pair of")
	fmt.Println("  folders takes a minute.")
	fmt.Println()
	firstLook(dirA, dirB)

	fmt.Println()
	fmt.Println("  Nothing was changed on either side.")
	fmt.Println("  The command-line version can record a baseline and then bring the")
	fmt.Println("  two folders back together: mirrorflow --help")
	pause(in)
}

// askDir asks for one of the two folders and keeps asking until the answer is
// a folder that really exists, returning it as an absolute path. It reports
// false only when stdin closes, at which point there is nothing left to ask.
func askDir(in *bufio.Scanner, which, suggested string) (string, bool) {
	for {
		fmt.Printf("  Which is the %s folder?\n", which)
		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.Printf("  I need the %s folder. Try again, or close this window.\n", which)
			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
		}
		abs, err := filepath.Abs(answer)
		if err != nil {
			abs = answer
		}
		return abs, true
	}
}

// firstLook is the read-only comparison, run without a baseline: MirrorFlow has
// never seen these two folders before, so every difference is reported as it
// stands today. It reuses the same scan, classify and print code the "status"
// command uses; it writes nothing, and no state file is created or read.
func firstLook(dirA, dirB string) {
	a, skipA, err := scanTree(dirA)
	if err != nil {
		fmt.Printf("  I could not read everything in %s:\n    %v\n", dirA, err)
		return
	}
	b, skipB, err := scanTree(dirB)
	if err != nil {
		fmt.Printf("  I could not read everything in %s:\n    %v\n", dirB, err)
		return
	}

	empty := &syncState{Version: stateVersion, Tool: appName, Files: map[string]stateEntry{}}
	entries := classify(a, b, empty)
	rep := report{
		Tool:      appName,
		Version:   appVersion,
		Command:   "status",
		DirA:      dirA,
		DirB:      dirB,
		StateFile: "(none — first look, no baseline recorded)",
		Entries:   entries,
		Actions:   []op{},
		Summary:   summarize(entries),
	}
	printText(rep, skipA, skipB)

	fmt.Println()
	fmt.Println("  Reading that table:")
	fmt.Println("    unchanged   the same file, byte for byte, on both sides")
	fmt.Println("    new-on-A    present in the first folder only")
	fmt.Println("    new-on-B    present in the second folder only")
	fmt.Println("    conflict    same name on both sides, different contents")
}

// suggestedFolder offers a starting folder that is certain to exist, so the
// reader can answer the first question with one keypress.
func suggestedFolder() string {
	home, err := os.UserHomeDir()
	if err != nil {
		return ""
	}
	for _, name := range []string{"Documents", "Desktop"} {
		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()
}
