package main

import (
	"fmt"
	"strings"

	h "git.bullercodeworks.com/brian/adventofcode/helpers"
)

func main() {
	inp := h.StdinToStringSlice()
	boards := buildBoards(inp[1:])
	draws := parseDraws(inp[0])
	part1(boards, draws)
	part2(boards, draws)
}

func part1(boards []Board, draws []int) {
	var winners []Board
	for i := range draws {
		for b := range boards {
			if boards[b].Mark(draws[i]) {
				winners = append(winners, boards[b])
			}
		}
		if len(winners) > 0 {
			break
		}
	}
	for w := range winners {
		fmt.Println("# Part 1")
		fmt.Println(winners[w])
		fmt.Println("Score:", winners[w].Score())
		fmt.Println()
	}
}

func part2(boards []Board, draws []int) {
	var winners []Board
	for i := range draws {
		for b := range boards {
			if boards[b].Mark(draws[i]) {
				winners = append(winners, boards[b])
			}
		}
		// Remove all winners from boards
		for i := range winners {
			for b := range boards {
				if winners[i].String() == boards[b].String() {
					boards = append(boards[:b], boards[b+1:]...)
					break
				}
			}
		}

		if len(boards) == 0 {
			break
		}
	}
	fmt.Println("# Part 2")
	fmt.Println("Last Winner")
	fmt.Println(winners[len(winners)-1])
	fmt.Println("Score:", winners[len(winners)-1].Score())
	fmt.Println()
}

func parseDraws(inp string) []int {
	var ret []int
	pts := strings.Split(inp, ",")
	for i := range pts {
		ret = append(ret, h.Atoi(pts[i]))
	}
	return ret
}

type Spot struct {
	value  int
	marked bool
}

type Board struct {
	spots map[h.Coordinate]Spot
	draws []int
}

func NewBoard(inp []string) Board {
	b := Board{
		spots: make(map[h.Coordinate]Spot),
	}
	for y := range inp {
		row := strings.Fields(strings.TrimSpace(inp[y]))
		for x := range row {
			v := h.Atoi(row[x])
			b.spots[h.Coordinate{X: x, Y: y}] = Spot{value: v}
		}
	}
	return b
}

func buildBoards(inp []string) []Board {
	var boards []Board
	var boardStr []string
	for i := range inp {
		if inp[i] == "" {
			// start/end of a board
			if len(boardStr) == 5 {
				boards = append(boards, NewBoard(boardStr))
			}
			boardStr = []string{}
		} else {
			boardStr = append(boardStr, inp[i])
		}
	}
	if len(boardStr) == 5 {
		boards = append(boards, NewBoard(boardStr))
	}

	return boards
}

// Mark adds a number to the draws and returns whether this board has won
func (b *Board) Mark(v int) bool {
	b.draws = append(b.draws, v)
	for k := range b.spots {
		s := b.spots[k]
		if b.spots[k].value == v {
			s.marked = true
			b.spots[k] = s
			return b.Won()
		}
	}
	return b.Won()
}

func (b *Board) Won() bool {
	for y := 0; y < 5; y++ {
		if b.CheckRowForWin(y) {
			return true
		}
	}
	for x := 0; x < 5; x++ {
		if b.CheckColForWin(x) {
			return true
		}
	}
	return false
}

func (b *Board) CheckRowForWin(y int) bool {
	for x := 0; x < 5; x++ {
		if b.spots[c(x, y)].marked == false {
			return false
		}
	}
	return true
}

func (b *Board) CheckColForWin(x int) bool {
	for y := 0; y < 5; y++ {
		if b.spots[c(x, y)].marked == false {
			return false
		}
	}
	return true
}

func (b *Board) Score() int {
	var unmarked int
	for y := 0; y < 5; y++ {
		for x := 0; x < 5; x++ {
			if b.spots[c(x, y)].marked == false {
				unmarked += b.spots[c(x, y)].value
			}
		}
	}
	return unmarked * b.draws[len(b.draws)-1]
}

func (b Board) String() string {
	var ret string
	for y := 0; y < 5; y++ {
		for x := 0; x < 5; x++ {
			c := c(x, y)
			marked := " "
			if b.spots[c].marked {
				marked = "X"
			}
			ret = fmt.Sprintf("%s%s: %2d[%v]  ", ret, c, b.spots[c].value, marked)
		}
		ret = ret + "\n"
	}
	return ret
}

func c(x, y int) h.Coordinate {
	return h.Coordinate{X: x, Y: y}
}