package main

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

type Step struct {
	ID         byte
	Requires   []byte
	RequiredBy []byte
}

type Puzzle struct {
	Steps []Step
}

func findStep(steps []Step, ID byte) int {
	for t := range steps {
		if steps[t].ID == ID {
			return t
		}
	}
	return -1
}

func findFirst(steps []Step) int {
	for t := range steps {
		if len(steps[t].Requires) == 0 {
			return t
		}
	}
	return -1
}

func findLast(steps []Step) int {
	for t := range steps {
		if len(steps[t].RequiredBy) == 0 {
			return t
		}
	}
	return -1
}

// a is a superset of b
func contains(a []byte, b []byte) bool {
	if len(b) == 0 {
		return true
	}
	for _, bc := range b {
		found := false
		for _, ac := range a {
			if ac == bc {
				found = true
				break
			}
		}
		if !found {
			return false
		}
	}
	return true
}

func (p *Puzzle) Add(ID byte, requires byte) {
	t := findStep(p.Steps, ID)
	if t == -1 {
		p.Steps = append(p.Steps, Step{
			ID:         ID,
			Requires:   make([]byte, 0, 10),
			RequiredBy: make([]byte, 0, 10),
		})
		t = len(p.Steps) - 1
	}
	p.Steps[t].RequiredBy = append(p.Steps[t].RequiredBy, requires)

	t = findStep(p.Steps, requires)
	if t == -1 {
		p.Steps = append(p.Steps, Step{
			ID:       requires,
			Requires: make([]byte, 0, 10),
		})
		t = len(p.Steps) - 1
	}
	p.Steps[t].Requires = append(p.Steps[t].Requires, ID)
}

func (p *Puzzle) Solution1() string {
	avail := make([]byte, 0, 10)
	visited := make([]byte, 0, 10)

	for _, step := range p.Steps {
		if contains(visited, step.Requires) {
			fmt.Println(string(step.ID), "->", string(step.Requires), "/", string(step.RequiredBy))
			avail = append(avail, step.ID)
		}
	}

	for {
		if len(avail) == 0 {
			break
		}

		next := byte('[')
		for _, c := range avail {
			if c < next {
				next = c
			}
		}
		alreadyVisited := false
		for _, c := range visited {
			if c == next {
				alreadyVisited = true
				break
			}
		}
		if alreadyVisited {
			break
		}
		visited = append(visited, next)
		curr := findStep(p.Steps, next)

		for _, c := range p.Steps[curr].RequiredBy {
			step := findStep(p.Steps, c)
			if contains(visited, p.Steps[step].Requires) && !contains(avail, []byte{c}) {
				avail = append(avail, c)
			}
		}

		fmt.Println("\ncurr", string(p.Steps[curr].ID))
		fmt.Println("avail", string(avail))
		fmt.Println("visited", string(visited))

		for t, c := range avail {
			if next == c {
				avail = append(avail[:t], avail[t+1:]...)
				break
			}
		}
	}

	return string(visited)
}

type Worker struct {
	Current byte
	Left    int
}

func (w *Worker) Assign(c byte) {
	w.Current = c
	w.Left = int(c-'A'+1) + 60
}

func (w *Worker) Tick() {
	w.Left--
}

func (w *Worker) Done() bool {
	return (w.Left == 0)
}

func (p *Puzzle) Solution2() int {
	workers := make([]Worker, 5)

	avail := make([]byte, 0, 10)
	visited := make([]byte, 0, 10)
	done := make([]byte, 0, 10)

	for _, step := range p.Steps {
		if contains(visited, step.Requires) {
			fmt.Println(string(step.ID), "->", string(step.Requires), "/", string(step.RequiredBy))
			avail = append(avail, step.ID)
		}
	}

	count := 0
	fmt.Printf("Second")
	for t := range workers {
		workers[t].Current = '.'
		fmt.Printf("\tWorker%d", t)
	}
	fmt.Printf("\tAvail")
	fmt.Println("\tDone")
	for {
		for i := range workers {
			if workers[i].Done() {
				if workers[i].Current != 0 && workers[i].Current != '.' {
					if !contains(done, []byte{workers[i].Current}) {
						done = append(done, workers[i].Current)
					}
					for _, step := range p.Steps {
						if contains(done, step.Requires) && !contains(visited, []byte{step.ID}) &&
							!contains(avail, []byte{step.ID}) && !contains(done, []byte{step.ID}) {
							avail = append(avail, step.ID)
						}
					}
					workers[i].Current = '.'
				}

				ni := -1
				next := byte('[')
				for t, c := range avail {
					if c < next {
						next = c
						ni = t
					}
				}
				if next == '[' {
					continue
				}
				avail = append(avail[:ni], avail[ni+1:]...)

				if !contains(visited, []byte{next}) {
					visited = append(visited, next)
				}

				workers[i].Assign(next)
			}

			workers[i].Tick()
		}

		fmt.Printf("%4d", count)
		for _, worker := range workers {
			fmt.Printf("\t%4c", worker.Current)
		}
		fmt.Printf("\t%s", avail)
		fmt.Printf("\t%s\n", done)
		if len(done) == len(p.Steps) {
			break
		}
		count++
	}

	return count
}

func main() {
	input, err := os.Open("input.txt")
	if err != nil {
		panic(err.Error())
	}
	defer input.Close()
	scanner := bufio.NewScanner(input)

	p := Puzzle{
		Steps: make([]Step, 0, 10),
	}
	for scanner.Scan() {
		words := strings.Split(scanner.Text(), " ")
		p.Add(words[1][0], words[7][0])
	}
	if err := scanner.Err(); err != nil {
		panic(err.Error())
	}

	fmt.Println(p.Solution1())
	fmt.Println("-->", p.Solution2(), "<--")
}