package main

import (
	"bufio"
	"fmt"
	"image"
	"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.
//
// captureflow edits pixels, so guided mode does the measuring half only: it
// opens images and reports what they are. It writes no image, and this file
// offers no way to ask it to.
func runGuided() {
	in := bufio.NewScanner(os.Stdin)

	fmt.Println()
	fmt.Println("  CaptureFlow")
	fmt.Println("  Blurs, blacks out and marks up screenshots — a whole folder of them")
	fmt.Println("  at once, all in the same places.")
	fmt.Println()
	fmt.Println("  Before you can say where to blur, you need to know how big your")
	fmt.Println("  screenshots are. Point me at a picture or a folder of them and I will")
	fmt.Println("  measure each one and work out the coordinates you would type.")
	fmt.Println()
	fmt.Println("  I only look. No picture is edited, overwritten or moved.")
	fmt.Println()

	suggested := suggestedShots()
	for {
		fmt.Println("  Which picture, or folder of pictures, shall I measure?")
		if suggested != "" {
			fmt.Printf("  (press Enter for %s)\n", suggested)
		}
		fmt.Println("  Tip: you can drag a folder or an image from Explorer onto this")
		fmt.Println("  window to paste its location.")
		fmt.Print("  > ")

		if !in.Scan() {
			// stdin closed on us; there is nothing sensible left to ask.
			return
		}
		answer := strings.TrimSpace(in.Text())
		answer = strings.Trim(answer, `"`)
		if answer == "" {
			answer = suggested
		}
		if answer == "" {
			fmt.Println()
			fmt.Println("  I need a picture or a folder. Try again, or close this window.")
			fmt.Println()
			continue
		}

		info, err := os.Stat(answer)
		if err != nil {
			fmt.Println()
			fmt.Printf("  I cannot find %q.\n", answer)
			fmt.Println("  Tip: you can drag a folder or an image from Explorer onto this")
			fmt.Println("  window to paste its location, then press Enter.")
			fmt.Println()
			continue
		}

		var targets []string
		if info.IsDir() {
			targets = imagesInDir(answer)
			if len(targets) == 0 {
				fmt.Println()
				fmt.Printf("  There are no PNG or JPEG pictures directly inside %q.\n", answer)
				fmt.Println("  CaptureFlow does not look inside sub-folders — pick the folder")
				fmt.Println("  the pictures are actually in.")
				fmt.Println()
				continue
			}
		} else {
			targets = []string{answer}
		}

		fmt.Println()
		fmt.Printf("  Measuring %d picture(s).\n", len(targets))
		fmt.Println()
		guidedInspect(targets)
		break
	}

	fmt.Println()
	fmt.Println("  Done, and nothing was edited. To actually blur or black out those")
	fmt.Println("  areas, the command-line version does it: captureflow help")
	pause(in)
}

// guidedInspect reports what each picture is, the same facts the inspect
// command reports, and finishes with the practical bit: whether one set of
// coordinates would suit the whole batch.
func guidedInspect(paths []string) {
	type shot struct {
		path          string
		width, height int
		format        string
		bytes         int64
	}
	var shots []shot
	var unreadable int

	for _, p := range paths {
		fi, err := os.Stat(p)
		if err != nil {
			fmt.Printf("    %s\n      I cannot read this one: %v\n", filepath.Base(p), err)
			unreadable++
			continue
		}
		f, err := os.Open(p)
		if err != nil {
			fmt.Printf("    %s\n      I cannot open this one: %v\n", filepath.Base(p), err)
			unreadable++
			continue
		}
		cfg, format, err := image.DecodeConfig(f)
		f.Close()
		if err != nil {
			fmt.Printf("    %s\n      Not a readable PNG or JPEG picture.\n", filepath.Base(p))
			unreadable++
			continue
		}
		shots = append(shots, shot{p, cfg.Width, cfg.Height, format, fi.Size()})
		fmt.Printf("    %s\n", filepath.Base(p))
		fmt.Printf("      %d x %d pixels, %s, %s\n", cfg.Width, cfg.Height, strings.ToUpper(format), humanBytes(fi.Size()))
	}

	if len(shots) == 0 {
		fmt.Println()
		fmt.Println("  Nothing readable there.")
		return
	}

	fmt.Println()
	sameSize := true
	for _, s := range shots[1:] {
		if s.width != shots[0].width || s.height != shots[0].height {
			sameSize = false
			break
		}
	}
	if unreadable > 0 {
		fmt.Printf("  %d picture(s) measured, %d could not be read.\n", len(shots), unreadable)
	} else {
		fmt.Printf("  %d picture(s) measured.\n", len(shots))
	}
	fmt.Println()

	if len(shots) == 1 {
		s := shots[0]
		fmt.Println("  Working out coordinates for it:")
		fmt.Printf("    the whole picture            0,0,%d,%d\n", s.width, s.height)
		fmt.Printf("    the top strip (title bar)    0,0,%d,%d\n", s.width, s.height/12+1)
		fmt.Printf("    the middle band              0,%d,%d,%d\n", s.height/3, s.width, s.height/3)
		fmt.Println()
		fmt.Println("  Those are pixels. You can write percentages instead — 0,10%,100%,20%")
		fmt.Println("  means a full-width band from a tenth to three tenths of the way down.")
		return
	}

	if sameSize {
		fmt.Printf("  All %d are exactly %d x %d, so one set of pixel coordinates will\n",
			len(shots), shots[0].width, shots[0].height)
		fmt.Println("  land in the same place on every one of them.")
		return
	}
	fmt.Println("  They are not all the same size. Write the areas as percentages")
	fmt.Println("  rather than pixels — 0,10%,100%,20% is the same band on a small")
	fmt.Println("  screenshot and a large one — and one set of coordinates will still")
	fmt.Println("  suit the whole batch.")
}

// imagesInDir lists the pictures directly inside dir, in the same sorted order
// and with the same "no sub-folders" rule the redact command uses.
func imagesInDir(dir string) []string {
	entries, err := os.ReadDir(dir)
	if err != nil {
		return nil
	}
	var out []string
	for _, e := range entries {
		if !e.IsDir() && isImageName(e.Name()) {
			out = append(out, filepath.Join(dir, e.Name()))
		}
	}
	sort.Strings(out)
	return out
}

// suggestedShots offers somewhere with screenshots in it that is certain to
// exist, so the reader can get going by pressing one key.
func suggestedShots() string {
	home, err := os.UserHomeDir()
	if err != nil {
		return ""
	}
	// Windows drops screenshots in Pictures\Screenshots; macOS puts them on
	// the Desktop; everything else tends to end up in Downloads.
	for _, parts := range [][]string{
		{"Pictures", "Screenshots"},
		{"Desktop"},
		{"Pictures"},
		{"Downloads"},
	} {
		candidate := filepath.Join(append([]string{home}, parts...)...)
		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()
}
