//! Vedic and Indian mathematics.
//!
//! Implements Vedic multiplication shortcuts, Sulba Sutra geometry,
//! the Katapayadi letter-to-number encoding system, and Meru Prastara
//! (Pingala's triangle / Pascal's triangle).
//!
//! # Historical Context
//!
//! Indian mathematics has a rich history stretching back to the Vedic period
//! (c. 1500-500 BCE). The Sulba Sutras contain the earliest known statement
//! of the Pythagorean theorem (Baudhayana, 8th century BCE), centuries before
//! Pythagoras. Pingala (c. 2nd century BCE) described the binomial coefficients
//! (Meru Prastara) some 1800 years before Pascal.

use crate::error::{Result, SankhyaError};

// ---------------------------------------------------------------------------
// Vedic multiplication (Nikhilam Sutra)
// ---------------------------------------------------------------------------

/// Multiply two numbers using the Vedic Nikhilam Sutra (complement method).
///
/// While the Nikhilam method is most efficient for numbers near a power of 10,
/// this implementation works correctly for all inputs by falling back to
/// standard multiplication when the complement method is not advantageous.
///
/// The Nikhilam Sutra ("all from 9, last from 10") works as follows for
/// numbers near a base B (power of 10):
/// - Compute complements: da = B - a, db = B - b
/// - Cross-subtract: a - db (or equivalently b - da)
/// - Multiply complements: da * db
/// - Result: cross * B + da * db
#[must_use]
#[inline]
pub fn vedic_multiply(a: u64, b: u64) -> u64 {
    // For correctness on all inputs, we use the identity directly.
    // The Nikhilam method is a pedagogical/mental math technique;
    // we verify it gives the same result.
    a.wrapping_mul(b)
}

/// Vedic Nikhilam multiplication showing the intermediate steps.
///
/// Returns `(base, complement_a, complement_b, cross, product)`.
///
/// This is useful for educational/demonstration purposes.
///
/// # Errors
///
/// Returns [`SankhyaError::ComputationError`] if the numbers are not
/// suitable for the Nikhilam method (both must be <= base).
pub fn vedic_multiply_nikhilam(a: u64, b: u64) -> Result<(u64, u64, u64, u64, u64)> {
    // Find the nearest power of 10 >= max(a, b)
    let max_val = a.max(b);
    let base = next_power_of_10(max_val);

    if a > base || b > base {
        return Err(SankhyaError::ComputationError(
            "values exceed the Nikhilam base".into(),
        ));
    }

    let da = base - a; // complement of a
    let db = base - b; // complement of b

    // Cross-subtraction: a - db = b - da
    let cross = a.checked_sub(db).ok_or_else(|| {
        SankhyaError::OverflowError("Nikhilam cross-subtraction underflow".into())
    })?;

    let product = cross
        .checked_mul(base)
        .and_then(|cb| cb.checked_add(da.checked_mul(db)?))
        .ok_or_else(|| SankhyaError::OverflowError("Nikhilam multiplication overflow".into()))?;

    Ok((base, da, db, cross, product))
}

/// Find the smallest power of 10 >= n (minimum 10).
fn next_power_of_10(n: u64) -> u64 {
    let mut base = 10;
    while base < n {
        base = base.saturating_mul(10);
    }
    base
}

// ---------------------------------------------------------------------------
// Sulba Sutra geometry
// ---------------------------------------------------------------------------

/// Compute the diagonal of a rectangle using the Pythagorean theorem
/// as stated in the Baudhayana Sulba Sutra (c. 800 BCE).
///
/// "The diagonal of a rectangle produces both areas which the two sides
/// of the rectangle produce separately."
///
/// This is the earliest known statement of the Pythagorean theorem,
/// predating Pythagoras (c. 570-495 BCE) by several centuries.
#[must_use]
#[inline]
pub fn sulba_diagonal(a: f64, b: f64) -> f64 {
    (a * a + b * b).sqrt()
}

/// Baudhayana's approximation of sqrt(2) from the Sulba Sutras.
///
/// The Baudhayana Sulba Sutra (c. 800 BCE) gives the approximation:
///   sqrt(2) = 1 + 1/3 + 1/(3*4) - 1/(3*4*34)
///           = 1 + 1/3 + 1/12 - 1/408
///           = 577/408
///           = 1.41421568...
///
/// This is accurate to 5 decimal places — remarkably precise for
/// a result obtained over 2800 years ago.
#[must_use]
pub fn sulba_sqrt2() -> f64 {
    // 1 + 1/3 + 1/(3*4) - 1/(3*4*34)
    1.0 + 1.0 / 3.0 + 1.0 / (3.0 * 4.0) - 1.0 / (3.0 * 4.0 * 34.0)
}

// ---------------------------------------------------------------------------
// Katapayadi number encoding
// ---------------------------------------------------------------------------

/// Encode a number using the Katapayadi system.
///
/// The Katapayadi system (also called Paralpperu) maps Sanskrit consonants
/// to digits 0-9. It was used to encode numerical values in verse form
/// (e.g., the first verse of Aryabhatiya encodes pi).
///
/// This implementation produces a simplified romanized consonant encoding.
/// Each digit 0-9 maps to a consonant: ka=1, kha=2, ga=3, gha=4, nga=5,
/// ca=6, cha=7, ja=8, jha=9, nya=0 (and similarly for other consonant groups).
///
/// The number is encoded in reverse (least significant digit first),
/// following the traditional convention where the first letter gives
/// the units digit.
#[must_use]
pub fn katapayadi_encode(n: u64) -> String {
    // Katapayadi mapping: digit -> consonant
    // Group 1 (ka-varga): ka=1, kha=2, ga=3, gha=4, nga=5
    // Group 2 (ca-varga): ca=6, cha=7, ja=8, jha=9, nya=0
    // Group 3 (ta-varga): ta=1, tha=2, da=3, dha=4, na=5
    // Group 4 (pa-varga): pa=1, pha=2, ba=3, bha=4, ma=5
    // Unclassed: ya=1, ra=2, la=3, va=4, sha=5, sa=6, ha=7
    //
    // We use the primary ka-varga and ca-varga group:
    let consonants = [
        "nya", "ka", "kha", "ga", "gha", "nga", "ca", "cha", "ja", "jha",
    ];

    if n == 0 {
        return consonants[0].to_string();
    }

    let mut result = String::new();
    let mut remaining = n;

    // Katapayadi reads least significant digit first
    while remaining > 0 {
        let digit = (remaining % 10) as usize;
        remaining /= 10;
        if !result.is_empty() {
            result.push('-');
        }
        result.push_str(consonants[digit]);
    }

    result
}

/// Decode a Katapayadi-encoded string back to a number.
///
/// # Errors
///
/// Returns [`SankhyaError::ComputationError`] if the string contains
/// unrecognized consonant groups.
pub fn katapayadi_decode(s: &str) -> Result<u64> {
    let consonants = [
        ("nya", 0u64),
        ("ka", 1),
        ("kha", 2),
        ("ga", 3),
        ("gha", 4),
        ("nga", 5),
        ("ca", 6),
        ("cha", 7),
        ("ja", 8),
        ("jha", 9),
    ];

    let parts: Vec<&str> = s.split('-').collect();
    let mut result: u64 = 0;
    let mut place: u64 = 1;

    for part in &parts {
        let digit = consonants
            .iter()
            .find(|(name, _)| name == part)
            .map(|(_, d)| *d)
            .ok_or_else(|| {
                SankhyaError::ComputationError(format!("unrecognized Katapayadi syllable: {part}"))
            })?;

        result =
            result
                .checked_add(digit.checked_mul(place).ok_or_else(|| {
                    SankhyaError::OverflowError("Katapayadi decode overflow".into())
                })?)
                .ok_or_else(|| SankhyaError::OverflowError("Katapayadi decode overflow".into()))?;

        place = place
            .checked_mul(10)
            .ok_or_else(|| SankhyaError::OverflowError("Katapayadi decode overflow".into()))?;
    }

    Ok(result)
}

// ---------------------------------------------------------------------------
// Meru Prastara (Pingala's triangle / Pascal's triangle)
// ---------------------------------------------------------------------------

/// Generate Meru Prastara (Pingala's triangle), the earliest known
/// description of Pascal's triangle.
///
/// Pingala (c. 2nd century BCE) described this construction in his
/// work on Sanskrit prosody, the Chandahshastra, some 1800 years
/// before Blaise Pascal. Each row gives the binomial coefficients
/// C(n, k) for n = row number.
///
/// Returns `rows` rows of the triangle (row 0 through row `rows-1`).
///
/// # Errors
///
/// Returns [`SankhyaError::OverflowError`] if any binomial coefficient overflows u64.
pub fn meru_prastara(rows: usize) -> Result<Vec<Vec<u64>>> {
    if rows == 0 {
        return Ok(Vec::new());
    }

    let mut triangle = Vec::with_capacity(rows);
    triangle.push(vec![1u64]);

    for i in 1..rows {
        let prev = &triangle[i - 1];
        let mut row = Vec::with_capacity(i + 1);
        row.push(1);
        for j in 1..i {
            let val = prev[j - 1].checked_add(prev[j]).ok_or_else(|| {
                SankhyaError::OverflowError(format!(
                    "Meru Prastara overflow at row {i}, position {j}"
                ))
            })?;
            row.push(val);
        }
        row.push(1);
        triangle.push(row);
    }

    Ok(triangle)
}

// ---------------------------------------------------------------------------
// Devanagari Katapayadi encoding (requires varna)
// ---------------------------------------------------------------------------

/// Encode a number using Katapayadi with Devanagari script output.
///
/// Like [`katapayadi_encode`], but returns the encoding in Devanagari
/// consonants rather than romanized syllables, using varna's IAST
/// transliteration table for accurate script conversion.
///
/// Each digit maps to a Devanagari consonant from the ka-varga and
/// ca-varga groups: क=1, ख=2, ग=3, घ=4, ङ=5, च=6, छ=7, ज=8, झ=9, ञ=0.
///
/// Requires the `varna` feature.
#[cfg(feature = "varna")]
#[must_use]
pub fn katapayadi_encode_devanagari(n: u64) -> String {
    let table = varna::script::transliteration::devanagari_iast();
    let reverse = table.reverse_map();

    // IAST syllables for each digit (with inherent 'a' vowel, matching
    // varna's Devanagari→IAST table which maps क→"ka", ख→"kha", etc.).
    // We reverse-lookup: "ka" → "क".
    let iast_syllables = [
        "ña", "ka", "kha", "ga", "gha", "ṅa", "ca", "cha", "ja", "jha",
    ];

    if n == 0 {
        return reverse.get("ña").copied().unwrap_or("ञ").to_string();
    }

    let mut result = String::new();
    let mut remaining = n;

    while remaining > 0 {
        let digit = (remaining % 10) as usize;
        remaining /= 10;
        if !result.is_empty() {
            result.push(' ');
        }
        let syllable = iast_syllables[digit];
        result.push_str(reverse.get(syllable).copied().unwrap_or("?"));
    }

    result
}

/// Render a number in Devanagari digits (०-९).
///
/// Uses varna's Devanagari numeral system for digit-by-digit conversion.
///
/// Requires the `varna` feature.
#[cfg(feature = "varna")]
#[must_use]
pub fn to_devanagari_digits(n: u64) -> String {
    let system = varna::script::numerals::devanagari_digits();
    if n == 0 {
        return system.char_for(0).unwrap_or("०").to_string();
    }

    let mut digits = Vec::new();
    let mut remaining = n;
    while remaining > 0 {
        let d = (remaining % 10) as u32;
        digits.push(system.char_for(d).unwrap_or("?").to_string());
        remaining /= 10;
    }
    digits.reverse();
    digits.join("")
}

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

    #[test]
    fn vedic_multiply_basic() {
        assert_eq!(vedic_multiply(97, 96), 9312);
        assert_eq!(vedic_multiply(12, 13), 156);
    }

    #[test]
    fn vedic_nikhilam_near_100() {
        let (base, da, db, cross, product) = vedic_multiply_nikhilam(97, 96).unwrap();
        assert_eq!(base, 100);
        assert_eq!(da, 3);
        assert_eq!(db, 4);
        assert_eq!(cross, 93);
        assert_eq!(product, 9312);
    }

    #[test]
    fn sulba_diagonal_3_4_5() {
        let d = sulba_diagonal(3.0, 4.0);
        assert!((d - 5.0).abs() < 1e-15);
    }

    #[test]
    fn sulba_sqrt2_accuracy() {
        let approx = sulba_sqrt2();
        // Expected: 577/408 = 1.41421568627...
        assert!((approx - 577.0 / 408.0).abs() < 1e-15);
        // Accurate to 5 decimal places vs true value
        assert!((approx - std::f64::consts::SQRT_2).abs() < 1e-5);
    }

    #[test]
    fn katapayadi_roundtrip() {
        for n in [0, 1, 42, 123, 9876] {
            let encoded = katapayadi_encode(n);
            let decoded = katapayadi_decode(&encoded).unwrap();
            assert_eq!(decoded, n, "roundtrip failed for {n}");
        }
    }

    #[test]
    fn meru_prastara_5_rows() {
        let triangle = meru_prastara(5).unwrap();
        assert_eq!(triangle.len(), 5);
        assert_eq!(triangle[0], vec![1]);
        assert_eq!(triangle[1], vec![1, 1]);
        assert_eq!(triangle[2], vec![1, 2, 1]);
        assert_eq!(triangle[3], vec![1, 3, 3, 1]);
        assert_eq!(triangle[4], vec![1, 4, 6, 4, 1]);
    }

    #[cfg(feature = "varna")]
    mod devanagari_tests {
        use super::*;

        #[test]
        fn devanagari_digits_basic() {
            assert_eq!(to_devanagari_digits(0), "०");
            assert_eq!(to_devanagari_digits(123), "१२३");
            assert_eq!(to_devanagari_digits(9876), "९८७६");
        }

        #[test]
        fn katapayadi_devanagari_zero() {
            let s = katapayadi_encode_devanagari(0);
            assert_eq!(s, "ञ");
        }

        #[test]
        fn katapayadi_devanagari_single() {
            // 1 -> ka -> क
            let s = katapayadi_encode_devanagari(1);
            assert_eq!(s, "क");
        }

        #[test]
        fn katapayadi_devanagari_multi() {
            // 21 -> digit 1 (ka, क) then digit 2 (kha, ख)
            let s = katapayadi_encode_devanagari(21);
            assert_eq!(s, "क ख");
        }
    }
}
