package main

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

type StartOption int8

const (
	StartUndefined StartOption = iota
	ComputerFirst
	PlayerFirst
)

type WinOption int8

const (
	WinUndefined WinOption = iota
	TakeLast
	AvoidLast
)

type GameOptions struct {
	pileSize    int
	winOption   WinOption
	startOption StartOption
	minSelect   int
	maxSelect   int
}

func NewOptions() *GameOptions {
	g := GameOptions{}

	g.pileSize = getPileSize()
	if g.pileSize < 0 {
		return &g
	}

	g.winOption = getWinOption()
	g.minSelect, g.maxSelect = getMinMax()
	g.startOption = getStartOption()

	return &g
}

func getPileSize() int {
	ps := 0
	var err error
	scanner := bufio.NewScanner(os.Stdin)

	for {
		fmt.Println("Enter Pile Size ")
		scanner.Scan()
		ps, err = strconv.Atoi(scanner.Text())
		if err == nil {
			break
		}
	}
	return ps
}

func getWinOption() WinOption {
	scanner := bufio.NewScanner(os.Stdin)

	for {
		fmt.Println("ENTER WIN OPTION - 1 TO TAKE LAST, 2 TO AVOID LAST:")
		scanner.Scan()
		w, err := strconv.Atoi(scanner.Text())
		if err == nil && (w == 1 || w == 2) {
			return WinOption(w)
		}
	}
}

func getStartOption() StartOption {
	scanner := bufio.NewScanner(os.Stdin)

	for {
		fmt.Println("ENTER START OPTION - 1 COMPUTER FIRST, 2 YOU FIRST ")
		scanner.Scan()
		s, err := strconv.Atoi(scanner.Text())
		if err == nil && (s == 1 || s == 2) {
			return StartOption(s)
		}
	}
}

func getMinMax() (int, int) {
	minSelect := 0
	maxSelect := 0
	var minErr error
	var maxErr error
	scanner := bufio.NewScanner(os.Stdin)

	for {
		fmt.Println("ENTER MIN AND MAX ")
		scanner.Scan()
		enteredValues := scanner.Text()
		vals := strings.Split(enteredValues, " ")
		minSelect, minErr = strconv.Atoi(vals[0])
		maxSelect, maxErr = strconv.Atoi(vals[1])
		if (minErr == nil) && (maxErr == nil) && (minSelect > 0) && (maxSelect > 0) && (maxSelect > minSelect) {
			return minSelect, maxSelect
		}
	}
}

// This handles the player's turn - asking the player how many objects
// to take and doing some basic validation around that input.  Then it
// checks for any win conditions.
// Returns a boolean indicating whether the game is over and the new pile_size.
func playerMove(pile, min, max int, win WinOption) (bool, int) {
	scanner := bufio.NewScanner(os.Stdin)
	done := false
	for !done {
		fmt.Println("YOUR MOVE")
		scanner.Scan()
		m, err := strconv.Atoi(scanner.Text())
		if err != nil {
			continue
		}

		if m == 0 {
			fmt.Println("I TOLD YOU NOT TO USE ZERO!  COMPUTER WINS BY FORFEIT.")
			return true, pile
		}

		if m > max || m < min {
			fmt.Println("ILLEGAL MOVE, REENTER IT")
			continue
		}

		pile -= m
		done = true

		if pile <= 0 {
			if win == AvoidLast {
				fmt.Println("TOUGH LUCK, YOU LOSE.")
			} else {
				fmt.Println("CONGRATULATIONS, YOU WIN.")
			}
			return true, pile
		}
	}
	return false, pile
}

// This handles the logic to determine how many objects the computer
// will select on its turn.
func computerPick(pile, min, max int, win WinOption) int {
	var q int
	if win == AvoidLast {
		q = pile - 1
	} else {
		q = pile
	}
	c := min + max

	pick := q - (c * int(q/c))

	if pick < min {
		pick = min
	} else if pick > max {
		pick = max
	}

	return pick
}

// This handles the computer's turn - first checking for the various
// win/lose conditions and then calculating how many objects
// the computer will take.
// Returns a boolean indicating whether the game is over and the new pile_size.
func computerMove(pile, min, max int, win WinOption) (bool, int) {
	// first check for end-game conditions
	if win == TakeLast && pile <= max {
		fmt.Printf("COMPUTER TAKES %d AND WINS\n", pile)
		return true, pile
	}

	if win == AvoidLast && pile <= min {
		fmt.Printf("COMPUTER TAKES %d AND LOSES\n", pile)
		return true, pile
	}

	// otherwise determine the computer's selection
	selection := computerPick(pile, min, max, win)
	pile -= selection
	fmt.Printf("COMPUTER TAKES %d AND LEAVES %d\n", selection, pile)
	return false, pile
}

// This is the main game loop - repeating each turn until one
// of the win/lose conditions is met.
func play(pile, min, max int, start StartOption, win WinOption) {
	gameOver := false
	playersTurn := (start == PlayerFirst)

	for !gameOver {
		if playersTurn {
			gameOver, pile = playerMove(pile, min, max, win)
			playersTurn = false
			if gameOver {
				return
			}
		}

		if !playersTurn {
			gameOver, pile = computerMove(pile, min, max, win)
			playersTurn = true
		}
	}
}

// Print out the introduction and rules of the game
func printIntro() {
	fmt.Printf("%33s%s\n", " ", "BATNUM")
	fmt.Printf("%15s%s\n", " ", "CREATIVE COMPUTING  MORRISSTOWN, NEW JERSEY")
	fmt.Printf("\n\n\n")
	fmt.Println("THIS PROGRAM IS A 'BATTLE OF NUMBERS' GAME, WHERE THE")
	fmt.Println("COMPUTER IS YOUR OPPONENT.")
	fmt.Println()
	fmt.Println("THE GAME STARTS WITH AN ASSUMED PILE OF OBJECTS. YOU")
	fmt.Println("AND YOUR OPPONENT ALTERNATELY REMOVE OBJECTS FROM THE PILE.")
	fmt.Println("WINNING IS DEFINED IN ADVANCE AS TAKING THE LAST OBJECT OR")
	fmt.Println("NOT. YOU CAN ALSO SPECIFY SOME OTHER BEGINNING CONDITIONS.")
	fmt.Println("DON'T USE ZERO, HOWEVER, IN PLAYING THE GAME.")
	fmt.Println("ENTER A NEGATIVE NUMBER FOR NEW PILE SIZE TO STOP PLAYING.")
	fmt.Println()
}

func main() {
	for {
		printIntro()

		g := NewOptions()

		if g.pileSize < 0 {
			return
		}

		play(g.pileSize, g.minSelect, g.maxSelect, g.startOption, g.winOption)
	}
}
