package main

import "fmt"

const (
	geologicY  = 16807
	geologicX  = 48271
	caveModulo = 20183
)

const (
	TypeRocky  = 0
	TypeWet    = 1
	TypeNarrow = 2
)

const (
	ToolNone = 1 << iota
	ToolTorch
	ToolGear
)

type Map struct {
	target               coord
	depth                int
	geologicIndicesCache map[int]map[int]int
	erosionLevelsCache   map[int]map[int]int
}

func NewMap(input []string) *Map {
	m := Map{}
	m.geologicIndicesCache = make(map[int]map[int]int)
	m.erosionLevelsCache = make(map[int]map[int]int)

	if len(input) != 2 {
		panic("Invalid Input")
	}
	_, err := fmt.Sscanf(input[0], "depth: %d", &m.depth)
	if err != nil {
		panic(err)
	}
	_, err = fmt.Sscanf(input[1], "target: %d,%d", &m.target.x, &m.target.y)
	if err != nil {
		panic(err)
	}

	return &m
}

func (m *Map) GeologicIndex(x, y int) int {
	if m.geologicIndicesCache[y] != nil {
		if i, ok := m.geologicIndicesCache[y][x]; ok {
			return i
		}
	} else {
		m.geologicIndicesCache[y] = make(map[int]int)
	}

	switch {
	case x == 0 && y == 0, x == m.target.x && y == m.target.y:
		m.geologicIndicesCache[y][x] = 0
	case y == 0:
		m.geologicIndicesCache[y][x] = x * geologicY
	case x == 0:
		m.geologicIndicesCache[y][x] = y * geologicX
	default:
		m.geologicIndicesCache[y][x] = m.ErosionLevel(x-1, y) * m.ErosionLevel(x, y-1)
	}
	return m.geologicIndicesCache[y][x]
}

func (m *Map) ErosionLevel(x, y int) int {
	if m.erosionLevelsCache[y] != nil {
		if level, ok := m.erosionLevelsCache[y][x]; ok {
			return level
		}
	} else {
		m.erosionLevelsCache[y] = make(map[int]int)
	}
	m.erosionLevelsCache[y][x] = (m.GeologicIndex(x, y) + m.depth) % caveModulo
	return m.erosionLevelsCache[y][x]
}

func (m Map) Type(x, y int) int {
	return m.ErosionLevel(x, y) % 3
}

func (m Map) Neighbors(pos coord, equip int) []Item {
	var n []Item
	for _, c := range pos.neighbors() {
		t := m.Type(c.x, c.y)
		if equip&allowed(t) != 0 {
			n = append(n, Item{pos: c, equip: equip, time: 1})
			n = append(n, Item{pos: c, equip: equip ^ allowed(t), time: 8})
		}
	}
	return n
}

func allowed(regionType int) int {
	switch regionType {
	case TypeRocky:
		return ToolGear | ToolTorch
	case TypeWet:
		return ToolGear | ToolNone
	case TypeNarrow:
		return ToolTorch | ToolNone
	default:
		panic(fmt.Errorf("unknown region type: %d", regionType))
	}
}
