package main

import (
	"fmt"
	"math"
)

const (
	springX = 500
	springY = 0
)
const (
	KindSand = 1 << iota
	KindClay
	KindWater
	KindFlow
	KindSpring
	NumKinds
)

var KindRunes = [NumKinds]rune{
	KindSand:   '.',
	KindClay:   '#',
	KindWater:  '~',
	KindFlow:   '|',
	KindSpring: '+',
}

type Map struct {
	data    map[int]map[int]int
	minX    int
	minY    int
	maxX    int
	maxY    int
	done    bool
	changed bool
}

func (m *Map) Set(kind, x, y int) {
	if m.data[y] == nil {
		m.data[y] = make(map[int]int)
	}
	if m.data[y][x] != kind {
		m.data[y][x] = kind
		m.changed = true
	}
	if x < m.minX {
		m.minX = x
	}
	if x > m.maxX {
		m.maxX = x
	}
}

func (m *Map) Get(x, y int) int {
	if m.data[y] == nil {
		return KindSand
	}
	if k, ok := m.data[y][x]; ok {
		return k
	}
	return KindSand
}

func (m *Map) ParseInput(input []string) {
	m.minY = math.MaxInt32
	m.maxY = 0
	m.minX = math.MaxInt32
	m.maxX = 0

	m.Set(KindSpring, springX, springY)

	for _, line := range input {
		var a, b rune
		var posA, startB, endB int
		_, err := fmt.Sscanf(line, "%c=%d, %c=%d..%d", &a, &posA, &b, &startB, &endB)
		if err != nil {
			panic(err)
		}
		if a == 'x' {
			for y := startB; y <= endB; y++ {
				m.Set(KindClay, posA, y)
			}
			if posA < m.minX {
				m.minX = posA
			}
			if posA > m.maxX {
				m.maxX = posA
			}
			if startB < m.minY {
				m.minY = startB
			}
			if endB > m.maxY {
				m.maxY = endB
			}
		} else {
			for x := startB; x <= endB; x++ {
				m.Set(KindClay, x, posA)
			}
			if posA < m.minY {
				m.minY = posA
			}
			if posA > m.maxY {
				m.maxY = posA
			}
			if startB < m.minX {
				m.minX = startB
			}
			if endB > m.maxX {
				m.maxX = endB
			}
		}
	}
}

func (m *Map) Print() {
	for y := 0; y <= m.maxY; y++ {
		for x := m.minX; x <= m.maxX; x++ {
			fmt.Printf("%c", KindRunes[m.Get(x, y)])
		}
		fmt.Println()
	}
}

func (m *Map) Count(mask int) int {
	var sum int
	for y := m.minY; y <= m.maxY; y++ {
		for x := m.minX; x <= m.maxX; x++ {
			if m.Get(x, y)&mask != 0 {
				sum++
			}
		}
	}
	return sum
}

func NewMap(input []string) *Map {
	m := &Map{}
	m.data = make(map[int]map[int]int)
	m.ParseInput(input)
	return m
}

type Coordinate struct {
	X int
	Y int
}

func (m *Map) Tick() {
	m.changed = false
	for y := 0; y <= m.maxY; y++ {
		for x := m.minX; x <= m.maxX; x++ {
			switch m.Get(x, y) {
			case KindSpring, KindFlow:
				switch m.Get(x, y+1) {
				case KindSand:
					m.Set(KindFlow, x, y+1)
				case KindClay, KindWater:
					m.HorizontalFlow(x, y, -1)
					m.HorizontalFlow(x, y, +1)
				}
			case KindClay:
				m.HorizontalSettle(x+1, y, +1)
			}
		}
	}
	m.done = !m.changed
}

func (m *Map) HorizontalSettle(x, y, dir int) bool {
	if m.Get(x, y) != KindFlow {
		return false
	}
	switch m.Get(x+dir, y) {
	case KindClay, KindWater:
		if m.Get(x, y+1)&(KindClay|KindWater) != 0 {
			m.Set(KindWater, x, y)
			return true
		}
	case KindFlow:
		if m.HorizontalSettle(x+dir, y, dir) && m.Get(x, y+1)&(KindClay|KindWater) != 0 {
			m.Set(KindWater, x, y)
			return true
		}
	}
	return false
}

func (m *Map) HorizontalFlow(x, y, dir int) {
	if m.Get(x+dir, y) == KindSand && m.Get(x, y+1)&(KindClay|KindWater) != 0 {
		m.Set(KindFlow, x+dir, y)
		m.HorizontalFlow(x+dir, y, dir)
	}
}

func (m *Map) Flood() {
	for !m.done {
		m.Tick()
	}
}
