package main

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

const myBag = "shiny gold"

var (
	reContainer = regexp.MustCompile(`^\w+ \w+`)
	reContains  = regexp.MustCompile(`\d+ \w+ \w+`)
)

type Container map[string][]Contains

type Contains struct {
	piece int
	kind  string
}

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

func realMain() error {
	data, err := parseInput()
	if err != nil {
		return err
	}

	log.Printf("%d bags could contain %q", solveFirst(data), myBag)

	log.Printf("%d bags could be tucked in a %q", solveSecond(data, myBag), myBag)

	return nil
}

func solveSecond(data Container, root string) int {
	var sum int
	for _, c := range data[root] {
		sum += c.piece + c.piece*solveSecond(data, c.kind)
	}

	return sum
}

func solveFirst(data Container) int {
	var direct []string
	for d := range data {
		for _, c := range data[d] {
			if c.kind == myBag && c.piece > 0 {
				direct = append(direct, d)
			}
		}
	}
	// log.Printf("%v bag(s) can contain %q directly.", direct, myBag)

	indirect := map[string]bool{}
	// fill in directs to indirect
	for _, dr := range direct {
		indirect[dr] = true
	}

	iteration := 0
	for {
		iteration++
		size := len(indirect)
		for indr := range indirect {
			for d := range data {
				for _, c := range data[d] {
					if indr == c.kind && c.piece > 0 {
						// log.Println("ADD INDIRECT:", d.root, "CHILD:", c.kind)
						indirect[d] = true
					}
				}
			}
		}

		// if we have found new items, then we have to add those root too
		if len(indirect) > size {
			continue
		}

		// no new item, we finished
		break
	}
	// log.Println("iteration:", iteration)

	return len(indirect)
}

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

	ret := Container{}

	buf := bufio.NewScanner(fd)
	for buf.Scan() {
		childs := reContains.FindAllString(buf.Text(), -1)

		// if nothing in the container we do not care
		if len(childs) == 0 {
			continue
		}

		root := reContainer.FindString(buf.Text())
		ret[root] = []Contains{}

		for _, c := range childs {
			qs := strings.Split(c, " ")

			q, err := strconv.Atoi(qs[0])
			if err != nil {
				return nil, err
			}

			c := Contains{}
			c.piece = q
			c.kind = qs[1] + " " + qs[2]
			ret[root] = append(ret[root], c)
		}
	}

	return ret, nil
}