package main

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

// defaultVolumeSize is the one-keypress answer to the second question: a size
// that fits comfortably on anything a person is likely to be copying onto, and
// that parseSize understands.
const defaultVolumeSize = "100MB"

// 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.
//
// PackSafe writes archive volumes to disk, so guided mode runs the dry run and
// only the dry run: the folder is read and measured, the exact set of volumes
// is worked out and printed, and not one byte is written anywhere.
func runGuided() {
	in := bufio.NewScanner(os.Stdin)

	fmt.Println()
	fmt.Println("  PackSafe")
	fmt.Println("  Pack a folder into a series of equal-sized pieces that fit whatever")
	fmt.Println("  you are copying them onto, with a receipt that proves later on that")
	fmt.Println("  none of the pieces are missing or damaged.")
	fmt.Println()
	fmt.Println("  This window works out exactly what packing your folder would produce")
	fmt.Println("  — how many pieces, how big each one is — without writing anything.")
	fmt.Println()

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

	name := setName(folder)
	fmt.Println()
	fmt.Println("  Working. Every file is read and compressed to measure the result.")
	fmt.Println()
	cmdPack([]string{"--out", name, "--volume-size", size, folder})

	fmt.Println()
	fmt.Println("  Nothing was written: that was a rehearsal.")
	fmt.Println("  The command-line version can create those pieces for real, and can")
	fmt.Println("  check a set you already have or unpack one: packsafe --help")
	pause(in)
}

// askFolder asks what to pack 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.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
		}

		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 single file. Give me the folder it sits in and I will\n", answer)
			fmt.Println("  measure everything inside.")
			fmt.Println()
			continue
		}
		return answer, true
	}
}

// askVolumeSize asks how big each piece may be, in the same words the command
// line accepts, and keeps asking until PackSafe itself can read the answer.
func askVolumeSize(in *bufio.Scanner) (string, bool) {
	for {
		fmt.Println()
		fmt.Println("  How big may each piece be?")
		fmt.Println("  Write it like 700MB, 4GB or 100MiB.")
		fmt.Printf("  (press Enter for %s)\n", defaultVolumeSize)
		fmt.Print("  > ")

		if !in.Scan() {
			return "", false
		}
		answer := strings.TrimSpace(in.Text())
		answer = strings.Trim(answer, `"`)
		if answer == "" {
			answer = defaultVolumeSize
		}
		if _, err := parseSize(answer); err != nil {
			fmt.Println()
			fmt.Printf("  I cannot make a size out of %q.\n", answer)
			fmt.Println("  It needs a number and a unit, for example 700MB.")
			continue
		}
		return answer, true
	}
}

// setName picks the name the volumes would carry, derived from the folder so
// the reader recognises it. Nothing is created with it in guided mode.
func setName(folder string) string {
	base := filepath.Base(filepath.Clean(folder))
	base = strings.Map(func(r rune) rune {
		switch {
		case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', r >= '0' && r <= '9', r == '-', r == '_':
			return r
		default:
			return '-'
		}
	}, base)
	base = strings.Trim(base, "-")
	if base == "" {
		base = "backup"
	}
	return base
}

// suggestedFolder offers something worth packing 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 ""
	}
	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
}

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