package main

import (
	"bufio"
	"log"
	"os"
	"regexp"
	"strconv"
	"strings"
)

const gridSize = 5

type Grid [gridSize][gridSize]int

type Bingo struct {
	marks []int
	grids []Grid
}

type markData struct {
	grid, row, col int
}

var re = regexp.MustCompile(`(\d+)`)

func main() {
	if err := myMain(); err != nil {
		log.Println(err)
	}
}

func myMain() error {
	// parse input.txt
	bingo, err := parseInput()
	if err != nil {
		return err
	}

	// log.Println(bingo)

	log.Println(solveFirst(bingo))

	log.Println(solveSecond(bingo))

	return nil
}

func solveSecond(bingo Bingo) int {
	var winners = map[int]struct{}{}
	for mark, elem := range bingo.marks {
		if mark < gridSize {
			continue
		}
		marks := bingo.findMark(elem)
		for _, m := range marks {
			if bingo.checkRow(m.grid, m.row, mark) || bingo.checkCol(m.grid, m.col, mark) {
				winners[m.grid] = struct{}{}
				if len(winners) == len(bingo.grids) {
					log.Println(bingo.gridValue(m.grid, mark), bingo.marks[mark])
					return bingo.gridValue(m.grid, mark) * bingo.marks[mark]
				}
			}
		}
	}
	return 0
}

func solveFirst(bingo Bingo) int {
	for mark, elem := range bingo.marks {
		if mark < gridSize {
			continue
		}
		marks := bingo.findMark(elem)
		for _, m := range marks {
			// log.Printf("mark value: %d, found: %d %d %d", elem, m.grid, m.row, m.col)
			if bingo.checkRow(m.grid, m.row, mark) || bingo.checkCol(m.grid, m.col, mark) {
				return bingo.gridValue(m.grid, mark) * bingo.marks[mark]
			}
		}
	}
	return 0
}

func (b Bingo) gridValue(gridnum int, mark int) int {
	var sum int
	for _, line := range b.grids[gridnum] {
		for _, elem := range line {
			if !b.isMarked(elem, mark) {
				sum += elem
			}
		}
	}
	return sum
}

func (b Bingo) checkRow(gridnum int, row int, mark int) bool {
	for _, elem := range b.grids[gridnum][row] {
		if !b.isMarked(elem, mark) {
			return false
		}
	}
	// log.Println("checkRow:", b.marks[:mark+1], b.grids[gridnum][row])
	return true
}

func (b Bingo) checkCol(gridnum int, col int, mark int) bool {
	for row := 0; row < gridSize; row++ {
		elem := b.grids[gridnum][row][col]
		if !b.isMarked(elem, mark) {
			return false
		}
	}
	// log.Println("checkCol:", b.marks[:mark+1], b.grids[gridnum][0:gridSize][col])
	return true
}

func (b Bingo) isMarked(elem int, maxMark int) bool {
	for i := 0; i <= maxMark; i++ {
		if b.marks[i] == elem {
			return true
		}
	}
	return false
}

func (b Bingo) findMark(item int) []markData {
	var ret []markData
	for gridnum := range b.grids {
		if m, ok := b.findInGrid(gridnum, item); ok {
			ret = append(ret, m)
		}
	}
	return ret
}

func (b Bingo) findInGrid(gridnum int, item int) (markData, bool) {
	for row, line := range b.grids[gridnum] {
		for col, elem := range line {
			if elem == item {
				return markData{gridnum, row, col}, true
			}
		}
	}
	return markData{}, false
}

func parseInput() (Bingo, error) {
	var ret = Bingo{}
	fd, err := os.Open("input.txt")
	if err != nil {
		return ret, err
	}
	defer fd.Close()

	buf := bufio.NewScanner(fd)
	grid := Grid{}
	gridLine := 0
	for line := 0; buf.Scan(); line++ {
		if line == 0 {
			marks := strings.Split(buf.Text(), ",")
			tmp := make([]int, len(marks))
			for i, m := range marks {
				tmp[i], err = strconv.Atoi(m)
				if err != nil {
					return ret, err
				}
			}
			ret.marks = tmp
			continue
		}
		if buf.Text() == "" {
			if line != 1 {
				ret.grids = append(ret.grids, grid)
			}

			grid = Grid{}
			gridLine = 0

			continue
		}
		tmp := re.FindAllString(buf.Text(), -1)
		for i, elem := range tmp {
			grid[gridLine][i], err = strconv.Atoi(elem)
			if err != nil {
				return ret, err
			}
		}
		gridLine++
	}
	ret.grids = append(ret.grids, grid)

	return ret, nil
}