package main

import (
	"fmt"
	"strings"

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

var debugLevel int

const (
	debugLo = 1
	debugMd = 2
	debugHi = 3
	debugNo = -1
)

func main() {
	fmt.Println("# Day 22")
	inp := h.StdinToStringSlice()

	debugLevel = h.Atoi(h.OptArgNumber(2, "-1"))

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

func print(s string, lvl int) {
	if debugLevel != -1 && lvl >= debugLevel {
		fmt.Print(s)
	}
}

func solve(inp []string, part int) {
	var player int
	decks := make(map[int]*Deck)
	for k := range inp {
		if strings.TrimSpace(inp[k]) == "" {
			continue
		} else if strings.HasPrefix(inp[k], "Player ") {
			player = h.Atoi(inp[k][7 : len(inp[k])-1])
			decks[player] = &Deck{Player: player}
			continue
		} else {
			decks[player].Add(h.Atoi(inp[k]))
		}
	}

	if part == 1 {
		solveNormal(decks[1], decks[2])
	} else {
		solveRecursive(decks[1], decks[2])
	}
}

func solveNormal(deck1, deck2 *Deck) {
	round := 1
	winner := Winner(deck1, deck2)
	for winner == -1 {

		print(fmt.Sprintf("-- Round %d --\n%s\n%s\n", round, deck1, deck2), debugLo)

		cmp1 := deck1.Draw()
		cmp2 := deck2.Draw()
		print(fmt.Sprintf("Player 1 plays: %d\nPlayer 2 plays: %d\n", cmp1, cmp2), debugLo)
		if cmp1 > cmp2 {
			print(fmt.Sprintln("Player 1 wins the round!"), debugLo)
			deck1.Add(cmp1, cmp2)
		} else if cmp2 > cmp1 {
			print(fmt.Sprintln("Player 2 wins the round!"), debugLo)
			deck2.Add(cmp2, cmp1)
		}
		round++
		winner = Winner(deck1, deck2)
	}
	fmt.Printf("== Post-game results ==\n%s\n%s\n", deck1, deck2)
	var score int
	if winner == 1 {
		score = deck1.Score()
	} else {
		score = deck2.Score()
	}

	fmt.Println("## Part 1:\nAnswer: ", score)
}

func solveRecursive(deck1, deck2 *Deck) {
	winner := RPlayGame(deck1, deck2)

	fmt.Printf("== Post-game results ==\n%s\n%s\n", deck1, deck2)
	var score int
	if winner == 1 {
		score = deck1.Score()
	} else {
		score = deck2.Score()
	}
	fmt.Println("## Part 2:\nAnswer: ", score)
}

var totalGames int

func RPlayGame(deck1, deck2 *Deck) int {
	var history []string
	var repeat bool
	for deck1.Len() > 0 && deck2.Len() > 0 {
		history, repeat = RPlayRound(deck1, deck2, history)
		if repeat {
			return 1
		}
	}
	return Winner(deck1, deck2)
}

func RPlayRound(deck1, deck2 *Deck, history []string) ([]string, bool) {
	status := fmt.Sprintf("%s%s", deck1, deck2)
	if h.StringSliceContains(history, status) {
		return history, true
	}
	nextHistory := append(history, status)
	c1, c2 := deck1.Draw(), deck2.Draw()
	if deck1.Len() >= c1 && deck2.Len() >= c2 {
		winner := RPlayGame(deck1.Copy(c1), deck2.Copy(c2))
		switch winner {
		case 1:
			deck1.Add(c1, c2)
		case 2:
			deck2.Add(c2, c1)
		}
		return nextHistory, false
	}
	if c1 > c2 {
		deck1.Add(c1, c2)
	} else {
		deck2.Add(c2, c1)
	}

	return nextHistory, false
}

func Winner(deck1, deck2 *Deck) int {
	if deck2.Empty() {
		return 1
	} else if deck1.Empty() {
		return 2
	}
	return -1
}

type Deck struct {
	Player int
	cards  []int
}

func (d *Deck) Add(card ...int) {
	d.cards = append(d.cards, card...)
}

func (d *Deck) Draw() int {
	var ret int
	ret, d.cards = d.cards[0], d.cards[1:]
	return ret
}

func (d *Deck) Empty() bool {
	return len(d.cards) == 0
}

func (d *Deck) Len() int {
	return len(d.cards)
}

func (d *Deck) Score() int {
	var ret int
	for k := len(d.cards) - 1; k >= 0; k-- {
		ret = ret + (d.cards[k] * (len(d.cards) - k))
	}
	return ret
}

func (d *Deck) Copy(num int) *Deck {
	ret := Deck{
		Player: d.Player,
	}
	for k := 0; k < num && k < len(d.cards); k++ {
		ret.Add(d.cards[k])
	}
	return &ret

}

func (d Deck) String() string {
	return fmt.Sprintf("Player %d's deck: %v", d.Player, d.cards)
}