use crate::libs::utils::u8arr_xor;

use super::Operation;


pub(super) trait OperationModeImpl {

    fn decrypt_operation(&self) -> Operation;
    fn pre_calculate(&mut self, message: &[u8], operation: &Operation) -> Vec<u8>;
    fn post_calculate(&mut self, message: &[u8], operation: &Operation) -> Vec<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) -> Vec<u8> {
        message.to_vec()
    }

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


pub(super) struct CBC {
    side_input: Vec<u8>,
    next_side_input: Vec<u8>,
}

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

impl OperationModeImpl for CBC {

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

    fn pre_calculate(&mut self, message: &[u8], operation: &Operation) -> Vec<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();
                }

                u8arr_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.to_vec();

                message.to_vec()
            },
        }
    }

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

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

                message.to_vec()
            },
            Operation::Decrypt => {
                u8arr_xor(&self.side_input, message)
            },
        }
    }
}


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

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

impl OperationModeImpl for CFB {

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

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

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

        self.input.clone()
    }

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

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

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

                u8arr_xor(&self.message, message).to_vec()
            },
        }
    }
}


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

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

impl OperationModeImpl for OFB {

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

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

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

        self.input.clone()
    }

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

        self.next_input = message.to_vec();

        u8arr_xor(&self.message, message)
    }
}
