package main

import (
	"fmt"
	"log"
	"regexp"
	"sort"
	"strconv"
	"strings"
)

type Battlefield map[int]Army

var (
	armyName         = regexp.MustCompile(`^(.*):$`)
	groupImmunities  = regexp.MustCompile(`immune to (.*?)[;)]`)
	groupWeaknesses  = regexp.MustCompile(`weak to (.*?)[;)]`)
	groupDescription = regexp.MustCompile(`^(\d+) units each with (\d+) hit points.*with an attack that does (\d+) (\w+) damage at initiative (\d+)$`)
)

const (
	descriptionCount = iota + 1
	descriptionHitPoints
	descriptionDamage
	descriptionDamageType
	descriptionInitiative
)

func PrepareForBattle(input []string) (Battlefield, Initiative) {
	var initiative Initiative
	battle := make(Battlefield)
	var currentArmy int

	for _, line := range input {
		if armyName.MatchString(line) {
			if id, ok := StringArmies[armyName.FindStringSubmatch(line)[1]]; ok {
				currentArmy = id
			} else {
				panic(fmt.Errorf("unknown army: %s", armyName.FindStringSubmatch(line)[1]))
			}
		} else {
			if currentArmy <= 0 || currentArmy >= ArmyCount {
				panic(fmt.Errorf("tried to assign group to invalid army: %d", currentArmy))
			}
			description := groupDescription.FindStringSubmatch(line)
			if len(description) == 0 {
				continue
			}

			group := &Group{
				Units:        Atoi(description[descriptionCount]),
				HitPoints:    Atoi(description[descriptionHitPoints]),
				AttackDamage: Atoi(description[descriptionDamage]),
				AttackType:   description[descriptionDamageType],
				Initiative:   Atoi(description[descriptionInitiative]),
			}

			immunities := groupImmunities.FindStringSubmatch(line)
			if len(immunities) > 0 {
				group.Immunities = strings.Split(immunities[1], ", ")
			}

			weaknesses := groupWeaknesses.FindStringSubmatch(line)
			if len(weaknesses) > 0 {
				group.Weaknesses = strings.Split(weaknesses[1], ", ")
			}

			battle[currentArmy] = append(battle[currentArmy], group)
			initiative = append(initiative, group)
		}
	}

	return battle, initiative
}

func (b Battlefield) FindTargets() {
	for army, groups := range b {
		sort.Sort(groups)
		for _, group := range groups {
			for enemyArmy, enemyGroups := range b {
				if army == enemyArmy || group.Units <= 0 {
					continue
				}

				var mostDamage int
				var targetGroup *Group

				for _, enemyGroup := range enemyGroups {
					if enemyGroup.Units <= 0 || enemyGroup.Attacker != nil || group.DamageDealt(enemyGroup) == 0 || group.DamageDealt(enemyGroup) < mostDamage {
						continue
					}
					if group.DamageDealt(enemyGroup) == mostDamage && targetGroup != nil {
						if enemyGroup.EffectivePower() < targetGroup.EffectivePower() {
							continue
						}
						if enemyGroup.EffectivePower() == targetGroup.EffectivePower() && enemyGroup.Initiative < targetGroup.Initiative {
							continue
						}
					}
					mostDamage = group.DamageDealt(enemyGroup)
					targetGroup = enemyGroup
				}
				if targetGroup != nil {
					group.Target = targetGroup
					targetGroup.Attacker = group
				}
			}
		}
	}
}

func (b Battlefield) Clean() {
	for army := range b {
		c := b[army][:0]
		for _, g := range b[army] {
			if g.Units > 0 {
				c = append(c, g)
			}
		}
		b[army] = c
	}
}

func (b Battlefield) Active() bool {
	for _, a := range b {
		if !a.Alive() {
			return false
		}
	}
	return true
}

func (b Battlefield) Result() (int, int) {
	var winner, units int

	for army, groups := range b {
		if groups.Alive() {
			winner = army

			for _, g := range groups {
				if g.Units > 0 {
					units += g.Units
				}
			}
		}
	}

	return winner, units
}

func (b Battlefield) TotalUnits() int {
	var sum int
	for _, groups := range b {
		for _, group := range groups {
			if group.Units > 0 {
				sum += group.Units
			}
		}
	}
	return sum
}

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
}
