package main

import (
	"bufio"
	"fmt"
	"log"
	"os"
	"regexp"
	"strconv"
	"strings"
)

var depth int
var target *Pos

func main() {
	input := stdinToStringSlice()
	reg, err := regexp.Compile("[^0-9]+")
	if err != nil {
		log.Fatal(err)
	}
	depth = Atoi(reg.ReplaceAllString(input[0], ""))
	tgt := strings.Trim(input[1], "target: ")
	tgtPts := strings.Split(tgt, ",")
	target = NewPos(Atoi(tgtPts[0]), Atoi(tgtPts[1]), depth)

}

func part1() {
	c := BuildCave(target)
	var risk int
	for y := 0; y <= target.y; y++ {
		for x := 0; x <= target.x; x++ {
			risk += c.risk(x, y, depth)
		}
	}
	fmt.Println("= Part 1 =")
	fmt.Println(risk)
}

func part2() {
	// Find shortest path form 0,0 to target
	// taking into account cost of changing gear
	// when necessary

}

type Cave struct {
	positions map[string]*Pos
}

func BuildCave(target *Pos) *Cave {
	c := &Cave{
		positions: make(map[string]*Pos),
	}
	c.addPos(target)
	return c
}

func (c *Cave) addPos(p *Pos) {
	c.positions[p.string()] = p
}

func (c *Cave) getPos(x, y, z int) *Pos {
	var p *Pos
	var ok bool
	if p, ok = c.positions[c.buildKey(x, y, z)]; !ok {
		p = NewPos(x, y, z)
		c.addPos(p)
	}
	return p
}

func (c *Cave) buildKey(x, y, z int) string {
	return fmt.Sprintf(
		"(%d,%d,%d)",
		x, y, z,
	)
}

func (c *Cave) getGeoIndex(x, y, z int) int {
	p := c.getPos(x, y, z)
	if p.geo < 0 {
		if x == 0 && y == 0 {
			p.geo = 0
		} else if x == target.x && y == target.y {
			p.geo = 0
		} else if y == 0 {
			p.geo = x * 16807
		} else if x == 0 {
			p.geo = y * 48271
		} else {
			p.geo = c.erosion(x-1, y, z) * c.erosion(x, y-1, z)
		}
	}
	return p.geo
}

func (c *Cave) erosion(x, y, z int) int {
	p := c.getPos(x, y, z)
	if p.erosion < 0 {
		p.erosion = (c.getGeoIndex(x, y, z) + z) % 20183
	}
	return p.erosion
}

func (c *Cave) risk(x, y, z int) int {
	return c.erosion(x, y, z) % 3
}

type Pos struct {
	x, y, z      int
	geo, erosion int
}

func NewPos(x, y, z int) *Pos {
	r := &Pos{
		x:       x,
		y:       y,
		z:       z,
		geo:     -1,
		erosion: -1,
	}

	return r
}

func (p *Pos) equals(n *Pos) bool {
	return p.x == n.x && p.y == n.y && p.z == n.z
}

func (p *Pos) string() string {
	return fmt.Sprintf(
		"(%d,%d,%d)",
		p.x, p.y, p.z,
	)
}

func stdinToStringSlice() []string {
	var input []string
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		input = append(input, scanner.Text())
	}
	return input
}

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