//! Implements Bitransform for product of 2 16-bit bitstrings and its inverse

extern crate rand;

use rand::prelude::*;

use super::{
    Bitransform,
    BitransformDesc
};

#[derive(Debug)]
pub struct Mult {}

impl Mult {
    fn func(x: u16, y: u16) -> u16 {
        x * y
    }
}

impl Bitransform for Mult {
    fn id() -> String {
        String::from("Mult")
    }

    fn info() -> String {
        format!("IN: 32b as 2 16b str-s, OUT: 16b product of IN str-s, PREFS: -")
    }

    fn new(prefs: &[usize]) -> Result<Box<dyn Bitransform>, String> {
        match prefs.len() {
            0 => Ok(Box::new(Self{})),
            _ => Err(format!("Too many prefs (must be 0)"))
        }
    }

    fn desc(&self) -> BitransformDesc {
        BitransformDesc::new(Self::id(), &[])
    }

    fn inp_size(&self) -> usize {
        32
    }

    fn outp_size(&self) -> usize {
        16
    }

    fn calc_func(&self, args: &[u128]) -> Vec<u128> {
        let mut yb = vec![0u128; 0x10];
        for i in 0..0x80 {
            let x1: u16 = (0..0x10).fold(0, |x, j| x | ((((args[j] >> i) & 1) as u16) << j) );
            let x2: u16 = (0..0x10).fold(0, |x, j| x | ((((args[0x10 + j] >> i) & 1) as u16) << j) );
            let y = Self::func(x1, x2);
            (0..0x10).for_each(|j| yb[j] |= (((y >> j) & 1) as u128) << i);
        }
        yb
    }

    fn rand_inp(&self, rng: &mut ThreadRng) -> Vec<u128> {
        let mut xb = Vec::<u128>::with_capacity(0x20);
        for _ in 0..0x20 {
            xb.push(rng.gen::<u128>())
        }
        xb
    }

    fn score_inp_outp(&self, inp: &[u128], outp: &[u128]) -> f64 {
        let func = self.calc_func(inp);
        let mut score: i32 = 0;

        for i in 0..0x10 {
            score -= (func[i] ^ outp[i]).count_ones() as i32; // number of different bits
        }

        (score as f64) / (0x80 as f64)
    }

}

// ----------------------------------------------------------------

#[derive(Debug)]
pub struct InvMult {
    direct: Mult
}

impl Bitransform for InvMult {
    fn id() -> String {
        String::from("InvMult")
    }

    fn info() -> String {
        format!("IN: 16b str, OUT: 32b as 2 16b str-s whose product is IN, PREFS: -")
    }

    fn new(prefs: &[usize]) -> Result<Box<dyn Bitransform>, String> {
        match prefs.len() {
            0 => {
                let direct = Mult{};
                Ok(Box::new(Self{direct}))
            },
            _ => Err(format!("Too many prefs (must be 0)"))
        }
    }

    fn desc(&self) -> BitransformDesc {
        BitransformDesc::new(Self::id(), &[])
    }

    fn inp_size(&self) -> usize {
        16
    }

    fn outp_size(&self) -> usize {
        32
    }

    fn calc_func(&self, args: &[u128]) -> Vec<u128> {
        self.direct.calc_func(args)
    }

    fn rand_inp(&self, rng: &mut ThreadRng) -> Vec<u128> {
        self.direct.calc_func(& self.direct.rand_inp(rng))
    }

    fn score_inp_outp(&self, inp: &[u128], outp: &[u128]) -> f64 {
        let func = self.direct.calc_func(outp);
        let mut score: i32 = 0;

        for i in 0..0x10 {
            score -= (func[i] ^ inp[i]).count_ones() as i32; // number of different bits
        }

        (score as f64) / (0x80 as f64)
    }

}
