package main

import (
	"bufio"
	"errors"
	"fmt"
	"math"
	"os"
	"sort"
	"time"
)

/*
 * 173327 is too low
 */
var input []byte
var width int
var characters map[complex64]*Character
var charSlice []*complex64
var turnCount int

const (
	CLEAR_SCREEN = "\033[H\033[2J"
	MAX_INT      = int(^uint(0) >> 1)

	DIR_N = -1i
	DIR_E = 1
	DIR_S = 1i
	DIR_W = -1
)

func main() {
	characters = make(map[complex64]*Character)
	stdinToByteSlice()
	setupBattle()
	part1()
}

func part1() {
	for {
		// Sort the players on the field
		charSlice = charSlice[0:0]
		for i := 0; i < len(input); i++ {
			pos := getPosFromInt(i)
			bt := getByte(pos)
			if bt == 'G' || bt == 'E' {
				charSlice = append(charSlice, &pos)
			}
		}
		sort.Sort(ByPos(charSlice))

		// Tick every player
		for _, v := range charSlice {
			if char, ok := characters[*v]; ok {
				if !char.tick() {
					break
				}
			}
		}
		if true {
			time.Sleep(time.Millisecond * 250)
			printBattlefield()
			fmt.Println()
		}
		if checkBattleOver() {
			break
		}
		turnCount++
	}
	var totalHP int
	sort.Sort(ByPos(charSlice))
	for _, v := range charSlice {
		if c, ok := characters[*v]; ok {
			fmt.Println(c.string())
			totalHP += c.health
		}
	}
	fmt.Println(turnCount, totalHP)
	fmt.Println("Result:", (totalHP * turnCount))
}

func checkBattleOver() bool {
	var elves, gobs int
	for _, v := range characters {
		if v.tp == 'E' {
			elves++
		} else if v.tp == 'G' {
			gobs++
		}
	}
	return elves == 0 || gobs == 0
}

type Character struct {
	tp     byte
	health int
	power  int
	pos    complex64
	kills  int
}

func (c *Character) hasEnemies() bool {
	for _, oppPos := range charSlice {
		if v, ok := characters[*oppPos]; ok {
			if v.tp != c.tp {
				return true
			}
		}
	}
	return false
}

func (c *Character) tick() bool {
	// Check if this character has any enemies on the field
	if !c.hasEnemies() {
		fmt.Println(c.string(), "is unopposed")
		return false
	}
	// Now move/attack
	if _, err := c.easiestAdjacentTarget(); err != nil {
		// Ok, figure out a move
		nxt, tgt := c.findMove()
		if nxt != nil {
			fmt.Println(c.string(), "is moving to", *nxt, "(", *tgt, ")")
			c.moveTo(*nxt)
		}
	}
	if tPos, err := c.easiestAdjacentTarget(); err == nil {
		c.attack(tPos)
	}
	return c.hasEnemies()
}

// findMove returns the position this character should move to and the position
// that is it's ultimate target
func (c *Character) findMove() (*complex64, *complex64) {
	var opps []*complex64
	closestTargetDistance := MAX_INT
	dist, path := findAllPaths(&c.pos)

	for _, v := range characters {
		if v.health <= 0 {
			continue
		}
		if v.tp != c.tp {
			for _, t := range v.getOpenSides() {
				if d, ok := dist[t]; ok && d <= closestTargetDistance {
					if d < closestTargetDistance {
						closestTargetDistance = d
						opps = []*complex64{}
					}
					opps = append(opps, &t)
				}
			}
		}
		sort.Sort(ByPos(opps))
		if len(opps) > 0 {
			t := opps[0]
			curr := *t
			for {
				if pv == c.pos {
					return &curr, t
				}
				curr = pv
			}
		}
	}
	return nil, nil
}

func (c *Character) string() string {
	return fmt.Sprintf("[%s%s:%d]", string(c.tp), getCoordString(c.pos), c.health)
}

func (c *Character) attack(p complex64) {
	fmt.Println(c.string(), "attacks", characters[p].string())
	characters[p].health -= c.power
	if characters[p].health <= 0 {
		c.kills++
		delete(characters, p)
		setByte(p, '.')
	}
}

func (c *Character) easiestAdjacentTarget() (complex64, error) {
	var wrk *Character
	if v, ok := characters[c.pos+DIR_N]; ok && v != nil && v.tp != c.tp {
		wrk = v
	}
	if v, ok := characters[c.pos+DIR_W]; ok && v != nil && v.tp != c.tp {
		if wrk == nil || v.health < wrk.health {
			wrk = v
		}
	}
	if v, ok := characters[c.pos+DIR_E]; ok && v != nil && v.tp != c.tp {
		if wrk == nil || v.health < wrk.health {
			wrk = v
		}
	}
	if v, ok := characters[c.pos+DIR_S]; ok && v != nil && v.tp != c.tp {
		if wrk == nil || v.health < wrk.health {
			wrk = v
		}
	}
	if wrk != nil {
		return wrk.pos, nil
	}
	return 0i, errors.New("No adjacent target")
}

func (c *Character) isAdjacentTo(p complex64) bool {
	return c.pos+DIR_N == p || c.pos+DIR_E == p ||
		c.pos+DIR_S == p || c.pos+DIR_W == p
}

func (c *Character) getOpenSides() []complex64 {
	var ret []complex64
	for _, d := range []complex64{DIR_N, DIR_W, DIR_E, DIR_S} {
		if getByte(c.pos+d) == '.' {
			ret = append(ret, c.pos+d)
		}
	}
	return ret
}

func (c *Character) hasOpenFlank() bool {
	for _, d := range []complex64{DIR_N, DIR_E, DIR_S, DIR_W} {
		if getByte(c.pos+d) == '.' {
			return true
		}
	}
	return false
}

func (c *Character) moveTo(pos complex64) bool {
	if getByte(pos) != '.' {
		return false
	}
	delete(characters, c.pos)
	setByte(c.pos, '.')
	c.pos = pos
	characters[c.pos] = c
	setByte(c.pos, c.tp)
	return true
}

func getOpenSides(c complex64) []complex64 {
	var ret []complex64
	for _, d := range []complex64{DIR_N, DIR_E, DIR_S, DIR_W} {
		if getByte(c+d) == '.' {
			ret = append(ret, c+d)
		}
	}
	return ret
}

// findAllPaths returns a map of all distances and a map of all paths
func findAllPaths(start *complex64) (map[complex64]int, map[complex64]complex64) {
	all := []complex64{*start}
	dist := map[complex64]int{*start: 0}
	prev := map[complex64]complex64{*start: 0}

	for len(all) > 0 {
		c := all[0]
		all = all[1:]
		for _, n := range getOpenSides(c) {
			if _, ok := dist[n]; !ok {
				all = append(all, n)
				dist[n] = dist[c] + 1
				prev[n] = c
			}
		}
	}

	return dist, prev
}

func printBattlefield() {
	//fmt.Print(CLEAR_SCREEN)
	for i := 0; i < len(input)/width; i++ {
		fmt.Println(string(input[i*width : (i+1)*width]))
	}
}

func setupBattle() {
	characters = make(map[complex64]*Character)
	for i := 0; i < len(input); i++ {
		pos := getPosFromInt(i)
		bt := getByte(pos)
		if bt == 'G' || bt == 'E' {
			characters[pos] = &Character{
				tp:     bt,
				pos:    pos,
				power:  3,
				health: 200,
			}
			charSlice = append(charSlice, &pos)
		}
	}
}

func isInMap(pos complex64) bool {
	idx := int(real(pos)) + int(imag(pos))*width
	return idx >= 0 && idx < len(input)
}

// getByte pulls a byte from the given position in the input
func getByte(pos complex64) byte {
	return input[int(real(pos))+int(imag(pos))*width]
}

// setByte sets a byte in the input
func setByte(pos complex64, b byte) {
	input[int(real(pos))+int(imag(pos))*width] = b
}

func getPosFromInt(i int) complex64 {
	return complex(float32(i%width), float32(i/width))
}

func getCoordString(p complex64) string {
	return fmt.Sprintf("(%d,%d)", int(real(p)), int(imag(p)))
}

func stdinToByteSlice() {
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		data := scanner.Bytes()
		if width == 0 {
			width = len(data)
		}

		input = append(input, data...)
	}
}

// Returns the next move for the shortest path from p1 to p2
type pathPoint struct {
	pos   complex64
	count int
}

func findMove(p1 complex64, paths []pathPoint) (complex64, int) {
	pathCount := len(paths)
	// First check if p1 has _any_ possible moves
	lowest := pathPoint{
		pos:   0 - 1i,
		count: MAX_INT,
	}
	for _, v := range paths {
		for _, d := range []complex64{DIR_N, DIR_E, DIR_S, DIR_W} {
			wrkPt := pathPoint{
				pos:   v.pos + d,
				count: v.count + 1,
			}
			if !isInMap(wrkPt.pos) {
				continue
			}
			if wrkPt.pos == p1 {
				if wrkPt.count < lowest.count {
					lowest.pos = v.pos
					lowest.count = v.count
				}
			}
			if getByte(wrkPt.pos) != '.' {
				continue
			}
			var skip bool
			for _, v2 := range paths {
				if v2.pos == wrkPt.pos && v2.count <= wrkPt.count {
					skip = true
					break
				}
			}
			if skip {
				continue
			}
			paths = append(paths, wrkPt)
		}
	}
	if len(paths) != pathCount && lowest.count == MAX_INT {
		return findMove(p1, paths)
	}
	// We hit the end, return the lowest part
	return lowest.pos, lowest.count
}

// (Manhattan Distance, thanks earlier day)
func distance(p1, p2 complex64) int {
	x1, y1, x2, y2 := real(p1), imag(p1), real(p2), imag(p2)
	return int(math.Abs(float64(x1)-float64(x2)) + math.Abs(float64(y1)-float64(y2)))
}

type ByPos []*complex64

func (c ByPos) Len() int      { return len(c) }
func (c ByPos) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
func (c ByPos) Less(i, j int) bool {
	return imag(*c[i]) < imag(*c[j]) ||
		(imag(*c[i]) == imag(*c[j]) && real(*c[i]) < real(*c[j]))
}
