package main

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

const (
	playerTurn = iota
	bossTurn
)

type state struct {
	bossHp    int
	bossDmg   int
	playerHp  int
	playerMp  int
	playerDef int
	shield    int
	poison    int
	recharge  int
	manaSpent int
	move      int
}

type spell struct {
	name     string
	mpCost   int
	damage   int
	heal     int
	duration int
}

var minMp int
var spellBook []spell
var stateStack []state

func main() {
	minMp = -1
	initSpellBook()
	if len(os.Args) == 2 && os.Args[1] == "-help" {
		fmt.Println("Usage: day22 [<boss hp> <boss dmg> [<player hp> <player mp>]]")
		os.Exit(1)
	}
	//debugMode = true
	eHp := 71
	eDmg := 10
	if len(os.Args) >= 3 {
		eHp = mustAtoi(os.Args[1])
		eDmg = mustAtoi(os.Args[2])
	}
	pHp := 50
	pMp := 500
	if len(os.Args) == 5 {
		pHp = mustAtoi(os.Args[3])
		pMp = mustAtoi(os.Args[4])
	}
	s := state{
		bossHp:    eHp,
		bossDmg:   eDmg,
		playerHp:  pHp,
		playerMp:  pMp,
		playerDef: 0,
		shield:    0,
		poison:    0,
		recharge:  0,
		manaSpent: 0,
		move:      playerTurn,
	}
	stateStack = append(stateStack, s)
	for len(stateStack) > 0 {
		var wrk state
		wrk, stateStack = stateStack[0], stateStack[1:]
		// Hard Mode
		if wrk.move == playerTurn {
			wrk.playerHp--
		}
		wrk.playerDef = 0
		if wrk.shield > 0 {
			wrk.playerDef = 7
		}
		if wrk.poison > 0 {
			wrk.bossHp -= 3
		}
		if wrk.recharge > 0 {
			wrk.playerMp += 101
		}
		wrk = decrementTimers(wrk)
		if wrk.playerHp <= 0 || (wrk.manaSpent > minMp && minMp != -1) {
			// Unsuccessful path
			continue
		}
		if wrk.bossHp <= 0 {
			// Successful path!
			if minMp == -1 || minMp > wrk.manaSpent {
				minMp = wrk.manaSpent
			}
			continue
		}
		if wrk.move == bossTurn {
			wrk.move = playerTurn
			atk := 1
			if wrk.bossDmg-wrk.playerDef > 1 {
				atk = wrk.bossDmg - wrk.playerDef
			}
			wrk.playerHp -= atk
			stateStack = append([]state{wrk}, stateStack...)
		} else {
			wrk.move = bossTurn
			for i := range spellBook {
				tstSpell := spellBook[i]
				if tstSpell.mpCost >= wrk.playerMp {
					continue
				}
				nextState, ok := castSpell(wrk, tstSpell)
				if ok {
					stateStack = append([]state{nextState}, stateStack...)
				}
			}
		}
	}
	fmt.Printf("%d\n", minMp)
}

func castSpell(w state, s spell) (state, bool) {
	if s.name == "Magic Missile" || s.name == "Drain" {
		w.bossHp -= s.damage
		w.playerHp += s.heal
	} else if s.name == "Shield" {
		if w.shield > 0 {
			return w, false
		}
		w.shield = s.duration
	} else if s.name == "Poison" {
		if w.poison > 0 {
			return w, false
		}
		w.poison = s.duration
	} else if s.name == "Recharge" {
		if w.recharge > 0 {
			return w, false
		}
		w.recharge = s.duration
	}
	w.playerMp -= s.mpCost
	w.manaSpent += s.mpCost
	return w, true
}

func copyState(w state) state {
	ret := state{
		bossHp:    w.bossHp,
		bossDmg:   w.bossDmg,
		playerHp:  w.playerHp,
		playerMp:  w.playerMp,
		playerDef: w.playerDef,
		shield:    w.shield,
		poison:    w.poison,
		recharge:  w.recharge,
		manaSpent: w.manaSpent,
		move:      w.move,
	}
	return ret
}

func printState(w state) {
	if w.move == playerTurn {
		fmt.Println("*** Player Turn ***")
	} else {
		fmt.Println("*** Boss Turn ***")
	}
	fmt.Printf("Player - HP(%d) MP(%d)\n", w.playerHp, w.playerMp)
	fmt.Printf("Boss - HP(%d) DMG(%d)\n", w.bossHp, w.bossDmg)
	fmt.Printf("Shield(%d); Poison(%d); Recharge(%d)\n", w.shield, w.poison, w.recharge)
	fmt.Printf("Mana Used: %d\n", w.manaSpent)
	fmt.Println()
}

func decrementTimers(w state) state {
	w.shield--
	w.poison--
	w.recharge--
	return w
}

func initSpellBook() {
	spellBook = append(spellBook, spell{
		name:   "Magic Missile",
		mpCost: 53,
		damage: 4,
	})
	spellBook = append(spellBook, spell{
		name:   "Drain",
		mpCost: 73,
		damage: 2,
		heal:   2,
	})
	spellBook = append(spellBook, spell{
		name:     "Shield",
		mpCost:   113,
		duration: 6,
	})
	spellBook = append(spellBook, spell{
		name:     "Poison",
		mpCost:   173,
		duration: 6,
	})
	spellBook = append(spellBook, spell{
		name:     "Recharge",
		mpCost:   229,
		duration: 5,
	})
}

func itoa(i int) string {
	return strconv.Itoa(i)
}
func mustAtoi(s string) int {
	var i int
	var err error
	if i, err = strconv.Atoi(s); err != nil {
		fmt.Println("Tried to atoi " + s)
		os.Exit(1)
	}
	return i
}
