package main

import (
	"fmt"

	h "git.bullercodeworks.com/brian/adventofcode/helpers"
)

var highest int
var lowest int
var AllCups map[int]*Cup

func init() {
	highest = h.MIN_INT
	lowest = h.MAX_INT
	AllCups = make(map[int]*Cup)
}

func main() {
	fmt.Println("# Day 23")
	inp := h.StdinToString()

	solve(inp, h.Atoi(h.OptArgNumber(1, "2")))
}

var move int
var first *Cup

func solve(inp string, part int) {
	moves := 100
	if part == 1 {
		BuildCircle(inp, -1)
	} else {
		BuildCircle(inp, 1000000)
		moves = 10000000
	}
	current := first
	for i := 0; i < moves; i++ {
		current = DoMove(current)
	}
	ans := FindLabel(1, first).next
	fmt.Println("## Part", part, ":\nAnswer:")
	if part == 1 {
		for ans.label != 1 {
			fmt.Print(ans.label)
			ans = ans.next
		}
	} else {
		fmt.Print(ans.label * ans.next.label)
	}
	fmt.Println()
}

func BuildCircle(inp string, cups int) *Cup {
	var wrk *Cup
	for k := range inp {
		next := &Cup{label: int(inp[k] - '0')}
		next.next = next
		next.prev = next
		if first == nil {
			first = next
			first.next = next
			first.prev = next
		} else {
			wrk.SetNext(next)
		}
		if next.label > highest {
			highest = next.label
		}
		if next.label < lowest {
			lowest = next.label
		}
		AllCups[next.label] = next
		wrk = next
	}
	total := Count(first)
	last := GetLast(first)
	nextLabel := highest + 1
	for cups > 0 && total < cups {
		next := &Cup{label: nextLabel}
		nextLabel++
		next.next = next
		next.prev = next
		last.SetNext(next)
		last = next
		if next.label > highest {
			highest = next.label
		}
		if next.label < lowest {
			lowest = next.label
		}
		AllCups[next.label] = next
		total++
	}
	return first
}

func DoMove(curr *Cup) *Cup {
	move++
	hold := curr.RemoveNext(3)
	target := curr.label - 1
	if target < lowest {
		target = highest
	}
	// Check the removed cups
	for FindLabel(target, hold) != nil {
		target = target - 1
		if target < lowest {
			target = highest
		}
	}
	//dest := FindLabel(target, curr)
	dest := AllCups[target]
	dest.SetNext(hold)
	return curr.next
}

func FindLabel(num int, first *Cup) *Cup {
	if first.label == num {
		return first
	}
	c := first.next
	for c != first {
		if c.label == num {
			return c
		}
		c = c.next
	}
	return nil
}

func StringifyCircle(first *Cup, current *Cup) string {
	c := first
	stringify := func(c *Cup) string {
		if c == nil {
			return "ERR"
		}
		if c == current {
			return fmt.Sprintf("(%d)", c.label)
		} else {
			return fmt.Sprintf(" %d ", c.label)
		}
	}
	ret := stringify(c)
	c = c.next
	for c != first {
		ret = ret + stringify(c)
		if c == nil {
			ret = ret + " ERR "
			break
		}
		if c.next == c {
			break
		}
		c = c.next
	}
	return ret
}

func GetLast(cups *Cup) *Cup {
	if cups.next == cups || cups.next == nil {
		return cups
	}
	c := cups.next
	for c.next != cups {
		if c.next == nil {
			break
		}
		c = c.next
	}
	return c
}

func Count(cups *Cup) int {
	start := cups
	wrk := start.next
	ret := 1
	for wrk != start {
		wrk = wrk.next
		ret++
	}
	return ret
}

var current *Cup

type Cup struct {
	label int
	next  *Cup
	prev  *Cup
}

func (c *Cup) SetNext(next *Cup) {
	if c.next == c {
		c.prev = next
		c.next = next
		next.next = c
		next.prev = c
		return
	}
	hold := c.next
	// c.p c c.n
	// c.p c next... c.n
	c.next = next
	if hold != nil {
		hold.prev = GetLast(next)
		GetLast(next).next = hold
	}
	next.prev = c
}

// RemoveNext removes the next cnt cups, returning a circle of the removed cups
func (c *Cup) RemoveNext(cnt int) *Cup {
	if cnt == 0 {
		return nil
	}
	hold := c.next
	dest := hold
	for cnt > 0 {
		dest = dest.next
		cnt--
	}
	c.next = dest
	dest.prev.next = hold
	hold.prev = dest.prev
	dest.prev = c
	return hold
}