package main

import (
	"errors"
	"fmt"
	"sort"
	"strings"

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

type Pair struct {
	a int
	b int
}

type Translation struct {
	orig, foreign, rotID int
}

type Triangle struct {
	a, b, c             Beacon
	circumference, area int
}

func NewTriangle(a, b, c Beacon) Triangle {
	t := Triangle{a: a, b: b, c: c}
	t.circumference = findCircumference(a, b, c)
	t.area = findArea(a, b, c)
	return t
}

type Scanner struct {
	number  int
	beacons []Beacon
	prints  []Triangle
}

func NewScannerFromInput(inp []string) (Scanner, error) {
	s := Scanner{}
	r := strings.NewReader(inp[0])
	_, err := fmt.Fscanf(r, "--- scanner %d ---", &s.number)
	if err != nil {
		return s, errors.New("No Scanner ID")
	}
	for _, v := range inp[1:] {
		r = strings.NewReader(v)
		b := Beacon{}
		_, err := fmt.Fscanf(r, "%d,%d,%d", &b.x, &b.y, &b.z)
		if err == nil {
			s.beacons = append(s.beacons, b)
		}
	}
	prints := make([]Triangle, 0, len(s.beacons))
	dupes := make(map[Pair]struct{})
	for _, b := range s.beacons {
		c1, c2 := b.findNearestTwo(s.beacons)
		t := NewTriangle(b, c1, c2)
		p := Pair{t.area, t.circumference}
		if _, ok := dupes[p]; !ok {
			prints = append(prints, t)
		}
		dupes[p] = struct{}{}
	}

	return s, nil
}

type Beacon struct {
	x, y, z int
}

func (b Beacon) Add(c Beacon) Beacon {
	return Beacon{
		x: b.x + c.x,
		y: b.y + c.y,
		z: b.z + c.z,
	}
}

func (b Beacon) Sub(c Beacon) Beacon {
	return Beacon{
		x: b.x - c.x,
		y: b.y - c.y,
		z: b.z - c.z,
	}
}

func (b Beacon) Negate() Beacon {
	return Beacon{
		x: -b.x,
		y: -b.y,
		z: -b.z,
	}
}

func (b Beacon) Equals(c Beacon) bool {
	return b.x == c.x && b.y == c.y && b.z == c.z
}

func (b Beacon) RotateX90() Beacon {
	return Beacon{
		x: b.x,
		y: -b.z,
		z: b.y,
	}
}

func (b Beacon) RotateY90() Beacon {
	return Beacon{
		x: -b.z,
		y: b.y,
		z: b.x,
	}
}

func (b Beacon) RotateZ90() Beacon {
	return Beacon{
		x: b.y,
		y: -b.x,
		z: b.z,
	}
}

func (b Beacon) AllRotations() []Beacon {
	return []Beacon{
		{b.x, b.y, b.z},
		{b.x, -b.z, b.y},
		{b.x, -b.y, -b.z},
		{b.x, b.z, -b.y},
		{-b.x, -b.y, b.z},
		{-b.x, -b.z, -b.y},
		{-b.x, b.y, -b.z},
		{-b.x, b.z, b.y},
		{-b.z, b.x, -b.y},
		{b.y, b.x, -b.z},
		{b.z, b.x, b.y},
		{-b.y, b.x, b.z},
		{b.z, -b.x, -b.y},
		{b.y, -b.x, b.z},
		{-b.z, -b.x, b.y},
		{-b.y, -b.x, -b.z},
		{-b.y, -b.z, b.x},
		{b.z, -b.y, b.x},
		{b.y, b.z, b.x},
		{-b.z, b.y, b.x},
		{b.z, b.y, -b.x},
		{-b.y, b.z, -b.x},
		{-b.z, -b.y, -b.x},
		{b.y, -b.z, -b.x},
	}
}

func (b Beacon) Rotate(rid int) Beacon {
	l := b.AllRotations()
	if rid < len(l) {
		return l[rid]
	}
	return b
}

func (b Beacon) Distance(c Beacon) int {
	return h.Abs(b.x-c.x) + h.Abs(b.y-c.y) + h.Abs(b.z-c.z)
}

func (b Beacon) findNearestTwo(list []Beacon) (Beacon, Beacon) {
	var distances []Pair
	for k, v := range list {
		if !b.Equals(v) {
			distances = append(distances, Pair{a: b.Distance(v), b: k})
		}
	}
	sort.Slice(distances, func(i, j int) bool { return distances[i].a < distances[j].a })
	return list[distances[0].b], list[distances[1].b]
}

func findCircumference(a, b, c Beacon) int {
	return a.Distance(b) + b.Distance(c) + c.Distance(a)
}

func findArea(a, b, c Beacon) int {
	wrk := crossProduct(b.Sub(a), c.Sub(a))
	return h.Abs(wrk.x) + h.Abs(wrk.y) + h.Abs(wrk.z)
}

func crossProduct(a, b Beacon) Beacon {
	return Beacon{
		x: a.y*b.z - a.z*b.y,
		y: a.z*b.x - a.x*b.z,
		z: a.x*b.y - a.y*b.x,
	}
}
