use ::prelude::*;

use std::path::{Path, PathBuf};
use std::fs::{self, File};
use std::io::{self, BufReader, BufWriter, Write, Read};


pub static CACHE_FILE_STRING: [u8; 7] = *b"zvault\x04";
pub static CACHE_FILE_VERSION: u8 = 1;


quick_error!{
    #[derive(Debug)]
    pub enum BundleCacheError {
        Read(err: io::Error) {
            cause(err)
            description("Failed to read bundle cache")
            display("Bundle cache error: failed to read bundle cache\n\tcaused by: {}", err)
        }
        Write(err: io::Error) {
            cause(err)
            description("Failed to write bundle cache")
            display("Bundle cache error: failed to write bundle cache\n\tcaused by: {}", err)
        }
        WrongHeader {
            description("Wrong header")
            display("Bundle cache error: wrong header on bundle cache")
        }
        UnsupportedVersion(version: u8) {
            description("Wrong version")
            display("Bundle cache error: unsupported version: {}", version)
        }
        Decode(err: msgpack::DecodeError) {
            from()
            cause(err)
            description("Failed to decode bundle cache")
            display("Bundle cache error: failed to decode bundle cache\n\tcaused by: {}", err)
        }
        Encode(err: msgpack::EncodeError) {
            from()
            cause(err)
            description("Failed to encode bundle cache")
            display("Bundle cache error: failed to encode bundle cache\n\tcaused by: {}", err)
        }
    }
}


#[derive(Clone, Default)]
pub struct StoredBundle {
    pub info: BundleInfo,
    pub path: PathBuf
}
serde_impl!(StoredBundle(u64) {
    info: BundleInfo => 0,
    path: PathBuf => 1
});

impl StoredBundle {
    #[inline]
    pub fn id(&self) -> BundleId {
        self.info.id.clone()
    }

    pub fn move_to<P: AsRef<Path>>(mut self, path: P) -> Result<Self, BundleDbError> {
        let path = path.as_ref();
        if fs::rename(&self.path, path).is_err() {
            try!(fs::copy(&self.path, path).context(path));
            try!(fs::remove_file(&self.path).context(&self.path as &Path));
        }
        self.path = path.to_path_buf();
        Ok(self)
    }

    pub fn copy_to<P: AsRef<Path>>(&self, path: P) -> Result<Self, BundleDbError> {
        let path = path.as_ref();
        try!(fs::copy(&self.path, path).context(path));
        let mut bundle = self.clone();
        bundle.path = path.to_path_buf();
        Ok(bundle)
    }

    pub fn read_list_from<P: AsRef<Path>>(path: P) -> Result<Vec<Self>, BundleCacheError> {
        let path = path.as_ref();
        let mut file = BufReader::new(try!(File::open(path).map_err(BundleCacheError::Read)));
        let mut header = [0u8; 8];
        try!(file.read_exact(&mut header).map_err(BundleCacheError::Read));
        if header[..CACHE_FILE_STRING.len()] != CACHE_FILE_STRING {
            return Err(BundleCacheError::WrongHeader)
        }
        let version = header[CACHE_FILE_STRING.len()];
        if version != CACHE_FILE_VERSION {
            return Err(BundleCacheError::UnsupportedVersion(version))
        }
        Ok(try!(msgpack::decode_from_stream(&mut file)))
    }

    pub fn save_list_to<P: AsRef<Path>>(list: &[Self], path: P) -> Result<(), BundleCacheError> {
        let path = path.as_ref();
        let mut file = BufWriter::new(try!(File::create(path).map_err(BundleCacheError::Write)));
        try!(file.write_all(&CACHE_FILE_STRING).map_err(BundleCacheError::Write));
        try!(file.write_all(&[CACHE_FILE_VERSION]).map_err(BundleCacheError::Write));
        try!(msgpack::encode_to_stream(&list, &mut file));
        Ok(())
    }
}