use std::fmt;
use std::str::FromStr;
use crate::dat_key::Kid;
use crate::error::DatError;
use crate::util::now_unix_timestamp;

pub struct Dat<T: Kid> {
    dat: String,
    expire: u64,
    kid: T,
    plain_ptr: usize,
    secure_ptr: usize,
    signature_ptr: usize,
}
impl <T: Kid> Dat<T> {
    pub fn kid(&self) -> &T {
        &self.kid
    }
    pub fn expire(&self) -> u64 {
        self.expire
    }
    pub fn plain_base64(&self) -> &str {
        &self.dat[self.plain_ptr .. self.secure_ptr - 1]
    }
    pub fn secure_base64(&self) -> &str {
        &self.dat[self.secure_ptr .. self.signature_ptr - 1]
    }
    pub fn sign_base64(&self) -> &str {
        &self.dat[self.signature_ptr..]
    }
    pub fn body_str(&self) -> &str {
        &self.dat[0 .. self.signature_ptr - 1]
    }
}

impl <T: Kid + Clone> Clone for Dat<T> {
    fn clone(&self) -> Self {
        Dat {
            dat: self.dat.clone(),
            expire: self.expire,
            kid: self.kid.clone(),
            plain_ptr: self.plain_ptr,
            secure_ptr: self.secure_ptr,
            signature_ptr: self.signature_ptr,
        }
    }
}

impl <T: Kid> fmt::Display for Dat<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(&self.dat)
    }
}

impl <T: Kid> FromStr for Dat<T> {
    type Err = DatError;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        s.to_string().try_into()
    }
}

impl <T: Kid> TryFrom<String> for Dat<T> {
    type Error = DatError;
    fn try_from(dat: String) -> Result<Self, Self::Error> {
        let ptr = dat.as_ptr() as usize;
        let mut parts = dat.split('.');

        let expire = parts.next()
            .and_then(|s| s.parse::<u64>().ok())
            .filter(|e| *e > now_unix_timestamp())
            .ok_or_else(|| DatError::InvalidDat)?;

        let kid = parts.next().ok_or_else(|| DatError::InvalidDat)?;
        let kid = T::from_str(kid).map_err(|_| DatError::InvalidDat)?;

        let plain = parts.next().ok_or_else(|| DatError::InvalidDat)?;
        let plain_ptr = plain.as_ptr() as usize - ptr;

        let secure = parts.next().ok_or_else(|| DatError::InvalidDat)?;
        let secure_ptr = secure.as_ptr() as usize - ptr;

        let signature = parts.next().filter(|e| !e.is_empty()).ok_or_else(|| DatError::InvalidDat)?;
        let signature_ptr = signature.as_ptr() as usize - ptr;

        if parts.next().is_some() {
            return Err(DatError::InvalidDat);
        }

        Ok(Dat {
            dat,
            expire,
            kid,
            plain_ptr,
            secure_ptr,
            signature_ptr,
        })
    }
}
