use crate::libs::utils::bytes_xor;

use super::Operation;


/// 分组工作模式（Operation Mode）
pub enum OperationMode {
    /// 电码本（Electronic Codebook）
    ///
    /// 明文分组直接作为加密算法的输入，对应的输出作为密文分组。\
    /// The message is divided into blocks, and each block is encrypted separately.
    ECB,
    /// 密文分组链接（Cipher Block Chaining）
    ///
    /// 当前的明文分组与前一密文分组进行异或运算后再进行加密得到当前的密文分组。\
    /// Each block of plaintext is XORed with the previous ciphertext block before being encrypted.
    ///
    /// # 参数（Arguments）
    ///
    /// * `Box<[u8]>` - 初始向量（Initialization vector）。
    ///
    /// # 示例（Examples）
    ///
    /// ```rust
    /// # use neuedu_cryptos::block_ciphers::OperationMode::CBC;
    /// #
    /// const IV: [u8; 16] = [
    ///     0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF,
    ///     0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
    /// ];
    ///
    /// CBC(IV.into());
    /// ```
    CBC(Box<[u8]>),
    /// 密文反馈（Cipher Feedback）
    ///
    /// 用密文依次更新存储该密码算法启动变量的反馈缓冲器。\
    /// It turning a block cipher into a self-synchronizing stream cipher.
    ///
    /// # 参数（Arguments）
    ///
    /// * `Box<[u8]>` - 初始向量（Initialization vector）。
    ///
    /// # 示例（Examples）
    ///
    /// ```rust
    /// # use neuedu_cryptos::block_ciphers::OperationMode::CFB;
    /// #
    /// const IV: [u8; 16] = [
    ///     0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF,
    ///     0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
    /// ];
    ///
    /// CFB(IV.into());
    /// ```
    CFB(Box<[u8]>),
    /// 输出反馈（Output Feedback）
    ///
    /// 用该算法当前时刻的输出作为下一时刻的输入。\
    /// It generates keystream blocks, which are then XORed with the plaintext blocks to get the ciphertext.
    ///
    /// # 参数（Arguments）
    ///
    /// * `Box<[u8]>` - 初始向量（Initialization vector）。
    ///
    /// # 示例（Examples）
    ///
    /// ```rust
    /// # use neuedu_cryptos::block_ciphers::OperationMode::OFB;
    /// #
    /// const IV: [u8; 16] = [
    ///     0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF,
    ///     0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
    /// ];
    ///
    /// OFB(IV.into());
    /// ```
    OFB(Box<[u8]>),
}


pub(super) trait OperationModeImpl {

    fn decrypt_operation(&self) -> Operation;
    fn pre_calculate(&mut self, message: &[u8], operation: &Operation) -> Box<[u8]>;
    fn post_calculate(&mut self, message: &[u8], operation: &Operation) -> Box<[u8]>;
}


pub(super) struct ECB;

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

impl ECB {
    pub(super) fn new() -> Self {
        Self {}
    }
}

impl OperationModeImpl for ECB {

    fn decrypt_operation(&self) -> Operation {
        Operation::Decrypt
    }

    fn pre_calculate(&mut self, message: &[u8], _operation: &Operation) -> Box<[u8]> {
        message.into()
    }

    fn post_calculate(&mut self, message: &[u8], _operation: &Operation) -> Box<[u8]> {
        message.into()
    }
}


pub(super) struct CBC {
    side_input: Box<[u8]>,
    next_side_input: Box<[u8]>,
}

impl CBC {
    pub(super) fn new(iv: Box<[u8]>) -> Self {
        Self {
            side_input: iv,
            next_side_input: Box::new([]),
        }
    }
}

impl OperationModeImpl for CBC {

    fn decrypt_operation(&self) -> Operation {
        Operation::Decrypt
    }

    fn pre_calculate(&mut self, message: &[u8], operation: &Operation) -> Box<[u8]> {
        if self.side_input.len() != message.len() {
            panic!();
        }

        match operation {
            Operation::Encrypt => {
                if !self.next_side_input.is_empty() {
                    self.side_input = self.next_side_input.clone();
                }

                bytes_xor(&self.side_input, message)
            },
            Operation::Decrypt => {
                if !self.next_side_input.is_empty() {
                    self.side_input = self.next_side_input.clone();
                }
                self.next_side_input = message.into();

                message.into()
            },
        }
    }

    fn post_calculate(&mut self, message: &[u8], operation: &Operation) -> Box<[u8]> {
        if self.side_input.len() != message.len() {
            panic!();
        }

        match operation {
            Operation::Encrypt => {
                self.next_side_input = message.into();

                message.into()
            },
            Operation::Decrypt => {
                bytes_xor(&self.side_input, message)
            },
        }
    }
}


pub(super) struct CFB {
    input: Box<[u8]>,
    next_input: Box<[u8]>,
    message: Box<[u8]>,
}

impl CFB {
    pub(super) fn new(iv: Box<[u8]>) -> Self {
        Self {
            input: iv,
            next_input: Box::new([]),
            message: Box::new([]),
        }
    }
}

impl OperationModeImpl for CFB {

    fn decrypt_operation(&self) -> Operation {
        Operation::Encrypt
    }

    fn pre_calculate(&mut self, message: &[u8], _operation: &Operation) -> Box<[u8]> {
        if self.input.len() != message.len() {
            panic!();
        }

        self.message = message.into();
        if !self.next_input.is_empty() {
            self.input = self.next_input.clone();
        }

        self.input.clone()
    }

    fn post_calculate(&mut self, message: &[u8], operation: &Operation) -> Box<[u8]> {
        if self.input.len() != message.len() {
            panic!();
        }

        match operation {
            Operation::Encrypt => {
                let result = bytes_xor(&self.message, message);
                self.next_input = result.clone();

                result
            },
            Operation::Decrypt => {
                self.next_input = self.message.clone();

                bytes_xor(&self.message, message)
            },
        }
    }
}


pub(super) struct OFB {
    input: Box<[u8]>,
    next_input: Box<[u8]>,
    message: Box<[u8]>,
}

impl OFB {
    pub(super) fn new(iv: Box<[u8]>) -> Self {
        Self {
            input: iv,
            next_input: Box::new([]),
            message: Box::new([]),
        }
    }
}

impl OperationModeImpl for OFB {

    fn decrypt_operation(&self) -> Operation {
        Operation::Encrypt
    }

    fn pre_calculate(&mut self, message: &[u8], _operation: &Operation) -> Box<[u8]> {
        if self.input.len() != message.len() {
            panic!();
        }

        self.message = message.into();
        if !self.next_input.is_empty() {
            self.input = self.next_input.clone();
        }

        self.input.clone()
    }

    fn post_calculate(&mut self, message: &[u8], _operation: &Operation) -> Box<[u8]> {
        if self.input.len() != message.len() {
            panic!();
        }

        self.next_input = message.into();

        bytes_xor(&self.message, message)
    }
}
