Bindings to the seekable variant of the ZSTD compression format
use crate::{
    Error, MAGIC_SKIPPABLE_START, SEEKABLE_MAGIC_NUMBER, SEEK_TABLE_FOOTER_SIZE,
    SKIPPABLE_HEADER_SIZE,
};
// use libc::*;

const SEEKABLE_MAX_FRAMES: usize = 0x8000000;

#[derive(Clone)]
struct FrameLogEntry {
    c_size: u32,
    d_size: u32,
    checksum: u32,
}

/// The type of compressors.
pub struct FrameLog {
    entries: Vec<FrameLogEntry>,
    pub checksum_flag: u32,
    seek_table_pos: u32,
    seek_table_index: u32,
}

unsafe impl Send for FrameLog {}

impl FrameLog {
    #[cfg(feature = "threadpool")]
    pub fn new() -> Self {
        FrameLog {
            entries: Vec::new(),
            checksum_flag: 1,
            seek_table_pos: 0,
            seek_table_index: 0,
        }
    }

    pub fn with_capacity(x: usize) -> Self {
        FrameLog {
            entries: Vec::with_capacity(x),
            checksum_flag: 1,
            seek_table_pos: 0,
            seek_table_index: 0,
        }
    }

    pub fn log_frame(&mut self, c_size: u32, d_size: u32, checksum: u32) -> Result<(), Error> {
        let size = self.entries.len();
        if size == SEEKABLE_MAX_FRAMES {
            return Err(Error::FIndexTooLarge(size, SEEKABLE_MAX_FRAMES as usize));
        }

        self.entries.push(FrameLogEntry {
            c_size,
            d_size,
            checksum,
        });

        Ok(())
    }

    #[inline(always)]
    pub fn seek_table_size(&self) -> usize {
        let size_p_frame = 8 + if self.checksum_flag != 0 { 4 } else { 0 };
        SKIPPABLE_HEADER_SIZE + size_p_frame * self.entries.len() + SEEK_TABLE_FOOTER_SIZE
    }

    pub fn stwrite32(
        &mut self,
        output: &mut [u8],
        value: u32,
        offset: u32,
        pos: &mut usize,
    ) -> usize {
        if self.seek_table_pos < offset + 4 {
            let len_write = std::cmp::min(
                output.len() - *pos,
                (offset + 4 - self.seek_table_pos) as usize,
            );

            let bytes = value.to_le_bytes();
            output[*pos..*pos + len_write].copy_from_slice(&bytes[..len_write]);

            *pos += len_write;
            self.seek_table_pos += len_write as u32;

            return if len_write < 4 {
                self.seek_table_size() - self.seek_table_pos as usize
            } else {
                0
            };
        }
        0
    }

    pub fn write_seek_table(&mut self, output: &mut [u8], pos: &mut usize) -> Result<usize, Error> {
        let size_p_frame = 8 + if self.checksum_flag != 0 { 4 } else { 0 };
        let table_len = self.seek_table_size();

        let ret = self.stwrite32(output, MAGIC_SKIPPABLE_START | 0xE, 0, pos);
        if ret != 0 {
            return Ok(ret);
        };

        // assert!(table_len <= usize::MAX);

        let ret = self.stwrite32(output, (table_len - SKIPPABLE_HEADER_SIZE) as u32, 4, pos);
        if ret != 0 {
            return Ok(ret);
        };

        let mut i = self.seek_table_index as usize;

        while i < self.entries.len() {
            let start = (SKIPPABLE_HEADER_SIZE + size_p_frame * i) as u32;
            // assert!(start + 8 <= usize::MAX);

            let ret = self.stwrite32(output, self.entries[i].c_size, start, pos);
            if ret != 0 {
                return Ok(ret);
            };

            let ret = self.stwrite32(output, self.entries[i].d_size, start + 4, pos);
            if ret != 0 {
                return Ok(ret);
            };

            if self.checksum_flag != 0 {
                let ret = self.stwrite32(output, self.entries[i].checksum, start + 8, pos);
                if ret != 0 {
                    return Ok(ret);
                };
            }

            i += 1;
        }

        self.seek_table_index = i as u32;
        // assert!(table_len <= usize::MAX);

        let ret = self.stwrite32(
            output,
            self.entries.len() as u32,
            (table_len - SEEK_TABLE_FOOTER_SIZE) as u32,
            pos,
        );
        if ret != 0 {
            return Ok(ret);
        };

        if output.len() - *pos < 1 {
            return Ok(table_len - self.seek_table_pos as usize);
        }
        if (self.seek_table_pos as usize) < (table_len - 4) {
            let sfd = (self.checksum_flag << 7) as u8;

            output[*pos] = sfd;
            *pos += 1;
            self.seek_table_pos += 1;
        }

        let ret = self.stwrite32(output, SEEKABLE_MAGIC_NUMBER, (table_len - 4) as u32, pos);
        if ret != 0 {
            return Ok(ret);
        };

        if (self.seek_table_pos as usize) != table_len {
            Err(Error::Generic)
        } else {
            Ok(0)
        }
    }

    #[cfg(feature = "threadpool")]
    pub fn write_all<W: std::io::Write>(&mut self, mut w: W) -> Result<(), std::io::Error> {
        use crate::ZSTD_outBuffer;
        use libc::c_void;
        let mut output = [0; 1024];
        let mut output_ = ZSTD_outBuffer {
            dst: output.as_mut_ptr() as *mut c_void,
            size: 1024,
            pos: 0,
        };

        while let Ok(ret) = self.write_seek_table(&mut output, &mut output_.pos) {
            if ret == 0 {
                break;
            }

            w.write_all(&output[..output_.pos as usize])?;
            output_.pos = 0;
        }

        w.write_all(&output[..output_.pos as usize])?;
        Ok(())
    }
}