use super::Error;
use super::FileHeader;
use super::ReaderAction;
use crate::crypt_file_data;
use crate::crypt_name_bytes;
use crate::crypt_u32;
use crate::DEFAULT_KEY;
use crate::HEADER_LEN;
use crate::MAGIC;
use crate::MAGIC_LEN;
use crate::MAX_FILE_NAME_LEN;
use crate::U32_LEN;
use crate::VERSION;
const DEFAULT_BUFFER_CAPACITY: usize = 10 * 1024;
#[derive(Debug)]
pub struct Reader {
    buffer: oval::Buffer,
    state: State,
    need_seek: bool,
    position: u64,
    next_file_position: u64,
    pub(crate) key: u32,
}
impl Reader {
    pub fn new() -> Self {
        Self {
            buffer: oval::Buffer::with_capacity(DEFAULT_BUFFER_CAPACITY),
            state: State::Header,
            need_seek: false,
            position: 0,
            next_file_position: 0,
            key: DEFAULT_KEY,
        }
    }
    pub fn space(&mut self) -> &mut [u8] {
        self.buffer.space()
    }
    pub fn fill(&mut self, num: usize) {
        self.buffer.fill(num);
    }
    pub fn available_data(&self) -> usize {
        self.buffer.available_data()
    }
    pub fn finish_seek(&mut self) {
        assert!(
            self.position != self.next_file_position,
            "a seek was not requested"
        );
        self.position = self.next_file_position;
        self.buffer.reset();
    }
    pub fn step_read_header(&mut self) -> Result<ReaderAction<()>, Error> {
        match self.state {
            State::Header => {}
            State::FileHeader | State::FileData { .. } => {
                return Ok(ReaderAction::Done(()));
            }
        }
        let data = self.buffer.data();
        let data_len = data.len();
        if data_len < HEADER_LEN {
            return Ok(ReaderAction::Read(HEADER_LEN - data_len));
        }
        let magic = *data.first_chunk::<MAGIC_LEN>().unwrap();
        if magic != MAGIC {
            return Err(Error::InvalidMagic { magic });
        }
        let version = data[MAGIC_LEN];
        if version != VERSION {
            return Err(Error::InvalidVersion { version });
        }
        let header_len_u64 = u64::try_from(HEADER_LEN).unwrap();
        self.buffer.consume(HEADER_LEN);
        self.position = header_len_u64;
        self.next_file_position = header_len_u64;
        self.state = State::FileHeader;
        Ok(ReaderAction::Done(()))
    }
    pub fn step_read_file_header(&mut self) -> Result<ReaderAction<FileHeader>, Error> {
        loop {
            match self.state {
                State::Header => {
                    let action = self.step_read_header()?;
                    if !action.is_done() {
                        return Ok(action.map_done(|_| unreachable!()));
                    }
                }
                State::FileHeader => break,
                State::FileData { .. } => {
                    if self.position != self.next_file_position {
                        return Ok(ReaderAction::Seek(self.next_file_position));
                    }
                    self.state = State::FileHeader;
                }
            }
        }
        let mut key = self.key;
        let data = self.buffer.data();
        let data_len = data.len();
        if data_len < U32_LEN {
            return Ok(ReaderAction::Read(U32_LEN - data_len));
        }
        let file_name_len = {
            let bytes = data[..U32_LEN].try_into().unwrap();
            let n = u32::from_le_bytes(bytes);
            let n = crypt_u32(&mut key, n);
            if n > MAX_FILE_NAME_LEN {
                return Err(Error::FileNameTooLongU32 { len: n });
            }
            usize::try_from(n).unwrap()
        };
        let file_header_size = (U32_LEN * 2) + file_name_len;
        if data_len < file_header_size {
            return Ok(ReaderAction::Read(file_header_size - data_len));
        }
        let file_name = {
            let mut bytes = data[U32_LEN..U32_LEN + file_name_len].to_vec();
            crypt_name_bytes(&mut key, &mut bytes);
            String::from_utf8(bytes).map_err(|error| Error::InvalidFileName { error })?
        };
        let file_data_len = {
            let index = U32_LEN + file_name_len;
            let range = index..index + U32_LEN;
            let bytes = data[range].try_into().unwrap();
            let n = u32::from_le_bytes(bytes);
            crypt_u32(&mut key, n)
        };
        let file_header_size_u64 = u64::try_from(file_header_size).unwrap();
        self.buffer.consume(file_header_size);
        self.position += file_header_size_u64;
        self.next_file_position += file_header_size_u64 + u64::from(file_data_len);
        self.key = key;
        self.need_seek = true;
        self.state = State::FileData {
            key: self.key,
            counter: 0,
            remaining: file_data_len,
        };
        Ok(ReaderAction::Done(FileHeader {
            name: file_name,
            size: file_data_len,
        }))
    }
    pub fn step_read_file_data(
        &mut self,
        output_buffer: &mut [u8],
    ) -> Result<ReaderAction<usize>, Error> {
        let (key, counter, remaining) = loop {
            match &mut self.state {
                State::Header => {
                    let action = self.step_read_header()?;
                    if !action.is_done() {
                        return Ok(action.map_done(|_| unreachable!()));
                    }
                }
                State::FileHeader => return Ok(ReaderAction::Done(0)),
                State::FileData {
                    key,
                    counter,
                    remaining,
                } => break (key, counter, remaining),
            }
        };
        if *remaining == 0 {
            return Ok(ReaderAction::Done(0));
        }
        let data = self.buffer.data();
        if data.is_empty() {
            return Ok(ReaderAction::Read(self.buffer.available_space()));
        }
        let remaining_usize =
            usize::try_from(*remaining).expect("remaining bytes cannot fit in a `usize`");
        let len = std::cmp::min(data.len(), output_buffer.len());
        let len = std::cmp::min(len, remaining_usize);
        let len_u32 = u32::try_from(len).expect("len cannot fit in a `u32`");
        let output_buffer = &mut output_buffer[..len];
        output_buffer.copy_from_slice(&data[..len]);
        crypt_file_data(key, counter, output_buffer);
        *remaining -= len_u32;
        self.buffer.consume(len);
        self.position += u64::from(len_u32);
        Ok(ReaderAction::Done(len))
    }
}
impl Default for Reader {
    fn default() -> Self {
        Self::new()
    }
}
#[derive(Debug, Copy, Clone)]
enum State {
    Header,
    FileHeader,
    FileData {
        key: u32,
        counter: u8,
        remaining: u32,
    },
}