//! Functions for working with entry files.

use smallvec::{Array, SmallVec};

use std::fs::{File, OpenOptions};
use std::io::{Read, Seek, Write};
use std::io;
use std::iter;
use std::path::Path;

#[derive(Debug)]
pub struct Entries {
    file: File,
}

impl Entries {
    /// Open an entries file or create it if it does not yet exist.
    pub fn open<P>(path: P) -> io::Result<Self>
        where P: AsRef<Path> {
        let file = OpenOptions::new()
            .read(true)
            .write(true)
            .create(true)
            .open(path)?;
        Ok(Entries{file})
    }

    /// Append an entry and fsync the entries file. Return the offset of the new
    /// entry.
    pub fn append(&mut self, entry: &[u8]) -> io::Result<u64> {
        let tell = self.file.seek(io::SeekFrom::End(0))?;
        self.file.write_all(entry)?;
        self.file.sync_data()?;
        Ok(tell)
    }

    /// Read an entry at a specified offset and with a specified length.
    pub fn read<A>(&mut self, offset: u64, length: u32)
                   -> io::Result<SmallVec<A>>
        where A: Array<Item=u8> {
        let mut buffer = SmallVec::with_capacity(length as usize);
        buffer.extend(iter::repeat(0).take(length as usize));

        self.file.seek(io::SeekFrom::Start(offset))?;
        self.file.read_exact(&mut buffer)?;
        Ok(buffer)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    use utility::path::push_path;

    use tempdir::TempDir;

    #[test]
    fn test_entries() {
        let directory = TempDir::new("").unwrap();
        let file_path = push_path(directory.path().to_path_buf(), "ENTRIES");
        let mut entries = Entries::open(&file_path).unwrap();
        let hello_offset = entries.append(b"HELLO").unwrap();
        let user_offset = entries.append(b"USER").unwrap();
        assert!(entries.read::<[u8; 32]>(hello_offset, 5).unwrap().as_ref() == b"HELLO");
        assert!(entries.read::<[u8; 32]>(user_offset, 4).unwrap().as_ref() == b"USER");
    }
}
