package main

import (
	"fmt"
	"log"
	"os"
	"strconv"

	"git.annabunch.es/annabunches/adventofcode/2020/lib/util"
)

type Cup struct {
	label int
	next  *Cup
}

func parseInput(input string, step string) (*Cup, map[int]*Cup) {
	labelMap := make(map[int]*Cup)

	var firstCup *Cup
	var prev *Cup
	first := true
	for _, char := range input {
		label := util.MustAtoi(string(char))
		newCup := &Cup{
			label: label,
		}
		labelMap[label] = newCup

		if first {
			first = false
			firstCup = newCup
		} else {
			prev.next = newCup
		}
		prev = newCup
	}
	if step == "2" {
		for i := 10; i <= 1000000; i++ {
			newCup := &Cup{
				label: i,
			}
			prev.next = newCup
			prev = newCup
			labelMap[i] = newCup
		}
	}

	prev.next = firstCup
	return firstCup, labelMap
}

func move(current *Cup, labels map[int]*Cup, step string) *Cup {
	// The crab picks up the three cups that are immediately clockwise of the current cup. They are removed from the circle; cup spacing is adjusted as necessary to maintain the circle.
	pickedUp := current.next
	current.next = current.next.next.next.next

	cursor := pickedUp
	holding := make(map[int]struct{})
	for i := 0; i < 3; i++ {
		holding[cursor.label] = struct{}{}
		cursor = cursor.next
	}

	// The crab selects a destination cup: the cup with a label equal to the current cup's label minus one. If this would select one of the cups that was just picked up, the crab will keep subtracting one until it finds a cup that wasn't just picked up. If at any point in this process the value goes below the lowest value on any cup's label, it wraps around to the highest value on any cup's label instead.
	dest := current.label - 1
	_, nope := holding[dest]
	for nope || dest == 0 {
		dest--
		if dest <= 0 {
			switch step {
			case "1":
				dest = 9
			case "2":
				dest = 1000000
			}
		}
		_, nope = holding[dest]
	}

	// The crab places the cups it just picked up so that they are immediately clockwise of the destination cup. They keep the same order as when they were picked up.
	destCup, ok := labels[dest]
	if !ok {
		log.Panicf("No such cup %d", dest)
	}
	oldNext := destCup.next
	destCup.next = pickedUp
	pickedUp.next.next.next = oldNext

	// The crab selects a new current cup: the cup which is immediately clockwise of the current cup.
	return current.next
}

func formatCups(start *Cup) string {
	answer := ""
	startLabel := start.label
	for cursor := start.next; cursor.label != startLabel; cursor = cursor.next {
		answer = answer + strconv.Itoa(cursor.label)
	}
	return answer
}

// too high: 540428194020
func main() {
	step := os.Args[1]
	values := os.Args[2]

	current, labels := parseInput(values, step)

	switch step {
	case "1":
		for i := 0; i < 100; i++ {
			current = move(current, labels, step)
		}
		fmt.Println(formatCups(labels[1]))
	case "2":
		for i := 0; i < 10000000; i++ {
			current = move(current, labels, step)
		}
		cursor := labels[1]
		fmt.Println(cursor.next.label)
		fmt.Println(cursor.next.next.label)
		fmt.Println(cursor.next.label * cursor.next.next.label)
	}
}