package main

import (
	"fmt"
	"strconv"
	"strings"
)

const (
	cellEmpty = " "
	cellUser  = "U"
	cellBot   = "B"
)

func boardToString(board [][]string) string {
	maxRowLength := 0
	rowStrings := make([]string, len(board)+1)
	for i, row := range board {
		if len(row) > maxRowLength {
			maxRowLength = len(row)
		}

		rowIndex := strconv.Itoa(i)
		if i < 10 {
			rowIndex += " "
		}
		rowStrings[i+1] = rowIndex + strings.Join(row, "|")
	}

	columnIndices := make([]string, maxRowLength)
	for i := 0; i < maxRowLength; i++ {
		rowIndex := strconv.Itoa(i)
		if i < 10 {
			rowIndex = " " + rowIndex
		}
		columnIndices[i] = rowIndex
	}
	rowStrings[0] = "/" + strings.Join(columnIndices, "")

	return strings.Join(rowStrings, "\n")
}

func createBoard(size int) [][]string {
	board := make([][]string, size)

	for r := 0; r < size; r++ {
		rowSlice := make([]string, size)
		for i := range rowSlice {
			rowSlice[i] = cellEmpty
		}

		board[r] = rowSlice
	}

	return board
}

func changeCellUserInput(board *[][]string) {
	var row *[]string

	for {
		var rowIndex int
		fmt.Print("row: ")
		fmt.Scanln(&rowIndex)

		if 0 <= rowIndex && rowIndex < len(*board) {
			row = &(*board)[rowIndex]
			break
		} else {
			fmt.Println("The value needs to be in the range of the board rows.")
			continue
		}
	}

	for {
		var colIndex int
		fmt.Print("col: ")
		fmt.Scanln(&colIndex)

		if 0 <= colIndex && colIndex < len(*row) {
			if (*row)[colIndex] != " " {
				fmt.Println("This cell is already occupied.")
				defer changeCellUserInput(board)
				break
			}

			(*row)[colIndex] = cellUser
			break
		} else {
			fmt.Println("The value needs to be in the range of the board columns.")
			continue
		}
	}
}

func getWinningCombinations(board *[][]string) [][]*string {
	// there are 8 winning combinations in total
	winningCombinations := make([][]*string, len(*board)*2+2)

	// board_size row wins | board_size col wins
	for i, _ := range *board {
		row := make([]*string, len(*board))
		col := make([]*string, len(*board))

		for j, _ := range *board {
			row[j] = &(*board)[i][j]
			col[j] = &(*board)[j][i]
		}

		winningCombinations[i*2] = row
		winningCombinations[i*2+1] = col
	}

	// 2 vertical wins
	a := make([]*string, len(*board))
	b := make([]*string, len(*board))
	for i, _ := range *board {
		a[i] = &(*board)[i][i]
		b[i] = &(*board)[i][len(*board)-i-1]
	}
	winningCombinations[len(*board)*2] = a
	winningCombinations[len(*board)*2+1] = b

	return winningCombinations
}

type combinationStat struct {
	totalCells int
	emptyCells int
	userCells  int
	botCells   int

	isWinnable    bool
	emptyCellRefs []*string
}

func calculateCombinationStats(combination []*string) combinationStat {
	c := combinationStat{}

	for _, cell := range combination {
		c.totalCells++
		switch *cell {
		case cellEmpty:
			c.emptyCells++
			c.emptyCellRefs = append(c.emptyCellRefs, cell)
		case cellBot:
			c.botCells++
		case cellUser:
			c.userCells++
		}
	}

	c.isWinnable = c.userCells == 0 || c.botCells == 0

	return c
}

func changeCellBotInput(winningCombinations [][]*string) {
	combinationStats := make([]combinationStat, len(winningCombinations))
	for i, combination := range winningCombinations {
		combinationStats[i] = calculateCombinationStats(combination)
	}

	// now count the still possible wins each pointer has
	cellScores := make(map[*string]int)
	for _, c := range combinationStats {
		score := 0
		if c.userCells == 0 {
			score = 1 + (c.totalCells - c.emptyCells)
		} else if c.botCells == 0 {
			score = c.totalCells - c.emptyCells
		}

		for _, emptyCell := range c.emptyCellRefs {
			cellScores[emptyCell] += score * score
		}
	}

	bestCellScore := 0
	var bestCell *string
	for cell, cellScore := range cellScores {
		if cellScore > bestCellScore {
			bestCellScore = cellScore
			bestCell = cell
		}
	}

	*bestCell = cellBot
}

type boardData struct {
	isWin bool
	isPat bool

	winningUser string
}

func analyzeWin(winningCombinations [][]*string) boardData {
	b := boardData{}

	winnable := false
	for _, combination := range winningCombinations {
		c := calculateCombinationStats(combination)

		if c.botCells == c.totalCells {
			b.isWin = true
			b.winningUser = cellBot
			return b
		}
		if c.userCells == c.totalCells {
			b.isWin = true
			b.winningUser = cellUser
			return b
		}

		if c.botCells == 0 || c.userCells == 0 {
			winnable = true
		}
	}

	if !winnable {
		b.isPat = true
	}

	return b
}

func main() {
	board := createBoard(4)
	winningCombinations := getWinningCombinations(&board)

	currentUser := 0
	for {
		fmt.Println()
		fmt.Println(boardToString(board))

		bd := analyzeWin(winningCombinations)
		if bd.isWin {
			fmt.Println()
			switch bd.winningUser {
			case cellBot:
				fmt.Println("THE BOT WINS!!")
			case cellUser:
				fmt.Println("CONGRATULATION, YOU WIN!!")
			}
			break
		}
		if bd.isPat {
			fmt.Println()
			fmt.Println("GAME OVER. NEITHER ONE WON")
			break
		}

		if currentUser == 0 {
			fmt.Println()
			fmt.Println("it's your turn human")
			changeCellUserInput(&board)
		} else {
			changeCellBotInput(winningCombinations)
		}

		currentUser = (currentUser + 1) % 2
	}
}
