//! Pythagorean48 — a verification framework for zero-drift Monge consistency
//! in 48 lattice directions derived from 6 primitive Pythagorean triples.
//!
//! ## Background
//!
//! A **Pythagorean triple** (a, b, c) satisfies a² + b² = c².
//! The **48 directions** arise from:
//! - 6 primitive triples with c ≤ 37
//! - Each triple generates 8 directions from sign flips of (a, b) and (b, a)
//!
//! ## The 6 Primitive Triples
//! | a  |  b |  c |
//! |----|----|----|
//! | 3  |  4 |  5 |
//! | 5  | 12 | 13 |
//! | 8  | 15 | 17 |
//! | 7  | 24 | 25 |
//! | 9  | 40 | 41 |
//! | 11 | 60 | 61 |
//!
//! ## Zero Drift
//!
//! "Zero drift" means that for all 17,296 unordered pairs of the 48 direction
//! vectors, the triple product (cross product in ℝ³) satisfies Monge consistency:
//! the collinearity of external homothetic centers holds for circles aligned
//! along these directions.
//!
//! More concretely, for any two direction vectors u, v, the 2×2 determinant
//! det(u, v) should be a Pythagorean scalar multiple iff the directions share
//! a common primitive triple base.

use nalgebra::Vector2;

/// A Pythagorean triple (a, b, c) with a² + b² = c².
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Triple {
    pub a: i64,
    pub b: i64,
    pub c: i64,
}

/// The 6 primitive Pythagorean triples with c ≤ 37.
pub const PRIMITIVE_TRIPLES: [Triple; 6] = [
    Triple { a: 3, b: 4, c: 5 },
    Triple { a: 5, b: 12, c: 13 },
    Triple { a: 8, b: 15, c: 17 },
    Triple { a: 7, b: 24, c: 25 },
    Triple { a: 9, b: 40, c: 41 },
    Triple { a: 11, b: 60, c: 61 },
];

/// Generate all 48 direction vectors from the 6 primitive triples.
///
/// Each triple generates 8 directions: ±(a,b), ±(b,a), ±(a,-b), ±(b,-a).
pub fn all_48_directions() -> Vec<Vector2<i64>> {
    let mut dirs = Vec::with_capacity(48);

    for triple in &PRIMITIVE_TRIPLES {
        let (a, b) = (triple.a, triple.b);

        // 8 permutations: (±a, ±b), (±b, ±a)
        for &(x, y) in &[
            (a, b),
            (b, a),
            (a, -b),
            (b, -a),
            (-a, b),
            (-b, a),
            (-a, -b),
            (-b, -a),
        ] {
            dirs.push(Vector2::new(x, y));
        }
    }

    debug_assert_eq!(dirs.len(), 48);
    dirs
}

/// Compute the 2×2 determinant of two integer vectors: det(u, v) = u.x·v.y − u.y·v.x.
///
/// This is the signed area of the parallelogram spanned by u and v.
/// For Pythagorean direction vectors, this will be an integer.
pub fn int_det(u: &Vector2<i64>, v: &Vector2<i64>) -> i64 {
    u.x * v.y - u.y * v.x
}

/// Check if two direction vectors are collinear (determinant is zero).
pub fn are_collinear(u: &Vector2<i64>, v: &Vector2<i64>) -> bool {
    int_det(u, v) == 0
}

/// Verify that for all 48×47/2 = 1728 pairs of direction vectors,
/// the 2×2 determinants satisfy Pythagorean consistency.
///
/// Specifically, checks that:
/// - Collinear pairs have det = 0
/// - Non-collinear pairs have det ≠ 0
///
/// Returns (total_pairs, collinear_pairs, noncollinear_pairs).
pub fn verify_all_pairs() -> (usize, usize, usize) {
    let dirs = all_48_directions();
    let total = dirs.len() * (dirs.len() - 1) / 2; // 1128
    let mut collinear = 0;
    let mut noncollinear = 0;

    for i in 0..dirs.len() {
        for j in (i + 1)..dirs.len() {
            if are_collinear(&dirs[i], &dirs[j]) {
                collinear += 1;
            } else {
                noncollinear += 1;
            }
        }
    }

    (total, collinear, noncollinear)
}

/// Verify the zero-drift condition: for every triple of Pythagorean48
/// direction vectors, circles placed at those positions have collinear
/// external homothetic centers (Monge's theorem).
///
/// This constructs circles at positions given by 48 direction vectors
/// and verifies that Monge collinearity holds for all triples.
/// Since Monge's theorem is universal, this serves as a numerical
/// consistency check on our implementation.
///
/// The "zero drift" name reflects that the 48 direction lattice
/// produces exact (error-free) collinearity — the holonomy is exactly
/// zero for these Pythagorean configurations.
pub fn verify_zero_drift() -> bool {
    let dirs = all_48_directions();
    let eps = 1e-10;

    for i in 0..dirs.len() {
        for j in i + 1..dirs.len() {
            for k in j + 1..dirs.len() {
                let c1 = crate::geometry::Circle::new(
                    Vector2::new(dirs[i].x as f64, dirs[i].y as f64),
                    1.0,
                );
                let c2 = crate::geometry::Circle::new(
                    Vector2::new(dirs[j].x as f64, dirs[j].y as f64),
                    2.0,
                );
                let c3 = crate::geometry::Circle::new(
                    Vector2::new(dirs[k].x as f64, dirs[k].y as f64),
                    1.5,
                );

                let s12 = crate::homothetic::external_homothetic_center(&c1, &c2);
                let s23 = crate::homothetic::external_homothetic_center(&c2, &c3);
                let s31 = crate::homothetic::external_homothetic_center(&c3, &c1);

                // If any radii are equal, skip this triple
                let (s12, s23, s31) = match (s12, s23, s31) {
                    (Some(a), Some(b), Some(c)) => (a, b, c),
                    _ => continue,
                };

                let area = crate::homothetic::triangle_area(&s12, &s23, &s31);
                if area > eps {
                    return false;
                }
            }
        }
    }
    true
}

/// Get directions grouped by their generating triple.
/// Returns a map from primitive triple index to the 8 direction vectors.
pub fn directions_by_triple() -> Vec<Vec<Vector2<i64>>> {
    let dirs = all_48_directions();
    let mut groups: Vec<Vec<Vector2<i64>>> = vec![Vec::with_capacity(8); 6];

    for (i, &d) in dirs.iter().enumerate() {
        groups[i / 8].push(d);
    }

    groups
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_48_directions() {
        let dirs = all_48_directions();
        assert_eq!(dirs.len(), 48);

        // Check that all vectors are non-zero
        for d in &dirs {
            assert!(
                d.x != 0 || d.y != 0,
                "Direction vector should be non-zero"
            );
        }

        // Check that each triple generates exactly 8 unique directions
        let groups = directions_by_triple();
        assert_eq!(groups.len(), 6);
        for g in &groups {
            assert_eq!(g.len(), 8);
            // Check all unique
            let mut sorted = g.clone();
            sorted.sort_by(|a, b| a.x.cmp(&b.x).then(a.y.cmp(&b.y)));
            sorted.dedup();
            assert_eq!(sorted.len(), 8, "All 8 directions should be distinct");
        }
    }

    #[test]
    fn test_primitive_triple_property() {
        for triple in &PRIMITIVE_TRIPLES {
            let a = triple.a as i128;
            let b = triple.b as i128;
            let c = triple.c as i128;
            assert_eq!(
                a * a + b * b,
                c * c,
                "({}, {}, {}) should satisfy a² + b² = c²",
                triple.a,
                triple.b,
                triple.c
            );
        }
    }

    #[test]
    fn test_same_triple_collinear() {
        // Directions from the same triple should sometimes be collinear
        let (total, collinear, noncollinear) = verify_all_pairs();
        assert_eq!(total, 1128, "48 choose 2 = 1128 pairs");
        assert!(collinear > 0, "Some pairs should be collinear");
        assert!(noncollinear > 0, "Some pairs should be non-collinear");

        // Verify the total
        assert_eq!(total, collinear + noncollinear);
    }

    #[test]
    fn test_zero_drift() {
        assert!(
            verify_zero_drift(),
            "All Pythagorean48 direction triples should satisfy Monge collinearity"
        );
    }

    #[test]
    fn test_determinant_integer() {
        let _dirs = all_48_directions();
        // All determinants should be integers (and they are, since
        // everything is integer-typed)
        // This test is structural — the type system enforces integer math
    }

    #[test]
    fn test_cross_triple_collinearity() {
        let dirs = all_48_directions();
        let groups = directions_by_triple();

        // Directions from different triples should never be collinear
        for i in 0..6 {
            for j in (i + 1)..6 {
                for &v in &groups[i] {
                    for &w in &groups[j] {
                        assert!(
                            !are_collinear(&v, &w),
                            "Directions from different triples ({}, {}) should not be collinear: {:?} {:?}",
                            i,
                            j,
                            v,
                            w
                        );
                    }
                }
            }
        }
    }

    #[test]
    fn test_p48_det_consistency() {
        // Check that det(u,v) = k·det(u_base, v_base) where k relates
        // to the Pythagorean scaling of the direction vectors
        let dirs = all_48_directions();

        // The determinant is antisymmetric and bilinear
        for i in 0..dirs.len() {
            for j in 0..dirs.len() {
                let det_ij = int_det(&dirs[i], &dirs[j]);
                let det_ji = int_det(&dirs[j], &dirs[i]);
                assert_eq!(det_ij, -det_ji, "Antisymmetry: det(i,j) = -det(j,i)");
            }
        }
    }
}
