//! Gas metering for BigInt operations.
//!
//! This module provides deterministic gas cost calculation functions
//! that depend only on public parameters (limb length, exponent length).

/// Gas type (u64).
pub type Gas = u64;

/// Baseline dispatch cost unit.
pub const G_BASE: Gas = 10;

/// Maximum number of limbs allowed (prevents DoS).
pub const MAX_LIMBS: usize = 512;

/// Calculate gas cost for addition.
///
/// Formula: `G_add(n) = 3n`
pub fn cost_add(n: usize) -> Gas {
    G_BASE + (3 * n as Gas)
}

/// Calculate gas cost for subtraction.
///
/// Formula: `G_sub(n) = 3n`
pub fn cost_sub(n: usize) -> Gas {
    G_BASE + (3 * n as Gas)
}

/// Calculate gas cost for negation.
///
/// Formula: `G_neg(n) = 2n`
pub fn cost_neg(n: usize) -> Gas {
    G_BASE + (2 * n as Gas)
}

/// Calculate gas cost for comparison.
///
/// Formula: `G_cmp(n) = 2n`
pub fn cost_cmp(n: usize) -> Gas {
    G_BASE + (2 * n as Gas)
}

/// Calculate gas cost for bit shift.
///
/// Formula: `G_shift(n) = 2n`
pub fn cost_shift(n: usize) -> Gas {
    G_BASE + (2 * n as Gas)
}

/// Calculate gas cost for multiplication.
///
/// Formula: `G_mul(n) = 2n²`
pub fn cost_mul(n: usize) -> Gas {
    G_BASE + (2 * (n as Gas) * (n as Gas))
}

/// Calculate gas cost for squaring.
///
/// Formula: `G_sqr(n) = 1.6n²`
pub fn cost_sqr(n: usize) -> Gas {
    G_BASE + ((16 * (n as Gas) * (n as Gas)) / 10)
}

/// Calculate gas cost for multiply-accumulate.
///
/// Formula: `G_mac(n) = 2n²`
pub fn cost_mac(n: usize) -> Gas {
    cost_mul(n)
}

/// Calculate gas cost for division.
///
/// Formula: `G_div(n) = 4n²`
pub fn cost_div(n: usize) -> Gas {
    G_BASE + (4 * (n as Gas) * (n as Gas))
}

/// Calculate gas cost for modulo.
///
/// Formula: `G_mod(n) = 4n²`
pub fn cost_mod(n: usize) -> Gas {
    cost_div(n)
}

/// Calculate gas cost for division with remainder.
///
/// Formula: `G_divmod(n) = 4n²`
pub fn cost_divmod(n: usize) -> Gas {
    cost_div(n)
}

/// Calculate gas cost for Montgomery reduction.
///
/// Formula: `G_mred(n) = n²`
pub fn cost_mont_reduce(n: usize) -> Gas {
    G_BASE + ((n as Gas) * (n as Gas))
}

/// Calculate gas cost for Montgomery multiplication.
///
/// Formula: `G_mmul(n) = 2n²`
pub fn cost_mont_mul(n: usize) -> Gas {
    G_BASE + (2 * (n as Gas) * (n as Gas))
}

/// Calculate gas cost for Montgomery addition/subtraction.
///
/// Formula: `G_madd(n) = 3n`
pub fn cost_mont_add(n: usize) -> Gas {
    G_BASE + (3 * n as Gas)
}

/// Calculate gas cost for conversion to Montgomery form.
///
/// Formula: `G_tomont(n) = G_mmul(n)`
pub fn cost_tomont(n: usize) -> Gas {
    cost_mont_mul(n)
}

/// Calculate gas cost for conversion from Montgomery form.
///
/// Formula: `G_frommont(n) = G_mred(n)`
pub fn cost_frommont(n: usize) -> Gas {
    cost_mont_reduce(n)
}

/// Calculate gas cost for modular exponentiation.
///
/// Formula: `G_modexp(n, k) = k × 4n² + 20n²`
/// where `k` is the exponent bit length.
pub fn cost_modexp(n: usize, k: usize) -> Gas {
    let n_gas = n as Gas;
    let k_gas = k as Gas;
    G_BASE + (k_gas * 4 * n_gas * n_gas) + (20 * n_gas * n_gas)
}

/// Calculate gas cost for modular inversion via exponentiation.
///
/// Same as `cost_modexp`.
pub fn cost_inv_modexp(n: usize, k: usize) -> Gas {
    cost_modexp(n, k)
}

/// Calculate gas cost for modular inversion via Binary GCD.
///
/// Formula: `G_inv(n) = 6n²`
pub fn cost_inv_gcd(n: usize) -> Gas {
    G_BASE + (6 * (n as Gas) * (n as Gas))
}

/// Calculate gas cost for canonicalization.
///
/// Formula: `G_canon(n) = n`
pub fn cost_canonicalize(n: usize) -> Gas {
    G_BASE + (n as Gas)
}

/// Calculate gas cost for encoding.
///
/// Formula: `G_enc(n) = n`
pub fn cost_encode(n: usize) -> Gas {
    G_BASE + (n as Gas)
}

/// Calculate gas cost for decoding.
///
/// Formula: `G_dec(n) = n`
pub fn cost_decode(n: usize) -> Gas {
    G_BASE + (n as Gas)
}

/// Validate that the number of limbs does not exceed MAX_LIMBS.
pub fn validate_limb_count(n: usize) -> Result<(), ()> {
    if n > MAX_LIMBS { Err(()) } else { Ok(()) }
}

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

    #[test]
    fn test_cost_add() {
        assert_eq!(cost_add(4), 10 + 12); // G_BASE + 3*4
        assert_eq!(cost_add(32), 10 + 96); // G_BASE + 3*32
    }

    #[test]
    fn test_cost_mul() {
        assert_eq!(cost_mul(4), 10 + 32); // G_BASE + 2*4*4
        assert_eq!(cost_mul(32), 10 + 2048); // G_BASE + 2*32*32
    }

    #[test]
    fn test_cost_modexp() {
        // 256-bit exponent with 256-bit modulus (n=4, k=256)
        let cost = cost_modexp(4, 256);
        // k × 4n² + 20n² = 256 × 4×16 + 20×16 = 256×64 + 320 = 16384 + 320 = 16704
        assert_eq!(cost, 10 + 16704);
    }

    #[test]
    fn test_max_limbs() {
        assert!(validate_limb_count(512).is_ok());
        assert!(validate_limb_count(513).is_err());
    }

    #[test]
    fn test_cost_sub() {
        assert_eq!(cost_sub(4), 10 + 12); // G_BASE + 3*4
        assert_eq!(cost_sub(32), 10 + 96); // G_BASE + 3*32
    }

    #[test]
    fn test_cost_neg() {
        assert_eq!(cost_neg(4), 10 + 8); // G_BASE + 2*4
        assert_eq!(cost_neg(32), 10 + 64); // G_BASE + 2*32
    }

    #[test]
    fn test_cost_cmp() {
        assert_eq!(cost_cmp(4), 10 + 8); // G_BASE + 2*4
        assert_eq!(cost_cmp(32), 10 + 64); // G_BASE + 2*32
    }

    #[test]
    fn test_cost_shift() {
        assert_eq!(cost_shift(4), 10 + 8); // G_BASE + 2*4
        assert_eq!(cost_shift(32), 10 + 64); // G_BASE + 2*32
    }

    #[test]
    fn test_cost_sqr() {
        assert_eq!(cost_sqr(4), 10 + 25); // G_BASE + (16*16)/10 = 10 + 25.6 = 35.6, truncated to 35
        assert_eq!(cost_sqr(32), 10 + 1638); // G_BASE + (16*1024)/10 = 10 + 16384/10 = 10 + 1638.4 = 1648.4, truncated to 1648
    }

    #[test]
    fn test_cost_mac() {
        assert_eq!(cost_mac(4), 10 + 32); // Same as cost_mul = G_BASE + 2*4*4
        assert_eq!(cost_mac(32), 10 + 2048); // Same as cost_mul = G_BASE + 2*32*32
    }

    #[test]
    fn test_cost_div() {
        assert_eq!(cost_div(4), 10 + 64); // G_BASE + 4*4*4
        assert_eq!(cost_div(32), 10 + 4096); // G_BASE + 4*32*32
    }

    #[test]
    fn test_cost_mod() {
        assert_eq!(cost_mod(4), 10 + 64); // Same as cost_div = G_BASE + 4*4*4
        assert_eq!(cost_mod(32), 10 + 4096); // Same as cost_div = G_BASE + 4*32*32
    }

    #[test]
    fn test_cost_divmod() {
        assert_eq!(cost_divmod(4), 10 + 64); // Same as cost_div = G_BASE + 4*4*4
        assert_eq!(cost_divmod(32), 10 + 4096); // Same as cost_div = G_BASE + 4*32*32
    }

    #[test]
    fn test_cost_mont_reduce() {
        assert_eq!(cost_mont_reduce(4), 10 + 16); // G_BASE + 4*4
        assert_eq!(cost_mont_reduce(32), 10 + 1024); // G_BASE + 32*32
    }

    #[test]
    fn test_cost_mont_mul() {
        assert_eq!(cost_mont_mul(4), 10 + 32); // G_BASE + 2*4*4 (same as regular mul)
        assert_eq!(cost_mont_mul(32), 10 + 2048); // G_BASE + 2*32*32 (same as regular mul)
    }

    #[test]
    fn test_cost_mont_add() {
        assert_eq!(cost_mont_add(4), 10 + 12); // G_BASE + 3*4
        assert_eq!(cost_mont_add(32), 10 + 96); // G_BASE + 3*32
    }

    #[test]
    fn test_cost_tomont() {
        assert_eq!(cost_tomont(4), 10 + 32); // Same as cost_mont_mul = G_BASE + 2*4*4
        assert_eq!(cost_tomont(32), 10 + 2048); // Same as cost_mont_mul = G_BASE + 2*32*32
    }

    #[test]
    fn test_cost_frommont() {
        assert_eq!(cost_frommont(4), 10 + 16); // Same as cost_mont_reduce = G_BASE + 4*4
        assert_eq!(cost_frommont(32), 10 + 1024); // Same as cost_mont_reduce = G_BASE + 32*32
    }

    #[test]
    fn test_cost_inv_modexp() {
        // 256-bit modulus with 256-bit exponent (n=4, k=256)
        let cost = cost_inv_modexp(4, 256);
        // Same as modexp cost
        assert_eq!(cost, cost_modexp(4, 256));
    }

    #[test]
    fn test_cost_inv_gcd() {
        assert_eq!(cost_inv_gcd(4), 10 + 96); // G_BASE + 6*4*4
        assert_eq!(cost_inv_gcd(32), 10 + 6144); // G_BASE + 6*32*32
    }

    #[test]
    fn test_cost_canonicalize() {
        assert_eq!(cost_canonicalize(4), 10 + 4); // G_BASE + 1*4
        assert_eq!(cost_canonicalize(32), 10 + 32); // G_BASE + 1*32
    }

    #[test]
    fn test_cost_encode() {
        assert_eq!(cost_encode(4), 10 + 4); // G_BASE + 1*4
        assert_eq!(cost_encode(32), 10 + 32); // G_BASE + 1*32
    }

    #[test]
    fn test_cost_decode() {
        assert_eq!(cost_decode(4), 10 + 4); // G_BASE + 1*4
        assert_eq!(cost_decode(32), 10 + 32); // G_BASE + 1*32
    }

    #[test]
    fn test_gas_costs_are_positive() {
        // All gas costs should be positive
        assert!(cost_add(1) > 0);
        assert!(cost_sub(1) > 0);
        assert!(cost_mul(1) > 0);
        assert!(cost_div(1) > 0);
        assert!(cost_modexp(1, 1) > 0);
    }

    #[test]
    fn test_gas_costs_increase_with_size() {
        // Gas costs should generally increase with operand size
        assert!(cost_add(8) > cost_add(4));
        assert!(cost_mul(8) > cost_mul(4));
        assert!(cost_modexp(8, 64) > cost_modexp(4, 32));
    }

    #[test]
    fn test_gas_constants() {
        assert_eq!(G_BASE, 10);
        assert_eq!(MAX_LIMBS, 512);
    }
}
