extern crate rand;
extern crate serde;

use rand::prelude::*;

use serde::{Serialize, Deserialize};

use std::{
    collections::HashMap,
    fmt::Debug
};

mod add;
mod keccak;
mod md5;
mod mult;
mod sha1;
mod sha2;
mod xor;

use add::{Add, InvAdd};
use keccak::{Keccak, InvKeccak};
use md5::{Md5, InvMd5};
use mult::{Mult, InvMult};
use sha1::{Sha1, InvSha1};
use sha2::{Sha2, InvSha2};
use xor::{Xor, InvXor, XorWithInv};

/// (De)serializable description of bitransform's preferences.
#[derive(Serialize, Deserialize)]
pub struct BitransformDesc {
    id: String,
    prefs: Vec<usize>
}

/// Provides methods around certain bitstring function to use it for evolution of boolnet population.
pub trait Bitransform: Debug + Sync {
    /// Key to get entry about this Bitransform from BitransformRegister instance
    /// and set Evolver to this Bitransform's instance.
    fn id() -> String where Self: Sized;

    /// Short info about this Bitransform: input, output, preferences.
    fn info() -> String where Self: Sized;

    /// Constructor, provided with bitransform's preferences.
    fn new(prefs: &[usize]) -> Result<Box<dyn Bitransform>, String> where Self: Sized;

    /// Make serializable description of this bitransform's preferences.
    fn desc(&self) -> BitransformDesc;

    /// Boolnet input size in bits.
    fn inp_size(&self) -> usize;

    /// Boolnet output size in bits.
    fn outp_size(&self) -> usize;

    /// Calculate underlying bitstring function for `args` batch.
    fn calc_func(&self, args: &[u128]) -> Vec<u128>;

    /// Make random input batch.
    /// The length of batch elements must be `inp_size()`.
    fn rand_inp(&self, rng: &mut ThreadRng) -> Vec<u128>;

    /// Score output batch, in regard to input batch and underlying bitstring function.
    /// Usually the score is the mean number of different bits in some 2 bitstrings.
    fn score_inp_outp(&self, inp: &[u128], outp: &[u128]) -> f64;

    /// Makes entry about this `Bitransform` to be added to `BitransformRegister` instance.
    fn reg_entry() -> (String, fn() -> String, fn(&[usize]) -> Result<Box<dyn Bitransform>, String>) // see BitransformRegister below
    where Self: Sized {
        (Self::id(), Self::info, Self::new)
    }
}

/// Connects Bitransform id with its info and constructor methods.
pub struct BitransformRegister {
    entries: HashMap<String, (fn() -> String, fn(&[usize]) -> Result<Box<dyn Bitransform>, String>)> // id -> (T::info(), T::new(&prefs))
}

const ERR_BITRAN_ALREADY_REGISTERED: &str = "Bitransform already registered";
const ERR_BITRAN_NOT_REGISTERED: &str = "Bitransform not registered";

impl BitransformDesc {
    pub fn new(id: impl ToString, prefs: &[usize]) -> Self {
        let id = id.to_string();
        let prefs = prefs.clone().to_vec();
        Self{id, prefs}
    }
}

impl BitransformRegister {
    /// Adds entries about Bitransforms.
    /// Such entries are provided by `Bitransform::reg_entry()` method.
    pub fn add(&mut self, entries: Vec<(String, fn() -> String, fn(&[usize]) -> Result<Box<dyn Bitransform>, String>)>) -> Result<(), String> {
        for (id, f_info, f_new) in entries {
            match self.entries.insert(id.clone(), (f_info, f_new)) {
                None => {},
                Some(..) => return Err(format!("{}: {}", ERR_BITRAN_ALREADY_REGISTERED, &id))
            };
        }
        Ok(())
    }

    /// Makes bitransform from its serializable description.
    /// Bitransform must be present in the register.
    pub fn new_from_desc(&self, desc: &BitransformDesc) -> Result<Box<dyn Bitransform>, String> {
        match self.entries.get(& desc.id) {
            Some((_, f_new)) => f_new(& desc.prefs),
            None => Err(format!("{}: {}", ERR_BITRAN_NOT_REGISTERED, & desc.id))
        }
    }

    /// Returns the list that consists of pairs (`id`, `info`), alphabetically sorted by `id`,
    /// for Bitransforms in this register.
    pub fn list(&self) -> Vec<(String, String)> {
        let mut list = Vec::<(String, String)>::with_capacity(self.entries.len());
        for (id, (f_info, _)) in & self.entries {
            list.push((id.clone(), f_info()));
        }
        // Sort alphabetically
        list.sort_by(|a, b| a.cmp(&b));
        list
    }

    /// Creates register with prepackaged Bitransforms.
    pub fn new_default() -> Self {
        let entries = HashMap::<String, (fn() -> String, fn(&[usize]) -> Result<Box<dyn Bitransform>, String>)>::new();
        let mut reg = Self{entries};
        let _ = reg.add(vec![
            Xor::reg_entry(), InvXor::reg_entry(), XorWithInv::reg_entry(),
            Add::reg_entry(), InvAdd::reg_entry(),
            Mult::reg_entry(), InvMult::reg_entry(),
            Md5::reg_entry(), InvMd5::reg_entry(),
            Sha1::reg_entry(), InvSha1::reg_entry(),
            Sha2::reg_entry(), InvSha2::reg_entry(),
            Keccak::reg_entry(), InvKeccak::reg_entry()
        ]);
        reg
    }

}
