package main

import (
	"bufio"
	"fmt"
	"log"
	"os"
	"strings"
)

type Resource struct {
	Type  string
	Count int
}

type Recipe struct {
	Materials []Resource
	Count     int
}

func produce(recipes map[string]Recipe, resources map[string]int, target string, debug bool) int {
	recipe, ok := recipes[target]
	if !ok {
		log.Fatalf("%s is not part of resources", target)
	}

	if debug {
		fmt.Printf("producing %d %s\n", recipe.Count, target)
	}
	for _, m := range recipe.Materials {
		if m.Type == "ORE" {
			resources[m.Type] += m.Count
			continue
		}
		if debug {
			fmt.Printf("need %d %s (%d)\n", m.Count, m.Type, resources[m.Type])
		}
		if resources[m.Type] < m.Count {
			var produced int
			for produced < m.Count-resources[m.Type] {
				produced += produce(recipes, resources, m.Type, debug)
			}
			resources[m.Type] += produced
			if debug {
				fmt.Printf("produced %+v %s\n", produced, m.Type)
				fmt.Printf("%+v\n", resources)
			}
		}
		resources[m.Type] -= m.Count
		if debug {
			fmt.Printf("took %+v %s\n", m.Count, m.Type)
			fmt.Printf("%+v\n", resources)
		}
	}

	return recipe.Count
}

func CalculateOreRequirement(recipes map[string]Recipe, debug bool) int {
	if debug {
		fmt.Println()
	}
	resources := make(map[string]int)
	produce(recipes, resources, "FUEL", debug)
	if debug {
		fmt.Printf("%+v\n", resources)
	}
	return resources["ORE"]
}

func CalculateMaxFuel(recipes map[string]Recipe, target int, debug bool) int {
	if debug {
		fmt.Println()
	}

	step := 100000
	var count, used int
	resources := make(map[string]int)
	for i := range recipes {
		recipe := recipes[i]
		recipe.Count *= step
		recipes[i] = recipe
		for j := range recipes[i].Materials {
			recipes[i].Materials[j].Count *= step
		}
	}
	it := 0
	for used < target {
		it++
		if it%100 == 0 {
			fmt.Println(count)
		}
		rec := make(map[string]int)
		for i := range resources {
			rec[i] = resources[i]
		}
		produce(recipes, rec, "FUEL", debug)

		ore := rec["ORE"]
		if used+ore > target {
			div := 10
			if step == 100000 {
				div = 100000
			}
			step /= div
			if step == 0 {
				break
			}
			for i := range recipes {
				recipe := recipes[i]
				recipe.Count /= div
				recipes[i] = recipe
				for j := range recipes[i].Materials {
					recipes[i].Materials[j].Count /= div
				}
			}
			for i := range resources {
				rec[i] = resources[i]
			}
			resources["ORE"] = 0
			continue
		}
		used += ore
		rec["ORE"] = 0
		count += step
		for i := range rec {
			resources[i] = rec[i]
		}
	}
	return count
}

func LoadRecipe(line string) (string, Recipe) {
	var in, out string
	parts := strings.Split(line, "=>")
	in = parts[0]
	out = parts[1]

	var count int
	var name string
	fmt.Sscanf(out, "%d %s", &count, &name)

	recipe := Recipe{
		Count: count,
	}
	for _, i := range strings.Split(in, ",") {
		var count int
		var name string
		fmt.Sscanf(i, "%d %s", &count, &name)
		recipe.Materials = append(recipe.Materials, Resource{
			Count: count,
			Type:  name,
		})
	}
	return name, recipe
}

func main() {
	input, err := os.Open("input.txt")
	if err != nil {
		log.Fatal(fmt.Errorf("could not open input file: %w", err))
	}
	defer input.Close()

	scanner := bufio.NewScanner(input)
	scanner.Split(bufio.ScanLines)

	recipes := make(map[string]Recipe)
	for scanner.Scan() {
		name, recipe := LoadRecipe(strings.TrimSpace(scanner.Text()))
		recipes[name] = recipe
	}

	fmt.Printf("Part 1: %d\n", CalculateOreRequirement(recipes, false))
	fmt.Printf("Part 2: %d\n", CalculateMaxFuel(recipes, 1000000000000, false))
}