
//! # md5 混淆加解密数字
//!
//! md5 混淆加解密数字到字符串，解析字符串到数字，抄袭php的一个加密算法，加密算法名字不明
//! 
/// 
/// 
/// # Examples
///
/// ```
/// let id: i32 = 101;
/// let md5_str = md5mix::encode(id);
/// assert_eq!(id, md5mix::decode(&md5_str));
/// ```
/// 
// --snip--


use crypto::md5::Md5;
use crypto::digest::Digest;


use std::time::SystemTime;

pub const SIGN_STR: &'static str = "a!takA:dlmcldEv,e";

pub fn decode(s: &str) -> i32 {

    let string = s.replace("-", "+").replace("_", "/").replace(".", "=");

    let ckey_length = 4;

    let ( keya, _keyb) = create_key();

    let keyc = &string[..ckey_length];

    let mut hasher = Md5::new();
    
    let keyacstr = format!("{}{}", keya, keyc);
    hasher.input_str(&keyacstr);
    let keyac = hasher.result_str();


    let cryptkey = format!("{}{}", &keya, &keyac);

    let string = base64::decode(&string[ckey_length..]).unwrap();


    let buf = mix(&cryptkey, &string);


    let result = std::str::from_utf8(&buf).unwrap();

    let number = &result[26..];

    let id: i32 = number.parse().unwrap();

    id

}


fn mix(cryptkey: &str, string: &[u8]) -> Vec<u8>{


    let key_length = cryptkey.len();

    // let string = s.as_bytes();
    let string_length = string.len();

    let mut randkey: Vec<i32> = Vec::new();
    for i in 0..256 {
        let index = i % key_length;
        let chars = &cryptkey[index..index+1];

        let ascii = chars.as_bytes();
        let ascii = ascii.to_ascii_lowercase();

        randkey.push(ascii[0] as i32);

    }



    let mut box_vec: Vec<i32> = Vec::new();
    for i in 0..256 {
        box_vec.push(i);
    }

    let mut j: i32 = 0;
    for i in 0..256 {
        j = (j + box_vec[i] + randkey[i]) % 256;
        let tmp = box_vec[i];
        box_vec[i] = box_vec[j as usize];
        box_vec[j as usize] = tmp;
    }



    let mut a = 0;
    let mut j = 0;
    

    let mut buf: Vec<u8> = vec![];

    for i in 0..string_length{
        a = (a + 1) % 256;
        j = (j + box_vec[a]) %256;

        let tmp = box_vec[a];

        box_vec[a] = box_vec[j as usize];
        box_vec[j as usize] = tmp;
       
        let stringord = string[i] as i32;

        let b = (box_vec[a] + box_vec[j as usize]) % 256;

        let chr_num =  stringord ^ box_vec[b as usize];

        buf.push(chr_num as u8);

    }

    buf

}


fn create_key() -> (String, String) {
    let mut hasher = Md5::new();
    hasher.input_str(&SIGN_STR);
    let key = hasher.result_str();



    let mut hasher = Md5::new();
    hasher.input_str(&key[..16]);
    let keya = hasher.result_str();

    let mut hasher = Md5::new();
    hasher.input_str(&key[16..]);
    let keyb = hasher.result_str();

    (keya, keyb)
}

pub fn encode(id: i32) -> String {


    let ckey_length = 4;

 
    let (keya, keyb) = create_key();



    let mut hasher = Md5::new();
    let now = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH){
        Ok(s) => s,
        Err(e) => panic!("SystemTime::now() err {:?}", e),
    };
    hasher.input_str(&now.as_secs().to_string());
    let keyc = hasher.result_str();
    let keyc = &keyc[..];
    let keycstart = keyc.len() - ckey_length;
    let keyc = &keyc[keycstart..];


    let mut hasher = Md5::new();
    let keyacstr = format!("{}{}", keya, keyc);
    hasher.input_str(&keyacstr);
    let keyac = hasher.result_str();

    let cryptkey = format!("{}{}", &keya, &keyac);


    let mut hasher = Md5::new();
    let string = format!("{}{}", id, keyb);
    hasher.input_str(&string);
    let string = hasher.result_str();

    let string: &str = &string[..];
    let string = format!("0000000000{}{}", &string[0..16], id);


    let buf = mix(&cryptkey, &string.as_bytes());

    let bs = &base64::encode(buf);
    let rs = bs.replace("=", "");
    let rs = format!("{}{}", keyc, rs);
    let rs = rs.replace("+", "-").replace("/", "_").replace("=", ".");


    rs
}





#[cfg(test)]
mod tests{



    use super::*;

    #[test]
    fn test_encode(){

        let id = 105;
        let encode_str= encode(id);
        let old_id = decode(&encode_str);
        assert_eq!(id, old_id);
    }

}

