package main

import (
	"bufio"
	"fmt"
	"net"
	"os"
	"strconv"
	"strings"
)

// defaultTarget is the one-keypress answer: Windows file sharing on this
// machine. It is a real address that answers instantly whether or not anything
// is listening, so pressing Enter always produces a truthful result.
const defaultTarget = "127.0.0.1:445"

// 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.
//
// The action is "check": one dial, one answer, then it returns. Guided mode
// deliberately never runs "forward", which opens a listening port and stays up
// until it is signalled — there is no way out of that from a window somebody
// double-clicked.
func runGuided() {
	in := bufio.NewScanner(os.Stdin)

	fmt.Println()
	fmt.Println("  OpsTunnel")
	fmt.Println("  Carry connections from a port on this machine through to a machine")
	fmt.Println("  somewhere else, and keep a record of every connection it carried.")
	fmt.Println()
	fmt.Println("  Before any of that is worth setting up, the far end has to answer at")
	fmt.Println("  all. That is the one thing this window does: a single knock on the")
	fmt.Println("  door. Nothing is sent, and the connection is dropped as soon as it")
	fmt.Println("  stands up.")
	fmt.Println()

	target, ok := askTarget(in)
	if !ok {
		return
	}

	fmt.Println()
	fmt.Printf("  Trying %s ...\n", target)
	fmt.Println()
	if err := cmdCheck([]string{"--target", target}); err != nil {
		fmt.Printf("  No answer: %v\n", err)
		fmt.Println()
		fmt.Println("  That usually means nothing is listening on that port, the address")
		fmt.Println("  is wrong, or a firewall in between is dropping the attempt.")
	}

	fmt.Println()
	fmt.Println("  That was a single check and it is finished; nothing is left running")
	fmt.Println("  and no port on this machine was opened.")
	fmt.Println("  The command-line version is what actually carries traffic, and logs")
	fmt.Println("  every connection while it does: opstunnel --help")
	pause(in)
}

// askTarget asks which machine and port to knock on, and keeps asking until the
// answer is a well-formed address. It reports false only when stdin closes, at
// which point there is nothing sensible left to ask.
func askTarget(in *bufio.Scanner) (string, bool) {
	for {
		fmt.Println("  Which machine and port shall I try?")
		fmt.Println("  Write it as address and port together, like 10.0.0.5:5432.")
		fmt.Printf("  (press Enter for %s)\n", defaultTarget)
		fmt.Print("  > ")

		if !in.Scan() {
			return "", false
		}
		answer := strings.TrimSpace(in.Text())
		answer = strings.Trim(answer, `"`)
		if answer == "" {
			answer = defaultTarget
		}
		if why := badTarget(answer); why != "" {
			fmt.Println()
			fmt.Printf("  %s\n", why)
			fmt.Println("  Try again, for example 192.168.1.10:22, or close this window.")
			fmt.Println()
			continue
		}
		return answer, true
	}
}

// badTarget returns a plain-language reason the address cannot be used, or an
// empty string when it is fine. It exists so a typo is answered with a sentence
// rather than a Go error string.
func badTarget(s string) string {
	host, port, err := net.SplitHostPort(s)
	if err != nil {
		return fmt.Sprintf("%q is missing the port. Addresses look like machine:port.", s)
	}
	if strings.TrimSpace(host) == "" {
		return fmt.Sprintf("%q has no machine in front of the port.", s)
	}
	n, err := strconv.Atoi(port)
	if err != nil {
		return fmt.Sprintf("%q is not a port number. Ports are numbers, like 22 or 3389.", port)
	}
	if n < 1 || n > 65535 {
		return fmt.Sprintf("Port %d does not exist. Ports run from 1 to 65535.", n)
	}
	return ""
}

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