use super::PaddingMode;


pub(super) trait PaddingModeImpl {

    fn padding_mode(&self) -> PaddingMode;
    fn set_block_size(&mut self, block_size: usize);
    fn pad(&self, message: &[u8]) -> Vec<u8>;
    fn unpad(&self, message: &[u8]) -> Vec<u8>;

    fn expect_size(&self) -> Option<usize> {
        None
    }
}


pub(super) struct Mode1 {
    block_size: usize,
}

impl Default for Mode1 {
    fn default() -> Self {
        Self::new()
    }
}

impl Mode1 {
    pub(super) fn new() -> Self {
        Self {
            block_size: 0,
        }
    }
}

impl PaddingModeImpl for Mode1 {

    fn padding_mode(&self) -> PaddingMode {
        PaddingMode::Mode1
    }

    fn set_block_size(&mut self, block_size: usize) {
        self.block_size = block_size
    }

    fn pad(&self, message: &[u8]) -> Vec<u8> {
        let to_pad = self.block_size - message.len();

        let mut result = Vec::<u8>::with_capacity(to_pad);
        result.resize(to_pad, to_pad as u8);

        result
    }

    fn unpad(&self, message: &[u8]) -> Vec<u8> {
        let to_unpad = message[self.block_size - 1];

        message[..self.block_size - to_unpad as usize].to_vec()
    }
}


pub(super) struct Mode2 {
    block_size: usize,
}

impl Default for Mode2 {
    fn default() -> Self {
        Self::new()
    }
}

impl Mode2 {
    pub(super) fn new() -> Self {
        Self {
            block_size: 0,
        }
    }
}

impl PaddingModeImpl for Mode2 {

    fn padding_mode(&self) -> PaddingMode {
        PaddingMode::Mode2
    }

    fn set_block_size(&mut self, block_size: usize) {
        self.block_size = block_size
    }

    fn pad(&self, message: &[u8]) -> Vec<u8> {
        let to_pad = (self.block_size - message.len() + self.block_size - 1) % self.block_size;

        let mut result = Vec::<u8>::with_capacity(to_pad + 1);
        result.push(0x80);
        result.resize(to_pad + 1, 0);

        result
    }

    fn unpad(&self, message: &[u8]) -> Vec<u8> {
        for i in 0..message.len() {
            if message[self.block_size - i - 1] == 0x80 {
                return message[..self.block_size - i - 1].to_vec();
            }
        }

        unreachable!()
    }
}


pub(super) struct Mode3 {
    block_size: usize,
    expect_size: Option<usize>,
}

impl Default for Mode3 {
    fn default() -> Self {
        Self::new(None)
    }
}

impl Mode3 {
    pub(super) fn new(expect_size: Option<usize>) -> Self {
        Self {
            block_size: 0,
            expect_size,
        }
    }
}

impl PaddingModeImpl for Mode3 {

    fn padding_mode(&self) -> PaddingMode {
        PaddingMode::Mode3(self.expect_size)
    }

    fn set_block_size(&mut self, block_size: usize) {
        self.block_size = block_size
    }

    fn pad(&self, message: &[u8]) -> Vec<u8> {
        let to_pad = (self.block_size - message.len()) % self.block_size;

        vec![0u8; to_pad]
    }

    fn unpad(&self, message: &[u8]) -> Vec<u8> {
        message.to_vec()
    }

    fn expect_size(&self) -> Option<usize> {
        self.expect_size
    }
}
