/// 填充方法（Padding Method）
pub enum PaddingMethod {
    /// 填充方法1（PKCS#7）
    ///
    /// 以字节为基本单位，在明文字节串P的右侧填充a个字节“a”，其中“a”表示P的最后一个分组达到分组长度所需要的字节数。
    /// 如果P为空串或其最后一个分组正好为分组长度，那么填充方法1规定在P右侧填充一个由b个字节“b”构成的分组，其中“b”表示一个明文分组所需要的字节数目。
    ///
    /// Padding is in whole bytes.
    /// The value of each added byte is the number of bytes that are added, i.e. N bytes, each of value N are added.
    /// The number of bytes added will depend on the block boundary to which the message needs to be extended.
    Method1,
    /// 填充方法2（Padding method 2 of ISO/IEC 9797-1）
    ///
    /// 在明文比特串P的右侧填充一个比特“1”，然后在所得到的比特串右侧填充“0”，尽可能少填充（甚至不填充），使填充后的比特串的长度是P的正整数倍。
    /// 如果P是空串，那么填充方法2规定对其填充一个“1”，然后在其右侧填充 个“0”。
    ///
    /// Add a single bit with value 1 to the end of the data.
    /// Then if necessary add bits with value 0 to the end of the data until the padded data is a multiple of n.
    Method2,
    /// 填充方法3（Padding method 3 of ISO/IEC 9797-1）
    ///
    /// 在明文比特串P的右侧填充“0”，尽可能少填充（甚至不填充），使填充后比特串的长度是j的正整数倍。
    /// 然后在所得到的明文比特串左侧填充一个分组L。
    /// 分组L由尽可能少的“0”和明文比特串P的长度L<sub>P</sub>的二进制表示组成，其中位于L<sub>P</sub>二进制表示的左侧的“0”尽可能少，且使L的长度为j比特。
    /// L最右端的比特和L<sub>P</sub>的二进制表示中的最低位相对应。
    /// 如果是空串，那么填充方法3规定对其填充j个“0”，然后在其左侧填充一个由j个“0”组成的分组L。
    ///
    /// The padded data comprises (in this order):
    ///
    /// * The length of the unpadded data (in bits) expressed in big-endian binary in n bits (i.e. one cipher block)
    /// * The unpadded data
    /// * As many (possibly none) bits with value 0 as are required to bring the total length to a multiple of n bits
    ///
    /// # 参数（Arguments）
    ///
    /// * `Option<usize>` - 要加密的明文消息的长度（以字节为单位）。
    ///                     The length (in bytes) of the plaintext message to be encrypted.
    ///
    /// # 示例（Examples）
    ///
    /// * 加密长度为31字节的消息。
    ///   Encrypt a message with a length of 31 bytes.
    /// ```rust
    /// # use neuedu_cryptos::common::PaddingMethod::Method3;
    /// #
    /// Method3(Some(31));
    /// ```
    ///
    /// * 解密消息。
    ///   Decrypt a message.
    /// ```rust
    /// # use neuedu_cryptos::common::PaddingMethod::Method3;
    /// #
    /// Method3(None);
    /// ```
    Method3(Option<usize>),
}


pub(crate) trait PaddingModeImpl {

    fn padding_mode(&self) -> PaddingMethod;
    fn pad(&self, message: &[u8]) -> Box<[u8]>;
    fn unpad(&self, message: &[u8]) -> Box<[u8]>;

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


pub(crate) struct Method1 {
    block_size: usize,
}

impl Method1 {
    pub(crate) fn new(block_size: usize) -> Self {
        Self {
            block_size,
        }
    }
}

impl PaddingModeImpl for Method1 {

    fn padding_mode(&self) -> PaddingMethod {
        PaddingMethod::Method1
    }

    fn pad(&self, message: &[u8]) -> Box<[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.into()
    }

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

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


pub(crate) struct Method2 {
    block_size: usize,
}

impl Method2 {
    pub(crate) fn new(block_size: usize) -> Self {
        Self {
            block_size,
        }
    }
}

impl PaddingModeImpl for Method2 {

    fn padding_mode(&self) -> PaddingMethod {
        PaddingMethod::Method2
    }

    fn pad(&self, message: &[u8]) -> Box<[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.into()
    }

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

        unreachable!()
    }
}


pub(crate) struct Method3 {
    block_size: usize,
    expect_size: Option<usize>,
}

impl Method3 {
    pub(crate) fn new(block_size: usize, expect_size: Option<usize>) -> Self {
        Self {
            block_size,
            expect_size,
        }
    }
}

impl PaddingModeImpl for Method3 {

    fn padding_mode(&self) -> PaddingMethod {
        PaddingMethod::Method3(self.expect_size)
    }

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

        vec![0u8; to_pad].into()
    }

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

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