use rand;
use base32;
use hmacsha1::hmac_sha1;
use std::mem;
use std::time::{SystemTime, UNIX_EPOCH};
use urlencoding;
#[cfg(any(feature = "with-qrcode"))]
use qrcode::{QrCode, Version, EcLevel};
#[cfg(any(feature = "with-qrcode"))]
use qrcode::render::svg;
const SECRET_MAX_LEN:usize = 128;
const SECRET_MIN_LEN:usize = 16;
pub struct GoogleAuthenticator{
    code_len:usize,
    _base32_alphabet:Vec<char>
}
impl GoogleAuthenticator{
    pub fn new() -> GoogleAuthenticator{
        GoogleAuthenticator{
            code_len: 6,
            _base32_alphabet: vec![
                'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 
                'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 
                'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 
                'Y', 'Z', '2', '3', '4', '5', '6', '7', 
                '=',
            ]
        }
    }
    
    
    
    
    
    
    
    
    
    
    
    
    pub fn create_secret(&self, length:u8) -> String{
        let mut secret = Vec::<char>::new();
        let mut index:usize ;
        for _ in 0 .. length {
            index = (rand::random::<u8>() & 0x1F) as usize;
            secret.push(self._base32_alphabet[ index ]);
        }
        secret.into_iter().collect()
    }
    
    
    
    
    
    
    
    
    
    
    
    
    
    pub fn get_code(&self, secret:&str, times_slice:i64) -> Result<String>{
        if secret.len() < SECRET_MIN_LEN || secret.len() > SECRET_MAX_LEN {
            return Err(GAError::Error("bad secret length. must be less than 128 and more than 16, recommand 32"));
        }
        let mut message: i64 = times_slice;
        if times_slice == 0 {
            message = (SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i64) / 30;
        }
        
        let key  = self._base32_decode(secret)?;
        let msg_bytes = message.to_be_bytes();
        
        
        
        
        let hash = hmac_sha1(&key, &msg_bytes);
        let offset = hash[hash.len() - 1] & 0x0F;
        let mut truncated_hash:[u8;4] = Default::default();
        truncated_hash.copy_from_slice(&hash[offset  as usize .. (offset + 4)  as usize]);
        let mut code:i32 = unsafe { mem::transmute::<[u8;4], i32>(truncated_hash) };
        if cfg!(target_endian = "big") {
        } else {
            code = i32::from_be(code);
        }
        code = code & 0x7FFFFFFF;
        code =  code % 1_000_000i32;
        let mut code_str = code.to_string();        
        for i in 0 .. (self.code_len - code_str.len()) {
            code_str.insert(i,'0');
        }
        Ok(code_str)
    }
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    pub fn verify_code(&self, secret:&str, code: &str, discrepancy:i64, time_slice:i64) -> Result<bool>{
        let mut curr_time_slice: i64 = time_slice;
        if time_slice == 0 {
            curr_time_slice = (SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i64) / 30 ;
        }
        if code.len() != self.code_len {
            return Ok(false);
        }
        for _time_slice in curr_time_slice.wrapping_sub(discrepancy) .. curr_time_slice.wrapping_add(discrepancy + 1)  {
            println!("{:?}",_time_slice);
            println!("{:?}",_time_slice);
            if code.eq(self.get_code(secret, _time_slice)?.as_str()) {
                return Ok(true);
            }
        }
        Ok(false)
    }
    
    
    
    
    
    pub fn qr_code_url(&self, secret:&str, name:&str, title:&str, width:u16, height:u16, level:char) -> String {
        let _width = if width == 0 {200} else {width};
        let _height = if  height == 0 {200} else {height};
        let levels = vec!['L', 'M', 'Q', 'H'];
        let _level = if levels.contains(&level) {level} else {'M'};
        let scheme =  urlencoding::encode(
            format!("otpauth://totp/{}?secret={}&issuer={}"
                    ,name
                    ,secret
                    ,urlencoding::encode(title)).as_str());
        format!("https://chart.googleapis.com/chart?chs={}x{}&chld={}|0&cht=qr&chl={}", _width, _height, level, scheme)
    }
    
    
    
    
    
    #[cfg(any(feature = "with-qrcode"))]
    pub fn qr_code(&self, secret:&str, name:&str, title:&str, width:u16, height:u16, level:char) -> Result<String>{
        let _width = if width == 0 {200} else {width};
        let _height = if  height == 0 {200} else {height};
        let levels = vec!['L', 'M', 'Q', 'H'];
        let _level = match level {
            'L' => EcLevel::L,
            'H' => EcLevel::H,
            _ => EcLevel::M
        };
        let scheme = format!("otpauth://totp/{}?secret={}&issuer={}", name, secret, title);
        let code = QrCode::with_error_correction_level(scheme.as_bytes(), _level)?;
        Ok(code.render()
            .min_dimensions(_width as u32, _height  as u32)
            .dark_color(svg::Color("#000000"))
            .light_color(svg::Color("#ffffff"))
            .build())
    }
    fn _base32_decode(&self, secret:&str) -> Result<Vec<u8>>{
        
        match  base32::decode(base32::Alphabet::RFC4648 { padding: true }, secret) {
            Some(_decode_str) => Ok(_decode_str),
            _                 => Err(GAError::Error("secret must can decode by base32."))
        }
    }
}
use std::error;
use std::result;
use std::fmt;
#[cfg(any(feature = "with-qrcode"))]
use qrcode::types::QrError;
#[derive(Debug)]
pub enum GAError {
    Error(&'static str),
    #[cfg(any(feature = "with-qrcode"))]
    QrError(QrError)
}
impl error::Error for GAError{
    fn description(&self) -> &str {
        match *self {
            GAError::Error(description) =>description,
            #[cfg(any(feature = "with-qrcode"))]
            GAError::QrError(ref err) => ""
        }
    }
    fn cause(&self) -> Option<&error::Error> {
        match *self {
            #[cfg(any(feature = "with-qrcode"))]
            GAError::QrError(ref err) =>  None,
            GAError::Error(_) => None,
        }
    }
}
#[cfg(any(feature = "with-qrcode"))]
impl From<QrError> for GAError{
    fn from(err:QrError) -> GAError {
        GAError::QrError(err)
    }
}
impl fmt::Display for GAError{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            GAError::Error(desc) => f.write_str(desc),
            #[cfg(any(feature = "with-qrcode"))]
            GAError::QrError(ref err) =>  fmt::Display::fmt(err, f),
        }
    }
}
type Result<T> = result::Result<T,GAError>;