//! This module deals with catalogs and their storage. Each database has a
//! single catalog, which stores metadata about journals.

use utility::rc_vec::RcVec;

use bincode::{deserialize_from, serialize_into};
use bincode;

use std::collections::HashMap;
use std::collections::hash_map;
use std::fs::File;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};

use uuid::Uuid;

aggregate_errors!(
    CatalogInitializeError {
        BincodeError(Box<bincode::ErrorKind>),
        IoError(io::Error),
    }

    CatalogOpenError {
        BincodeError(Box<bincode::ErrorKind>),
        IoError(io::Error),
    }

    CatalogAddJournalError {
        JournalAlreadyExistsError(JournalAlreadyExistsError),
        CatalogSaveError(CatalogSaveError),
    }

    CatalogAddExtensionError {
        ExtensionAlreadyExistsError(ExtensionAlreadyExistsError),
        CatalogSaveError(CatalogSaveError),
    }

    CatalogSaveError {
        BincodeError(Box<bincode::ErrorKind>),
        IoError(io::Error),
    }
);

unit_errors!(
    JournalAlreadyExistsError = "Journal already exists",
    ExtensionAlreadyExistsError = "Extension already exists",
);

#[derive(Debug)]
pub struct Catalog {
    path: PathBuf,
    scratch_path: PathBuf,
    data: CatalogData,
}

impl Catalog {
    pub fn initialize<P>(path: P) -> Result<(), CatalogInitializeError>
        where P: AsRef<Path> {
        let journals = HashMap::new();
        let extensions = HashMap::new();
        let data = CatalogData{journals, extensions};
        let mut file = File::create(path)?;
        serialize_into(&mut file, &data)?;
        Ok(())
    }

    pub fn open(path: PathBuf, scratch_path: PathBuf) -> Result<Self, CatalogOpenError> {
        let mut file = File::open(&path)?;
        let data = deserialize_from(&mut file)?;
        Ok(Catalog{path, scratch_path, data})
    }

    fn save(&self) -> Result<(), CatalogSaveError> {
        {
            let mut file = File::create(&self.scratch_path)?;
            serialize_into(&mut file, &self.data)?;
        }
        fs::rename(&self.scratch_path, &self.path)?;
        Ok(())
    }
}

impl Catalog {
    pub fn journals(&self) -> hash_map::Iter<RcVec<u8>, Uuid> {
        self.data.journals.iter()
    }

    pub fn get_journal(&self, name: &[u8]) -> Option<Uuid> {
        self.data.journals.get(name).map(|&a| a)
    }

    pub fn add_journal(&mut self, name: RcVec<u8>) -> Result<Uuid, CatalogAddJournalError> {
        if self.data.journals.contains_key(&name) {
            let error = JournalAlreadyExistsError;
            let error = CatalogAddJournalError::JournalAlreadyExistsError(error);
            return Err(error);
        }
        let journal_id = Uuid::new_v4();
        self.data.journals.insert(name, journal_id);
        self.save()?;
        Ok(journal_id)
    }
}

impl Catalog {
    pub fn extensions(&self) -> hash_map::Iter<RcVec<u8>, PathBuf> {
        self.data.extensions.iter()
    }

    pub fn get_extension(&self, name: &[u8]) -> Option<&Path> {
        self.data.extensions.get(name).map(|a| a.as_ref())
    }

    pub fn add_extension(&mut self, name: RcVec<u8>, path: PathBuf)
                         -> Result<(), CatalogAddExtensionError> {
        if self.data.extensions.contains_key(&name) {
            let error = ExtensionAlreadyExistsError;
            let error = CatalogAddExtensionError::ExtensionAlreadyExistsError(error);
            return Err(error);
        }
        self.data.extensions.insert(name, path);
        self.save()?;
        Ok(())
    }
}

#[derive(Clone, Debug, Deserialize, Serialize)]
struct CatalogData {
    journals: HashMap<RcVec<u8>, Uuid>,
    extensions: HashMap<RcVec<u8>, PathBuf>,
}
