use core_utils::circuit::CircuitPreprocessing;
use std::error::Error;

// Note: better to be too high and then go down, than too low and then go up.
const ARITH_SINGLET_WEIGHT: usize = 100; // 0.1ms per arith single ?
const ARITH_TRIPLE_WEIGHT: usize = 5_000; // 5ms per arith triple
const DA_BIT_WEIGHT: usize = (ARITH_TRIPLE_WEIGHT * 5) / 4; // 25% more cost than the triple
const POW_PAIR_WEIGHT: usize = ARITH_TRIPLE_WEIGHT * 2 * 255; // Maybe correct ???
const BIT_SINGLET_WEIGHT: usize = 10; // 0.01 ms per bit single ?
const BIT_TRIPLE_WEIGHT: usize = 100; // 0.1 ms per bit triple ?

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct PreprocessInfo {
    pub da_bits: usize,
    pub arith_triples: usize,
    pub arith_singlets: usize,
    pub pow_pairs: usize,
    pub bit_triples: usize,
    pub bit_singlets: usize,
}

impl PreprocessInfo {
    pub fn weight(&self) -> usize {
        self.da_bits * DA_BIT_WEIGHT
            + self.arith_triples * ARITH_TRIPLE_WEIGHT
            + self.arith_singlets * ARITH_SINGLET_WEIGHT
            + self.pow_pairs * POW_PAIR_WEIGHT
            + self.bit_triples * BIT_TRIPLE_WEIGHT
            + self.bit_singlets * BIT_SINGLET_WEIGHT
    }
}

impl From<&CircuitPreprocessing> for PreprocessInfo {
    fn from(value: &CircuitPreprocessing) -> Self {
        let da_bits = value.base_field.dabits + value.mersenne107.dabits + value.scalar.dabits;
        let arith_triples =
            value.base_field.triples + value.mersenne107.triples + value.scalar.triples;
        let arith_singlets =
            value.base_field.singlets + value.mersenne107.singlets + value.scalar.singlets;
        let pow_pairs = value.base_field_pow_pairs.values().sum::<usize>();
        let bit_triples = value.bit_triples;
        let bit_singlets = value.bit_singlets;
        Self {
            da_bits,
            arith_triples,
            arith_singlets,
            pow_pairs,
            bit_triples,
            bit_singlets,
        }
    }
}

impl TryFrom<&[usize]> for PreprocessInfo {
    type Error = Box<dyn Error>;

    fn try_from(value: &[usize]) -> Result<Self, Self::Error> {
        let expected = 6;
        if value.len() < expected {
            return Err(format!("Expected {} records, got {}", expected, value.len()).into());
        }
        let res = Self {
            da_bits: value[0],
            arith_triples: value[1],
            arith_singlets: value[2],
            pow_pairs: value[3],
            bit_triples: value[4],
            bit_singlets: value[5],
        };
        Ok(res)
    }
}
