package main

import (
	"AOC2021/src/helper"
	"fmt"
	"os"
	"strings"
)

type bingoField struct {
	number int
	checked bool
}

type bingoGame struct {
	winState []int
	currentRound int
	numbers []int
	boards [][][]bingoField
}

func main() {
	args := os.Args[1:]
	input, err := helper.GetInput(args[0])
	if err != nil {
		fmt.Println(err)
	}
	bingoNumbers, _ := helper.MapToNumber(strings.Split(input[0], ","))
	bingoBoards := parseBoards(input)

	part1(bingoNumbers, bingoBoards)
	part2(bingoNumbers, bingoBoards)
}

func part1(bingoNumbers []int, bingoBoards [][][]bingoField) {
	game := bingoGame{[]int{}, 0, bingoNumbers, bingoBoards}
	for len(game.winState) == 0 {
		game.playRound()
	}
	fmt.Println(getSumOfUncheckedField(game.boards[game.winState[0]]) * game.numbers[game.currentRound-1])
}

func part2(bingoNumbers []int, bingoBoards [][][]bingoField) {
	game := bingoGame{[]int{}, 0, bingoNumbers, bingoBoards}
	for len(game.winState) < len(game.boards) {
		game.playRound()
	}
	fmt.Println(getSumOfUncheckedField(game.boards[game.winState[len(game.winState)-1]]) * game.numbers[game.currentRound-1])
}

func parseBoards(input []string) [][][]bingoField {
	var bingoBoards [][][]bingoField
	for i := 0; i*6 + 6 <= len(input); i++ {
		begin := 2 + i*6
		end := 7 + i*6
		bingoBoards = append(bingoBoards,parseBoard(input[begin:end]) )
	}
	return bingoBoards
}

func parseBoard(input []string) [][]bingoField{
	bingoBoard := make([][]bingoField, len(input))
	for i, line := range input {
		numberStrings := strings.Split(line, " ")
		numberStrings = helper.RemoveElementFromStringArray(numberStrings, "")
		numbers, _ := helper.MapToNumber(numberStrings)
		row := make([]bingoField, len(numbers))
		for i, number := range numbers {
			row[i] = bingoField{number, false}
		}
		bingoBoard[i] = row
	}
	return bingoBoard
}

func (game* bingoGame) playRound () {
	drawnNumber := game.numbers[game.currentRound]
	for _, board := range game.boards {
		markFields(&board, drawnNumber)
	}
	for i, board := range game.boards {
		if !helper.Contains(i, game.winState) && checkWinCondition(board){
			game.winState = append(game.winState, i)
		}
	}
	game.currentRound++
}

func markFields(board *[][]bingoField, drawnNumber int) {
	for i,row := range *board {
		for j,field := range row {
			if field.number == drawnNumber {
				(*board)[i][j] = bingoField{drawnNumber, true}
			}
		}
	}
}

func checkWinCondition(board [][]bingoField) bool{
	winConditionBoard := make([][]int, len(board))
	for i,row := range board {
		for _,field := range row {
			if field.checked {
				winConditionBoard[i] = append(winConditionBoard[i], 1)
			} else {
				winConditionBoard[i] = append(winConditionBoard[i], 0)
			}
		}
	}
	if checkWinConditionRows(winConditionBoard) || checkWinConditionRows(helper.Transpose(winConditionBoard))  {
		return true
	}
	return false
}

func checkWinConditionRows(rows [][]int) bool{
	for _, row := range rows {
		if helper.AddNummbers(row...) == len(row) {
			return true
		}
	}
	return false
}

func checkWinConditionDiagonals(rows [][]int) bool{
	return checkWinConditionDiagonal(rows) || checkWinConditionDiagonalInverse(rows)
}

func checkWinConditionDiagonal(rows [][]int,) bool {
	for i := 0; i < len(rows); i++ {
		if rows[i][i] == 0 {
			return false
		}
	}
	return true
}

func checkWinConditionDiagonalInverse(rows [][]int) bool {
	for i := 0; i < len(rows); i++ {
		if rows[4-i][i] == 0 {
			return false
		}
	}
	return true
}

func getSumOfUncheckedField(board [][]bingoField) int {
	sum := 0
	for _, row := range board {
		for _, field := range row {
			if !field.checked {
				sum += field.number
			}
		}
	}
	return sum
}