package main

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

var playerCount, marbleCount int
var game *Game

func main() {
	if len(os.Args) < 3 {
		fmt.Println("Usage: day09 <# players> <# marbles>")
		os.Exit(1)
	}
	playerCount, marbleCount = Atoi(os.Args[1]), Atoi(os.Args[2])
	playGame()
}

func playGame() {
	g := NewGame(playerCount, marbleCount)
	for i := 1; i <= marbleCount; i++ {
		if i%23 == 0 {
			g.score(i)
		} else {
			g.insertMarble(&Marble{value: i})
		}
		g.nextPlayer()
	}
	fmt.Println("* Winner *")
	g.printWinner()
}

type Game struct {
	playerCount, marbleCount int
	currentPlayer            int
	scores                   map[int]int

	currentMarble *Marble
}

func NewGame(players, marbles int) *Game {
	startMarble := &Marble{
		value: 0,
	}
	startMarble.clockwise = startMarble
	startMarble.widdershins = startMarble
	return &Game{
		playerCount:   players,
		marbleCount:   marbles,
		currentPlayer: 1,
		scores:        make(map[int]int),
		currentMarble: startMarble,
	}
}

func (g *Game) insertMarble(m *Marble) {
	m.clockwise = g.currentMarble.clockwise.clockwise
	m.widdershins = g.currentMarble.clockwise
	g.currentMarble.clockwise.clockwise.widdershins = m
	g.currentMarble.clockwise.clockwise = m
	g.currentMarble = m
}

func (g *Game) score(val int) {
	g.scores[g.currentPlayer] += val
	for i := 0; i < 6; i++ {
		g.currentMarble = g.currentMarble.widdershins
	}
	// Remove g.currentMarble.widdershins
	// adding it's value to the current player's score
	rem := g.currentMarble.widdershins
	g.currentMarble.widdershins = rem.widdershins
	g.currentMarble.widdershins.clockwise = g.currentMarble
	g.scores[g.currentPlayer] += rem.value
}

func (g *Game) nextPlayer() {
	if g.currentPlayer == g.playerCount {
		g.currentPlayer = 1
	} else {
		g.currentPlayer++
	}
}

func (g *Game) printState() {
	fmt.Printf("[%d] ", g.currentPlayer)
	p := *g.currentMarble
	for p.value != 0 {
		// Find the '0' marble
		p = *p.clockwise
	}
	if p.value == g.currentMarble.value {
		fmt.Printf("(%d)", p.value)
	} else {
		fmt.Printf(" %d ", p.value)
	}
	p = *p.clockwise
	for p.value != 0 {
		if p.value == g.currentMarble.value {
			fmt.Printf("(%d)", p.value)
		} else {
			fmt.Printf(" %d ", p.value)
		}
		p = *p.clockwise
	}
	fmt.Println("")
}

func (g *Game) printScores() {
	for i := 1; i <= g.playerCount; i++ {
		fmt.Println(i, ":", g.scores[i])
	}
}

func (g *Game) printWinner() {
	var top int
	var winner int
	for k, v := range g.scores {
		if v > top {
			top = v
			winner = k
		}
	}
	fmt.Println(winner, ":", top)
}

type Marble struct {
	value       int
	clockwise   *Marble
	widdershins *Marble
}

func Atoi(i string) int {
	var ret int
	var err error
	if ret, err = strconv.Atoi(i); err != nil {
		log.Fatal("Invalid Atoi")
	}
	return ret
}
