package main

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

// runGuided is what happens when somebody double-clicks DeskScene instead of
// typing its name at a prompt.
//
// Explorer opens a console, runs the program with no arguments, and destroys
// the window the instant the process exits — so printing usage and quitting
// looks exactly like a crash. When we know we were double-clicked we ask the
// two things DeskScene needs and stay on screen until the reader is done.
//
// Everything here reads: the screen description is read, a layout is worked
// out in memory, and the answer is printed. No window is moved and no file is
// written — the guided session never asks for the drawing, because that is the
// one thing DeskScene puts on disk.
func runGuided() {
	in := bufio.NewScanner(os.Stdin)

	fmt.Println()
	fmt.Println("  DeskScene")
	fmt.Println("  Work out what has to give when your layout does not fit your screens.")
	fmt.Println()
	fmt.Println("  You describe the panels you want up at once — charts, an editor, logs,")
	fmt.Println("  a video preview — and describe the monitors you actually have.")
	fmt.Println("  DeskScene decides what stays full size, what shrinks, what goes behind")
	fmt.Println("  a tab and what has to be dropped, and tells you why for each one.")
	fmt.Println()
	fmt.Println("  It only works things out. Nothing on your desktop is moved or changed.")
	fmt.Println()
	fmt.Println("  These layouts come built in, so you can try it without writing one:")
	fmt.Println()
	cmdPresets(nil)

	displays, ok := askDisplaysFile(in)
	if !ok || displays == "" {
		if ok {
			explainDisplaysFile()
			pause(in)
		}
		return
	}

	scene, ok := askScene(in)
	if !ok {
		return
	}

	fmt.Println()
	fmt.Println("  Working it out.")
	fmt.Println()
	cmdFit([]string{"--scene", scene, "--displays", displays})

	fmt.Println()
	fmt.Println("  Done. Every line above is a decision and the reason for it — nothing")
	fmt.Println("  was rearranged on your actual desktop.")
	fmt.Println("  There is a command-line version too, which can compare two layouts,")
	fmt.Println("  suggest the smallest hardware change that would make one fit, and draw")
	fmt.Println("  the result to a picture file: deskscene --help")
	pause(in)
}

// askDisplaysFile asks for the file describing the reader's monitors, which is
// the one thing DeskScene cannot guess. It returns ("", true) when the reader
// has not got one yet, so the session can explain what such a file looks like
// instead of trapping them in a question they cannot answer.
func askDisplaysFile(in *bufio.Scanner) (string, bool) {
	suggested := suggestedDisplaysFile()
	for {
		fmt.Println()
		fmt.Println("  Which file describes your monitors?")
		if suggested != "" {
			fmt.Printf("  (press Enter for %s)\n", suggested)
		} else {
			fmt.Println("  (press Enter if you have not made one — I will show you what")
			fmt.Println("  goes in it)")
		}
		fmt.Println("  Tip: you can drag the file, or the folder it lives in, from Explorer")
		fmt.Println("  onto this window to paste its location, then press Enter.")
		fmt.Print("  > ")

		if !in.Scan() {
			// stdin closed on us; there is nothing sensible left to ask.
			return "", false
		}
		answer := strings.TrimSpace(in.Text())
		answer = strings.Trim(answer, `"`)
		if answer == "" {
			answer = suggested
		}
		if answer == "" {
			return "", true
		}

		info, err := os.Stat(answer)
		switch {
		case err != nil:
			fmt.Println()
			fmt.Printf("  I cannot find %q.\n", answer)
			continue
		case info.IsDir():
			// A dragged folder is an ordinary mistake: look for the obvious
			// file inside it before complaining about it.
			inside := filepath.Join(answer, "displays.json")
			if fi, err := os.Stat(inside); err == nil && !fi.IsDir() {
				fmt.Println()
				fmt.Printf("  That is a folder — I will use %s inside it.\n", inside)
				answer = inside
				break // leaves the switch, and carries on with the file below
			}
			fmt.Println()
			fmt.Printf("  %q is a folder. I need the monitor file itself, which ends in .json.\n", answer)
			continue
		}

		if _, err := loadTopology(answer); err != nil {
			fmt.Println()
			fmt.Println("  I could not read that as a description of your monitors.")
			fmt.Printf("  %v\n", err)
			continue
		}
		return answer, true
	}
}

// askScene asks which layout to try. Built-in names are accepted, and so is a
// path to a layout the reader has written themselves.
func askScene(in *bufio.Scanner) (string, bool) {
	suggested := defaultScene()
	for {
		fmt.Println()
		fmt.Println("  Which layout shall I try on those monitors?")
		if suggested != "" {
			fmt.Printf("  (press Enter for %s, or type any name from the list above)\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 layout to try. Type one of the names listed above.")
			continue
		}
		if _, _, err := loadScene(answer); err != nil {
			fmt.Println()
			fmt.Printf("  I do not know a layout called %q.\n", answer)
			fmt.Printf("  The built-in ones are: %s\n", strings.Join(presetNames(), ", "))
			continue
		}
		return answer, true
	}
}

// explainDisplaysFile shows the reader exactly what to write when they have no
// monitor description yet. Better than sending them away empty-handed.
func explainDisplaysFile() {
	fmt.Println()
	fmt.Println("  No problem. A monitor file is a small text file, saved with a .json")
	fmt.Println("  ending, listing each screen's size in pixels. For a 4K screen next to")
	fmt.Println("  a laptop screen it looks like this:")
	fmt.Println()
	fmt.Println(`    {"name":"desk","monitors":[`)
	fmt.Println(`      {"id":"main","width":3840,"height":2160,"scale":100},`)
	fmt.Println(`      {"id":"laptop","width":1920,"height":1200,"scale":125}`)
	fmt.Println("    ]}")
	fmt.Println()
	fmt.Println("  Save that beside this program as displays.json, with your own numbers,")
	fmt.Println("  and run it again — it will offer that file straight away.")
	fmt.Println("  Your screen's size in pixels is in Windows Display settings; scale is")
	fmt.Println("  the zoom percentage on the same page.")
}

// suggestedDisplaysFile offers a monitor description the reader can accept
// with one keypress: the obvious one next to the program itself, or in the
// folder the console opened in. It returns "" when there is no such file, and
// the prompt copes with that.
func suggestedDisplaysFile() string {
	var dirs []string
	if exe, err := os.Executable(); err == nil {
		dirs = append(dirs, filepath.Dir(exe))
	}
	if wd, err := os.Getwd(); err == nil {
		dirs = append(dirs, wd)
	}
	for _, dir := range dirs {
		for _, name := range []string{"displays.json", "monitors.json", "deskscene-displays.json"} {
			candidate := filepath.Join(dir, name)
			if info, err := os.Stat(candidate); err == nil && !info.IsDir() {
				return candidate
			}
		}
	}
	return ""
}

// defaultScene is the built-in layout offered with one keypress. It is picked
// from the built-in list rather than hard-coded, so it cannot drift out of
// step with what the program actually ships.
func defaultScene() string {
	names := presetNames()
	if len(names) == 0 {
		return ""
	}
	for _, want := range []string{"developer", "trading-desk"} {
		for _, n := range names {
			if n == want {
				return n
			}
		}
	}
	return names[0]
}

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