Documentation
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::io::Seek;
use std::io::Write;
use std::marker::PhantomData;
use std::path::Path;

use crate::{Batch, BatchCommit, JSStore, Ref};
pub trait Index<K, V>
where
    K: Eq + Ord + Serialize + DeserializeOwned,
    V: Serialize + DeserializeOwned,
{
    fn insert(&mut self, key: K, value: V);
    fn remove(&mut self, key: K, value: V);
}

pub struct JSStoreWithIndex<K, V, I>(JSStore<K, V>, I);
impl<
        K: DeserializeOwned + Eq + Ord + Serialize + Clone,
        V: DeserializeOwned + Serialize + Clone,
        I: Index<K, V>,
    > JSStoreWithIndex<K, V, I>
{
    pub fn new<P: AsRef<Path>>(
        filename: P,
        mut index: I,
    ) -> Result<JSStoreWithIndex<K, V, I>, std::io::Error> {
        let store = JSStore::new(filename)?;
        for (key, value) in store.iter() {
            index.insert(key, value)
        }
        Ok(JSStoreWithIndex(store, index))
    }
    pub fn insert<Key: Into<K>>(&mut self, k: Key, v: V) -> Result<(), std::io::Error> {
        let k = k.into();
        self.0.insert(k.clone(), v.clone())?;
        self.1.insert(k, v);
        Ok(())
    }
    pub fn remove<Key: Into<K>>(&mut self, k: Key) -> Result<(), std::io::Error> {
        let k = k.into();
        let v = self.0.get(k.clone())?;
        match v {
            Some(v) => {
                self.0.remove(k.clone())?;
                self.1.remove(k, v);
                Ok(())
            }
            None => Ok(()),
        }
    }
    pub fn basedb(&self) -> &JSStore<K, V> {
        &self.0
    }
    pub fn batch(&mut self) -> Batch<JSStoreWithIndex<K, V, I>, K, V> {
        Batch::new(self)
    }
    pub fn index(&self) -> &I {
        &self.1
    }
}

impl<K, V, I> BatchCommit<K, V> for JSStoreWithIndex<K, V, I>
where
    K: Eq + Ord + Serialize + DeserializeOwned + Clone,
    V: Serialize + DeserializeOwned,
    I: Index<K, V>,
{
    fn commit(&mut self, oplist: Vec<(K, Option<V>)>) -> Result<(), std::io::Error> {
        let mut file = &self.0 .1;
        let v: Vec<(K, Option<V>)> = oplist;
        file.seek(std::io::SeekFrom::End(0))?;
        let offset = file.stream_position()?;
        file.write_all(b"\n")?;
        let buf = serde_json::to_vec(&v).unwrap();
        file.write_all(&buf)?;
        for (key, value) in v {
            match value {
                Some(value) => {
                    self.0 .0.insert(
                        key.clone(),
                        Ref {
                            start: offset as usize,
                            length: buf.len() + 1,
                            p: PhantomData,
                        },
                    );
                    self.1.insert(key, value);
                }
                None => {
                    let value = self.0.get(key.clone())?;
                    if let Some(value) = value {
                        self.0 .0.remove(&key);
                        self.1.remove(key, value);
                    }
                }
            }
        }
        Ok(())
    }
}