#![deny(missing_docs)]
use std::ops::Range;
use crate::AsRawDescriptor;
use crate::Error;
use crate::Result;
enum LseekOption {
    Data,
    Hole,
}
fn lseek(fd: &dyn AsRawDescriptor, offset: u64, option: LseekOption) -> Result<u64> {
    let whence = match option {
        LseekOption::Data => libc::SEEK_DATA,
        LseekOption::Hole => libc::SEEK_HOLE,
    };
    let ret = unsafe { libc::lseek64(fd.as_raw_descriptor(), offset as i64, whence) };
    if ret < 0 {
        return Err(Error::last());
    }
    Ok(ret as u64)
}
pub fn find_next_data(
    fd: &dyn AsRawDescriptor,
    offset: u64,
    len: u64,
) -> Result<Option<Range<u64>>> {
    let end = offset + len;
    let offset_data = match lseek(fd, offset, LseekOption::Data) {
        Ok(offset) => {
            if offset >= end {
                return Ok(None);
            } else {
                offset
            }
        }
        Err(e) => {
            return match e.errno() {
                libc::ENXIO => Ok(None),
                _ => Err(e),
            }
        }
    };
    let offset_hole = lseek(fd, offset_data, LseekOption::Hole)?;
    Ok(Some(offset_data..offset_hole.min(end)))
}
pub struct FileDataIterator<'a> {
    fd: &'a dyn AsRawDescriptor,
    offset: u64,
    end: u64,
    failed: bool,
}
impl<'a> FileDataIterator<'a> {
    pub fn new(fd: &'a dyn AsRawDescriptor, offset: u64, len: u64) -> Self {
        Self {
            fd,
            offset,
            end: offset + len,
            failed: false,
        }
    }
}
impl Iterator for FileDataIterator<'_> {
    type Item = Result<Range<u64>>;
    fn next(&mut self) -> Option<Self::Item> {
        if self.failed {
            return None;
        }
        match find_next_data(self.fd, self.offset, self.end - self.offset) {
            Ok(data_range) => {
                if let Some(ref data_range) = data_range {
                    self.offset = data_range.end;
                }
                data_range.map(Ok)
            }
            Err(e) => {
                self.failed = true;
                Some(Err(e))
            }
        }
    }
}
#[cfg(test)]
mod tests {
    use std::os::unix::fs::FileExt;
    use super::*;
    use crate::pagesize;
    #[test]
    fn file_data_iterator() {
        let file = tempfile::tempfile().unwrap();
        file.write_at(&[1_u8], 10).unwrap();
        file.write_at(&[1_u8], 2 * pagesize() as u64).unwrap();
        file.write_at(&[1_u8], (4 * pagesize() - 1) as u64).unwrap();
        let iter = FileDataIterator::new(&file, 0, 4 * pagesize() as u64);
        let result = iter.collect::<Result<Vec<Range<u64>>>>().unwrap();
        assert_eq!(result.len(), 2);
        assert_eq!(result[0], 0..(pagesize() as u64));
        assert_eq!(result[1], (2 * pagesize() as u64)..(4 * pagesize() as u64));
    }
    #[test]
    fn file_data_iterator_subrange() {
        let file = tempfile::tempfile().unwrap();
        file.write_at(&[1_u8], 0).unwrap();
        file.write_at(&[1_u8], pagesize() as u64).unwrap();
        file.write_at(&[1_u8], 2 * pagesize() as u64).unwrap();
        file.write_at(&[1_u8], 4 * pagesize() as u64).unwrap();
        let iter = FileDataIterator::new(&file, pagesize() as u64, pagesize() as u64);
        let result = iter.collect::<Result<Vec<Range<u64>>>>().unwrap();
        assert_eq!(result.len(), 1);
        assert_eq!(result[0], (pagesize() as u64)..(2 * pagesize() as u64));
    }
}