package main

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

const ClearScreen = "\033[H\033[2J"

var manager *StepManager
var TotalWorkers int

func main() {
	inp := StdinToStringSlice()
	signTheContract(inp)
	part1()
	manager.resetJobs()
	part2()
}

func signTheContract(inp []string) {
	TotalWorkers = 5
	manager = &StepManager{workers: TotalWorkers}
	for _, v := range inp {
		manager.addOrUpdateStep(v[36], v[5])
	}
}

func part1() {
	fmt.Println("= Part 1 =")
	for !manager.done() {
		manager.part1Act()
	}
	fmt.Println("")
}

func part2() {
	var seconds int
	realtime := true
	for !manager.done() {
		seconds++
		manager.part2Act()

		fmt.Print(ClearScreen)
		fmt.Println(manager.string())
		fmt.Println("Total Seconds: ", seconds)
		if realtime {
			time.Sleep(time.Millisecond * 125)
		}
	}
}

type StepManager struct {
	steps   []*Step
	workers int
}

// In part 1, all steps complete immediately
func (m *StepManager) part1Act() {
	// Find the next step that should run
	var nextStep *Step
	for i := range m.steps {
		if !m.steps[i].done() && m.steps[i].ready() && (nextStep == nil || nextStep.id > m.steps[i].id) {
			nextStep = m.steps[i]
		}
	}
	nextStep.timeSpent = int(nextStep.id) + 5
	fmt.Print(string(nextStep.id))
}

func (m *StepManager) part2Act() {
	// Clear all done jobs
	for i := range m.steps {
		if m.steps[i].active && m.steps[i].done() {
			m.steps[i].active = false
			m.workers++
			fmt.Println(string(m.steps[i].id))
		}
	}
	// Find all steps that are ready for work
	var waitingSteps []*Step
	for i := range m.steps {
		if m.steps[i].ready() && !m.steps[i].done() && !m.steps[i].active {
			waitingSteps = append(waitingSteps, m.steps[i])
		}
	}
	// If we have any available workers, get them working
	for m.workers > 0 {
		var nextStep *Step
		for i := range waitingSteps {
			if !waitingSteps[i].active && (nextStep == nil || waitingSteps[i].id < nextStep.id) {
				nextStep = waitingSteps[i]
			}
		}
		if nextStep != nil {
			nextStep.active = true
			m.workers--
		} else {
			break
		}
	}
	// Increment all active steps
	for i := range m.steps {
		if m.steps[i].active {
			m.steps[i].timeSpent++
		}
	}
}

func (m *StepManager) getStep(id byte) (*Step, error) {
	for i := range m.steps {
		if m.steps[i].id == id {
			return m.steps[i], nil
		}
	}
	return nil, errors.New("No step with id " + string(id) + " found.")
}

func (m *StepManager) addOrUpdateStep(id, reqId byte) {
	s, err := m.getStep(id)
	if err != nil {
		s = &Step{id: id}
	}
	r, reqErr := m.getStep(reqId)
	if reqErr != nil {
		r = &Step{id: reqId}
		m.steps = append(m.steps, r)
	}
	s.addRequirement(r)
	if err != nil {
		m.steps = append(m.steps, s)
	}
}

func (m *StepManager) resetJobs() {
	for i := range manager.steps {
		manager.steps[i].timeSpent = 0
	}
}

func (m *StepManager) string() string {
	var ret string
	var stepsDone int
	for i := range manager.steps {
		ret += (manager.steps[i].string() + "\n")
		if manager.steps[i].done() {
			stepsDone++
		}
	}
	pct := 100 * (float64(stepsDone) / float64(len(manager.steps)))

	filled := int(pct / 10)
	progBar := strings.Repeat("\u2588", filled)
	progBar += strings.Repeat(" ", (10 - filled))
	ret += fmt.Sprintf("Progress: [%s] %d\n", progBar, int(pct))
	ret += fmt.Sprintf("Idle Workers %d/%d", m.workers, TotalWorkers)
	return ret
}

func (m *StepManager) done() bool {
	for i := range m.steps {
		if !m.steps[i].done() {
			return false
		}
	}
	return true
}

type Step struct {
	id        byte
	req       []*Step
	timeSpent int
	active    bool
}

func (s *Step) done() bool {
	return s.timeSpent >= int(s.id)-4
}

func (s *Step) addRequirement(r *Step) {
	for i := range s.req {
		if s.req[i] == r {
			return
		}
	}
	s.req = append(s.req, r)
}

func (s *Step) ready() bool {
	for i := range s.req {
		if !s.req[i].done() {
			return false
		}
	}
	return true
}

func (s *Step) string() string {
	var ret string
	if s.done() {
		ret += "[✔️ ] "
	} else {
		ret += "[✖️ ] "
	}
	ret += fmt.Sprintf("%s ( %d ): [", string(s.id), s.id)
	for i := range s.req {
		ret += string(s.req[i].id) + " "
	}
	ret += "] "
	if s.active {
		ret += "▶️ "
	}
	return ret
}

func StdinToStringSlice() []string {
	var input []string
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		input = append(input, scanner.Text())
	}
	return input
}
