package main

import (
	"fmt"
	"strings"

	h "git.bullercodeworks.com/brian/adventofcode/helpers"
)

var minX, maxX, minY, maxY int

func main() {
	maxX = h.MIN_INT
	minX = h.MAX_INT
	maxY = h.MIN_INT
	minY = h.MAX_INT
	inp := h.StdinToStringSlice()
	lines := parseLines(inp)

	fmt.Println("# Part 1")
	findDangerSpots(filterStraight(lines))
	fmt.Println("# Part 2")
	findDangerSpots(lines)
}

var ventMap map[h.Coordinate]int

var dangerSpots []h.Coordinate

func dingMap(c h.Coordinate) {
	s := ventMap[c]
	s++
	if s == 2 {
		dangerSpots = append(dangerSpots, c)
	}
	ventMap[c] = s
}

func findDangerSpots(lines []Line) {
	ventMap = make(map[h.Coordinate]int)
	for i := range lines {
		updateMinMax(lines[i])
	}
	for _, l := range lines {
		// Find all points on this line
		// All lines are 0 or 45 deg, so slope should always be 0,1,-1,INF,-INF
		if l.start.X == l.end.X { // INF, -INF: vertical
			for y := l.lowestY(); y <= l.highestY(); y++ {
				dingMap(h.Coordinate{X: l.start.X, Y: y})
			}
		} else if l.start.Y == l.end.Y { //0: horizontal
			for x := l.start.X; x <= l.end.X; x++ {
				dingMap(h.Coordinate{Y: l.start.Y, X: x})
			}
		} else { // 1 or -1
			y := l.start.Y
			x := l.start.X
			dingMap(h.Coordinate{Y: y, X: x})

			diffY := -1
			if l.start.Y < l.end.Y {
				diffY = 1
			}

			for x != l.end.X && y != l.end.Y {
				x += 1
				y += diffY
				dingMap(h.Coordinate{Y: y, X: x})
			}
		}
	}
	fmt.Println("Danger Spots:", len(dangerSpots))
}

type Line struct {
	start h.Coordinate
	end   h.Coordinate

	stX float64
	stY float64
	edX float64
	edY float64

	slope float64
}

func (l *Line) lowestY() int {
	if l.start.Y < l.end.Y {
		return l.start.Y
	}
	return l.end.Y
}

func (l *Line) highestY() int {
	if l.start.Y > l.end.Y {
		return l.start.Y
	}
	return l.end.Y
}

func (l *Line) intersects(c h.Coordinate) bool {
	ptX := float64(c.X)
	ptY := float64(c.Y)
	if (c.X == l.start.X && c.Y == l.start.Y) || (c.X == l.end.X && c.Y == l.end.Y) {
		return true
	}
	if c.X < l.start.X || c.X > l.end.X {
		return false
	}
	if (c.Y < l.start.Y && c.Y < l.end.Y) || (c.Y > l.start.Y && c.Y > l.end.Y) {
		return false
	}
	// Check if our slope is infinite
	if l.start.X == l.end.X {
		if l.start.Y < l.end.Y {
			return c.X == l.start.X && c.Y >= l.start.Y && c.Y <= l.end.Y
		} else {
			return c.X == l.start.X && c.Y <= l.start.Y && c.Y >= l.end.Y
		}
	}
	return slope(ptX, ptY, l.stX, l.stY) == l.slope
}

func (l Line) String() string {
	return fmt.Sprintf("%d,%d -> %d,%d; [%f]", l.start.X, l.start.Y, l.end.X, l.end.Y, l.slope)
}

func LineFromString(str string) Line {
	start := h.Coordinate{}
	end := h.Coordinate{}
	r := strings.NewReader(str)
	_, err := fmt.Fscanf(r, "%d,%d -> %d,%d", &start.X, &start.Y, &end.X, &end.Y)
	if err != nil {
		panic(err)
	}
	var l Line
	if start.X < end.X {
		l = Line{
			start: start,
			end:   end,
		}
	} else {
		l = Line{
			start: end,
			end:   start,
		}
	}
	l.stX = float64(l.start.X)
	l.stY = float64(l.start.Y)
	l.edX = float64(l.end.X)
	l.edY = float64(l.end.Y)
	l.slope = slope(l.stX, l.stY, l.edX, l.edY)
	return l
}

func updateMinMax(line Line) {
	if line.start.X < minX {
		minX = line.start.X
	}
	if line.start.X > maxX {
		maxX = line.start.X
	}
	if line.end.X < minX {
		minX = line.end.X
	}
	if line.end.X > maxX {
		maxX = line.end.X
	}
	if line.start.Y < minY {
		minY = line.start.Y
	}
	if line.start.Y > maxY {
		maxY = line.start.Y
	}
	if line.end.Y < minY {
		minY = line.end.Y
	}
	if line.end.Y > maxY {
		maxY = line.end.Y
	}
}

func parseLines(inp []string) []Line {
	var ret []Line
	for i := range inp {
		ret = append(ret, LineFromString(inp[i]))
	}
	return ret
}

func filterStraight(lines []Line) []Line {
	var ret []Line
	for i := range lines {
		if lines[i].start.X == lines[i].end.X || lines[i].start.Y == lines[i].end.Y {
			ret = append(ret, lines[i])
		}
	}
	return ret
}

func slope(x1, y1, x2, y2 float64) float64 {
	return (y1 - y2) / (x1 - x2)
}
