use crdts::{CmRDT, VClock, Dot};

use error::Result;
use map;
use data::{Data, Op, Actor, Kind};
use log::{TaggedOp, LogReplicable};

pub type Map = map::Map<(Vec<u8>, Kind), Data, Actor>;

pub struct DB<L: LogReplicable<Actor, Map>> {
    log: L,
    remote_logs: Vec<L>,
    map: Map
}

impl<L: LogReplicable<Actor, Map>> DB<L> {
    pub fn new(log: L, map: Map) -> Self {
        DB { log, remote_logs: Vec::new(), map }
    }

    pub fn dot(&self, actor: Actor) -> Result<Dot<Actor>> {
        self.map.dot(actor)
    }

    pub fn get(&self, key: &(Vec<u8>, Kind)) -> Result<Option<map::Entry<Data, Actor>>> {
        self.map.get(key)
    }

    pub fn update<F>(&mut self, key: (Vec<u8>, Kind), dot: Dot<Actor>, updater: F) -> Result<()>
        where F: FnOnce(Data, Dot<Actor>) -> Op
    {
        let map_op = self.map.update(key, dot, updater)?;
        let tagged_op = self.log.commit(map_op)?;
        self.map.apply(tagged_op.op())?;
        self.log.ack(&tagged_op)
    }

    pub fn rm(&mut self, key: (Vec<u8>, Kind), context: VClock<Actor>) -> Result<()> {
        let op = self.map.rm(key, context);
        let tagged_op = self.log.commit(op)?;
        self.map.apply(tagged_op.op())?;
        self.log.ack(&tagged_op)
    }

    pub fn sync(&mut self) -> Result<()> {
        for mut remote_log in self.remote_logs.iter_mut() {
            self.log.pull(&remote_log)?;
            self.log.push(&mut remote_log)?;
        }

        while let Some(tagged_op) = self.log.next()? {
            self.map.apply(tagged_op.op())?;
            self.log.ack(&tagged_op)?;
        }
        Ok(())
    }
}
