package main

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"os"
)

// Dir ...
type Dir int

// Dir constants.
const (
	DirNE Dir = iota
	DirE
	DirSE
	DirSW
	DirW
	DirNW
)

func (d Dir) String() string {
	switch d {
	case DirNE:
		return "ne"
	case DirE:
		return "e"
	case DirSE:
		return "se"
	case DirSW:
		return "sw"
	case DirW:
		return "w"
	case DirNW:
		return "nw"
	}
	return "n/a"
}

// MovedPos ...
func (d Dir) MovedPos(initial Pos) Pos {
	move := initial
	switch d {
	case DirNE:
		if move.Y%2 != 0 {
			move.X++
		}
		move.Y--
	case DirE:
		move.X++
	case DirSE:
		if move.Y%2 != 0 {
			move.X++
		}
		move.Y++
	case DirSW:
		if move.Y%2 == 0 {
			move.X--
		}
		move.Y++
	case DirW:
		move.X--
	case DirNW:
		if move.Y%2 == 0 {
			move.X--
		}
		move.Y--
	}
	return move
}

// StrToDir ...
func StrToDir(str string) Dir {
	switch str {
	case "ne":
		return DirNE
	case "e":
		return DirE
	case "se":
		return DirSE
	case "sw":
		return DirSW
	case "w":
		return DirW
	case "nw":
		return DirNW
	}
	return -1
}

// Cell ...
type Cell struct {
	Position Pos
	Flipped  bool
}

// Pos ...
type Pos struct {
	X, Y int
}

// HexGrid ...
type HexGrid struct {
	Root  *Cell
	Cells map[Pos]*Cell
}

// MakeMoves ...
func (hg *HexGrid) MakeMoves(moves string, debug bool) {
	if debug {
		fmt.Printf("\n==>%s\n", moves)
	}
	var currPos Pos
	for i := 0; i < len(moves); i++ {
		var dir Dir
		if moves[i] == 's' || moves[i] == 'n' {
			dir = StrToDir(moves[i : i+2])
			i++
		} else {
			dir = StrToDir(string(moves[i]))
		}

		move := dir.MovedPos(currPos)
		if debug {
			fmt.Printf("%+v move %s to %+v\n", currPos, dir, move)
		}

		cell, ok := hg.Cells[move]
		if !ok {
			cell = &Cell{
				Position: move,
			}
		}
		if i == len(moves)-1 {
			cell.Flipped = !cell.Flipped
			if debug {
				fmt.Printf("flipped %+v, now %v\n", cell.Position, cell.Flipped)
			}
		}
		hg.Cells[move] = cell
		currPos = move
	}
}

// IterateLife ...
func (hg *HexGrid) IterateLife(debug bool) {
	cells := make(map[Pos]*Cell)

	var topLeft, bottomRight Pos
	for _, c := range hg.Cells {
		if c.Position.X < topLeft.X {
			topLeft.X = c.Position.X
		}
		if c.Position.Y < topLeft.Y {
			topLeft.Y = c.Position.Y
		}
		if c.Position.X > bottomRight.X {
			bottomRight.X = c.Position.X
		}
		if c.Position.Y > bottomRight.Y {
			bottomRight.Y = c.Position.Y
		}
	}

	if debug {
		fmt.Printf("%+v -> %+v\n", topLeft, bottomRight)
	}
	// Adding in missing slots in grid to allow growth.
	for y := topLeft.Y - 1; y <= bottomRight.Y+1; y++ {
		for x := topLeft.X - 1; x <= bottomRight.X+1; x++ {
			pos := Pos{x, y}
			if _, ok := hg.Cells[pos]; !ok {
				hg.Cells[pos] = &Cell{
					Position: pos,
				}
			}
		}
	}

	for _, c := range hg.Cells {
		xw, xe, x, y := c.Position.X, c.Position.X, c.Position.X, c.Position.Y
		if y%2 != 0 {
			xe++
		}
		if y%2 == 0 {
			xw--
		}

		neighbors := []Pos{
			{xe, y - 1},
			{x + 1, y},
			{xe, y + 1},
			{xw, y + 1},
			{x - 1, y},
			{xw, y - 1},
		}
		var flipped int
		if debug {
			fmt.Printf("%+v is %v, neighbors...\n", c.Position, c.Flipped)
		}
		for i := range neighbors {
			cell, ok := hg.Cells[neighbors[i]]
			if debug {
				if ok {
					fmt.Printf("\t%+v is %v\n", cell.Position, cell.Flipped)
				} else {
					fmt.Printf("\t%+v [none] \n", neighbors[i])
				}
			}
			if ok && cell.Flipped {
				flipped++
			}
		}
		if debug {
			fmt.Printf("num flipped: %d\n", flipped)
		}

		cell := *c
		if (cell.Flipped && (flipped == 0 || flipped > 2)) ||
			(!cell.Flipped && flipped == 2) {
			if debug {
				fmt.Printf("=> flipping (%v and %d) <==\n", cell.Flipped, flipped)
			}
			cell.Flipped = !cell.Flipped
		}
		cells[cell.Position] = &cell
	}

	hg.Cells = cells
}

// Task1 ...
func Task1(input []string, debug bool) int {
	hg := HexGrid{
		Root:  &Cell{},
		Cells: make(map[Pos]*Cell),
	}
	for _, in := range input {
		hg.MakeMoves(in, debug)
	}

	var result int
	for _, c := range hg.Cells {
		if c.Flipped {
			result++
		}
	}
	return result
}

// Task2 ...
func Task2(input []string, iterate int, debug bool) int {
	hg := HexGrid{
		Root:  &Cell{},
		Cells: make(map[Pos]*Cell),
	}
	for _, in := range input {
		hg.MakeMoves(in, debug)
	}

	for i := 0; i < iterate; i++ {
		if debug {
			fmt.Printf("Day %d:", i+1)
		}
		hg.IterateLife(debug)
		if debug {
			var result int
			for _, c := range hg.Cells {
				if c.Flipped {
					result++
				}
			}
			fmt.Printf(" %2d\n", result)
		}
	}

	var result int
	for _, c := range hg.Cells {
		if c.Flipped {
			result++
		}
	}
	return result
}

func readInput(reader io.Reader) []string {
	input := make([]string, 0, 10)
	scanner := bufio.NewScanner(reader)
	for scanner.Scan() {
		input = append(input, scanner.Text())
	}
	return input
}

func main() {
	var debug bool
	flag.BoolVar(&debug, "debug", false, "debug")
	flag.Parse()

	var input []string
	file, _ := os.Open("input.txt")
	input = readInput(file)
	file.Close()

	result := Task1(input, debug)
	fmt.Printf("Task 1: %d\n", result)

	result = Task2(input, 100, debug)
	fmt.Printf("Task 2: %d\n", result)
}