// Unused solution: yes, this is a SECOND failed solution. Left here as a testament
// to... something. The actual solution uses CalculateRange from point.go
package day10

import (
	"sort"
)

// GroupPoints creates groups of points, then sorts them and returns the sorted list
// A 'group' is a series of points that are adjacent to each other.
// If this function finds any non-adjacent points, it returns nil.
func GroupPoints(points []*Point) [][]*Point {
	// this seems a bit weird, but it's mapping points that just contain an x,y value to
	// the actual point objects with velocity information.
	pointMap := make(map[int]map[int]*Point)
	for _, p := range points {
		if pointMap[p.X] == nil {
			pointMap[p.X] = make(map[int]*Point)
		}
		pointMap[p.X][p.Y] = p
	}

	// If any point is not adjacent to at least one other, we are not done moving.
	for _, p := range points {
		if !adjacencyInMap(pointMap, p) {
			return nil
		}
	}

	// now, for each point, check for presence in existing groups,
	// then check the map for adjacencies
	// This could be more efficient, but the readability of the range
	// is desirable.
	groups := [][]*Point{}
	for _, p := range points {
		if checkGroups(groups, p) {
			continue
		}

		// Find and place all adjacent points.
		group := buildGroup(pointMap, []*Point{}, p)
		groups = append(groups, group)
	}

	// sort the groups
	sort.Slice(groups[:], func(i, j int) bool {
		xMin1, _, _, _ := findBounds(groups[i])
		xMin2, _, _, _ := findBounds(groups[j])
		return xMin1 < xMin2
	})

	return groups
}

// returns true if point is already a member of one of groups
func checkGroups(groups [][]*Point, point *Point) bool {
	for _, group := range groups {
		if checkGroup(group, point) {
			return true
		}
	}
	return false
}

func checkGroup(group []*Point, point *Point) bool {
	for _, member := range group {
		if point == member {
			return true
		}
	}
	return false
}

func arePointsAdjacent(p1, p2 *Point) bool {
	return (p1.X == p2.X-1 || p1.X == p2.X+1 || p1.X == p2.X) &&
		(p1.Y == p2.Y-1 || p1.Y == p2.Y+1 || p1.Y == p2.Y)
}

func adjacencyInMap(pMap map[int]map[int]*Point, p *Point) bool {
	return (pMap[p.X-1] != nil && pMap[p.X-1][p.Y] != nil) ||
		(pMap[p.X+1] != nil && pMap[p.X+1][p.Y] != nil) ||
		pMap[p.X][p.Y-1] != nil || pMap[p.X][p.Y+1] != nil
}

// recursively walk all adjacent points, adding them to the group.
func buildGroup(pMap map[int]map[int]*Point, group []*Point, p *Point) []*Point {
	// this both prevents duplicates and prevents infinite recursion
	if checkGroup(group, p) {
		return group
	}

	group = append(group, p)

	// look in each of 8 directions, if a point is found, recurse
	if pMap[p.X-1] != nil {
		if pMap[p.X-1][p.Y] != nil {
			group = buildGroup(pMap, group, pMap[p.X-1][p.Y])
		}
		if pMap[p.X-1][p.Y-1] != nil {
			group = buildGroup(pMap, group, pMap[p.X-1][p.Y-1])
		}
		if pMap[p.X-1][p.Y+1] != nil {
			group = buildGroup(pMap, group, pMap[p.X-1][p.Y+1])
		}
	}
	if pMap[p.X+1] != nil {
		if pMap[p.X+1][p.Y] != nil {
			group = buildGroup(pMap, group, pMap[p.X+1][p.Y])
		}
		if pMap[p.X+1][p.Y-1] != nil {
			group = buildGroup(pMap, group, pMap[p.X+1][p.Y-1])
		}
		if pMap[p.X+1][p.Y+1] != nil {
			group = buildGroup(pMap, group, pMap[p.X+1][p.Y+1])
		}
	}
	if pMap[p.X][p.Y-1] != nil {
		group = buildGroup(pMap, group, pMap[p.X][p.Y-1])
	}
	if pMap[p.X][p.Y+1] != nil {
		group = buildGroup(pMap, group, pMap[p.X][p.Y+1])
	}

	return group
}