//! Types used to represent constant arrays with varying lengths that allow easy access to the head and tail.

use crate::encryption::Plaintext;

#[derive(Debug, PartialEq, Clone)]
pub struct ConstArg2(pub Plaintext, pub Plaintext);

#[derive(Debug, PartialEq, Clone)]
pub struct ConstArg3(pub Plaintext, pub ConstArg2);

#[derive(Debug, PartialEq, Clone)]
pub struct ConstArg4(pub Plaintext, pub ConstArg3);

impl ConstArg2 {
    pub fn new(a: impl Into<Plaintext>, b: impl Into<Plaintext>) -> Self {
        Self(b.into(), a.into())
    }

    pub fn head(&self) -> &Plaintext {
        &self.0
    }

    pub fn tail(&self) -> &Plaintext {
        &self.1
    }
}

impl ConstArg3 {
    pub fn new(a: impl Into<Plaintext>, b: impl Into<Plaintext>, c: impl Into<Plaintext>) -> Self {
        Self(c.into(), ConstArg2::new(a, b))
    }

    pub fn head(&self) -> &Plaintext {
        &self.0
    }

    pub fn tail(&self) -> &ConstArg2 {
        &self.1
    }
}

impl ConstArg4 {
    pub fn new(
        a: impl Into<Plaintext>,
        b: impl Into<Plaintext>,
        c: impl Into<Plaintext>,
        d: impl Into<Plaintext>,
    ) -> Self {
        Self(d.into(), ConstArg3::new(a, b, c))
    }

    pub fn head(&self) -> &Plaintext {
        &self.0
    }

    pub fn tail(&self) -> &ConstArg3 {
        &self.1
    }
}

impl<A, B> From<(A, B)> for ConstArg2
where
    Plaintext: From<A>,
    Plaintext: From<B>,
{
    fn from((a, b): (A, B)) -> Self {
        Self::new(a, b)
    }
}

impl<A, B, C> From<(A, B, C)> for ConstArg3
where
    Plaintext: From<A>,
    Plaintext: From<B>,
    Plaintext: From<C>,
{
    fn from((a, b, c): (A, B, C)) -> Self {
        Self::new(a, b, c)
    }
}

impl<A, B, C, D> From<(A, B, C, D)> for ConstArg4
where
    Plaintext: From<A>,
    Plaintext: From<B>,
    Plaintext: From<C>,
    Plaintext: From<D>,
{
    fn from((a, b, c, d): (A, B, C, D)) -> Self {
        Self::new(a, b, c, d)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_into_cons2() {
        let x: (i32, &str) = (1, "hey");
        let y: ConstArg2 = x.into();

        assert_eq!(y, ConstArg2::new(1, "hey".to_string()));
    }

    #[test]
    fn test_into_cons3() {
        let x: (i32, &str, i16) = (1, "hey", 12);
        let y: ConstArg3 = x.into();

        assert_eq!(y, ConstArg3::new(1_i32, "hey".to_string(), 12_i16));
    }

    #[test]
    fn test_into_cons4() {
        let x: (i32, &str, i16, &str) = (1, "hey", 12, "wow");
        let y: ConstArg4 = x.into();

        assert_eq!(
            y,
            ConstArg4::new(1_i32, "hey".to_string(), 12_i16, "wow".to_string())
        );
    }
}
