package main

import (
	"AOC2021/src/helper"
	"fmt"
	"math"
	"os"
	"strings"
)

type scanner struct {
	points   [][3]int
	position [3]int
}

func main() {
	args := os.Args[1:]
	input, err := helper.GetInput(args[0])
	if err != nil {
		fmt.Println(err)
	}
	var scanners []scanner
	for _, row := range input {
		if row[1] == '-' {
			scanners = append(scanners, scanner{[][3]int{}, [3]int{}})
		} else {
			tmpPoint, _ := helper.MapToNumber(strings.Split(row, ","))
			scanners[len(scanners)-1].points = append(scanners[len(scanners)-1].points, [3]int{tmpPoint[0], tmpPoint[1], tmpPoint[2]})
		}
	}
	//hitScanners := []int{0}
	//run := true
	//for run {
	//	run = runUntilFirstHit(&scanners, &hitScanners)
	//}
	runFor(&scanners, 0, 1)
	runFor(&scanners, 0, 4)
	runFor(&scanners, 0, 2)
	runFor(&scanners, 0, 3)
	fmt.Println(len(scanners[0].points))

	//inputTestResult, _ := helper.GetInput("day19TestResultPoints.txt")
	//resultPoints := [][3]int{}
	//for _, row := range inputTestResult {
	//	tmpPoint, _ := helper.MapToNumber(strings.Split(row, ","))
	//	resultPoints = append(resultPoints, [3]int{tmpPoint[0], tmpPoint[1], tmpPoint[2]})
	//}
	//
	//fmt.Println(helper.Difference3DPoint(resultPoints, scanners[0].points))
	//
	//fmt.Println(scanners[1].position)
	//fmt.Println(scanners[2].position)
	//fmt.Println(scanners[3].position)
	//fmt.Println(scanners[4].position)
	////fmt.Println(scanners[5].position)
	////fmt.Println(scanners[6].position)
	//fmt.Println(len(scanners[0].points))
}

func runUntilFirstHit(scanners *[]scanner, hitScanners *[]int) bool {
	for i := 0; i < len(*scanners); i++ {
		if !helper.Contains(i, *hitScanners) && runFor(scanners, 0, i) {
			*hitScanners = append(*hitScanners, i)
			return true
		}
	}
	return false
}

func runFor(scanners *[]scanner, i int, j int) bool {
	returnBool := checkIfScannersHaveMatchingBeacons(scanners, i, j)
	pointSet := make(map[[3]int]struct{})
	for _, point := range (*scanners)[i].points {
		if point[0] == 26 {
			fmt.Println("Help")
		}
		pointSet[point] = struct{}{}
	}
	(*scanners)[i].points = [][3]int{}
	for key, _ := range pointSet {
		(*scanners)[i].points = append((*scanners)[i].points, key)
	}
	return returnBool
}

func checkIfScannersHaveMatchingBeacons(scanners *[]scanner, i int, j int) bool {
	scanner1 := &(*scanners)[i]
	scanner2 := &(*scanners)[j]
	pointMapper := getOverlappingPoints(*scanner1, *scanner2)
	if len(pointMapper) >= 12 {
		rotationsForScanner := getAllPossibleRotationsForScanner(*scanner2)
		i, diff := getRightRotation(scanner1, pointMapper, rotationsForScanner)
		if i > -1 {
			scanner2.position = [3]int{scanner1.position[0] + diff[0], scanner1.position[1] + diff[1], scanner1.position[2] + diff[2]}
			var pointsOfScanner2inRelationToScanner1 [][3]int

			for x := 0; x < len(rotationsForScanner[0]); x++ {
				point := rotationsForScanner[x][i]
				pointWithDiff := [3]int{point[0] + diff[0], point[1] + diff[1], point[2] + diff[2]}
				pointsOfScanner2inRelationToScanner1 = append(pointsOfScanner2inRelationToScanner1, pointWithDiff)
			}
			scanner1.points = append(scanner1.points, pointsOfScanner2inRelationToScanner1...)
			return true
		}
	}
	return false
}

func getRightRotation(scanner1 *scanner, pointMapper [][2]int, rotationsForScanner [][][3]int) (int, [3]int) {
	for i := 0; i < 24; i++ {
		firstDiff := diffBetweenVectors(rotationsForScanner[pointMapper[0][1]][i], scanner1.points[pointMapper[0][0]])
		different := false
		for n := 1; n < len(pointMapper); n++ {
			rotation := rotationsForScanner[pointMapper[n][1]][i]
			diff := diffBetweenVectors(rotation, scanner1.points[pointMapper[n][0]])
			if firstDiff != diff {
				different = true
			}
		}
		if different == false {
			return i, firstDiff
		}
	}
	return -1, [3]int{0, 0, 0}
}

func getOverlappingPoints(sc1 scanner, sc2 scanner) (pointPairs [][2]int) {
	distances0 := getallDistancesBetweenPointsForScanner(sc1)
	distances1 := getallDistancesBetweenPointsForScanner(sc2)
	for i, _ := range distances0 {
		for j, _ := range distances1 {
			if len(helper.OverlapFloatArray(distances0[i], distances1[j])) >= 11 {
				pointPairs = append(pointPairs, [2]int{i, j})
			}
		}
	}
	return
}

func turn(point *[3]int) {
	*point = [3]int{-point[1], point[0], point[2]}
}

func roll(point *[3]int) {
	*point = [3]int{point[0], point[2], -point[1]}
}

func getAllPossibleRotationsForPoint(point *[3]int) (points [][3]int) {
	rotationOrder := "RTTTRTTTRTTT"
	switchRotationOrder := "RTR"
	points = append(points, *point)
	executeRotationOrder(point, rotationOrder, &points)
	executeRotationOrder(point, switchRotationOrder, &[][3]int{})
	executeRotationOrder(point, rotationOrder, &points)
	return
}

func executeRotationOrder(point *[3]int, order string, points *[][3]int) {
	for _, char := range order {
		switch char {
		case 'R':
			roll(point)
		case 'T':
			turn(point)
		}
		*points = append(*points, *point)
	}
	return
}

func getAllPossibleRotationsForScanner(scanner scanner) [][][3]int {
	permutations := [][][3]int{}
	for _, point := range scanner.points {
		permutations = append(permutations, getAllPossibleRotationsForPoint(&point))
	}
	return permutations
}

func getallDistancesBetweenPointsForScanner(scanner scanner) [][]float64 {
	var distances [][]float64
	for i, point1 := range scanner.points {
		var distancesForPoint []float64
		for j, point2 := range scanner.points {
			if i != j {
				distancesForPoint = append(distancesForPoint, distanceBetweenVectors(point1, point2))
			}
		}
		distances = append(distances, distancesForPoint)
	}
	return distances
}

func distanceBetweenVectors(v1 [3]int, v2 [3]int) float64 {
	var diffs [3]float64
	for i, _ := range v1 {
		diffs[i] = float64(v2[i] - v1[i])
	}
	return math.Sqrt(math.Pow(diffs[0], 2) + math.Pow(diffs[1], 2) + math.Pow(diffs[2], 2))
}

func diffBetweenVectors(v1 [3]int, v2 [3]int) [3]int {
	var diff [3]int
	for i, _ := range v1 {
		diff[i] = v2[i] - v1[i]
	}
	return diff
}
