//! Last-writer-wins (LWW) register CRDT.
//!
//! Implements a delete-wins LWW register for primitive values inside a nested
//! JSON CRDT. Concurrent writes are resolved by sequence number; ties are broken
//! by author public key so every node converges to the same value.

use crate::debug::DebugView;
use crate::json_crdt::{CrdtNode, JsonValue, OpState};
use crate::op::{join_path, print_path, Op, PathSegment, SequenceNumber};
use serde::{Deserialize, Serialize};
use std::cmp::{max, Ordering};
use std::fmt::Debug;

use crate::keypair::AuthorId;

/// A simple delete-wins, last-writer-wins (LWW) register CRDT.
/// Basically only for adding support for primitives within a more complex CRDT
#[derive(Clone, Serialize, Deserialize)]
#[serde(bound(
    serialize = "T: serde::Serialize",
    deserialize = "T: serde::de::DeserializeOwned"
))]
pub struct LwwRegisterCrdt<T>
where
    T: CrdtNode,
{
    /// Public key for this node
    pub our_id: AuthorId,
    /// Path to this CRDT
    pub path: Vec<PathSegment>,
    /// Internal value of this CRDT. We wrap it in an Op to retain the author/sequence metadata
    value: Op<T>,
    /// The sequence number of this node
    our_seq: SequenceNumber,
}

impl<T> LwwRegisterCrdt<T>
where
    T: CrdtNode,
{
    /// Create a new register CRDT with the given [`AuthorID`] (it should be unique)
    pub fn new(id: AuthorId, path: Vec<PathSegment>) -> LwwRegisterCrdt<T> {
        LwwRegisterCrdt {
            our_id: id,
            path,
            value: Op::make_root(),
            our_seq: 0,
        }
    }

    /// Returns the current Lamport sequence number for this register.
    pub fn our_seq(&self) -> SequenceNumber {
        self.our_seq
    }

    /// Advance the internal sequence counter to at least `seq`.
    ///
    /// After `advance_seq(n)`, the next local op will carry `seq = max(our_seq, n) + 1`
    /// instead of the default `1`.  Used on restart to resume the Lamport clock
    /// from the document-wide floor so that newly-created registers don't
    /// re-emit low sequence numbers.
    pub fn advance_seq(&mut self, seq: SequenceNumber) {
        self.our_seq = max(self.our_seq, seq);
    }

    /// Sets the current value of the register
    pub fn set<U: Into<JsonValue>>(&mut self, content: U) -> Op<JsonValue> {
        let mut op = Op::new(
            self.value.id,
            self.our_id,
            self.our_seq + 1,
            false,
            Some(content.into()),
            self.path.to_owned(),
        );

        // we need to know the op ID before setting the path as [`PathSegment::Index`] requires an
        // [`OpID`]
        let new_path = join_path(self.path.to_owned(), PathSegment::Index(op.id));
        op.path = new_path;
        self.apply(op.clone());
        op
    }

    /// Apply an operation (both local and remote) to this local register CRDT.
    pub fn apply(&mut self, op: Op<JsonValue>) -> OpState {
        if !op.is_valid_hash() {
            return OpState::ErrHashMismatch;
        }

        let op: Op<T> = op.into();
        let seq = op.sequence_num();

        // take most recent update by sequence number
        match seq.cmp(&self.our_seq) {
            Ordering::Greater => {
                self.value = Op {
                    id: self.value.id,
                    ..op
                };
            }
            Ordering::Equal => {
                // if we are equal, tie break on author
                if op.author() < self.value.author() {
                    // we want to keep id constant so replace everything but id
                    self.value = Op {
                        id: self.value.id,
                        ..op
                    };
                }
            }
            Ordering::Less => {} // LWW, ignore if its outdate
        };

        // update bookkeeping
        self.our_seq = max(self.our_seq, seq);
        OpState::Ok
    }

    fn view(&self) -> Option<T> {
        self.value.content.to_owned()
    }
}

impl<T> CrdtNode for LwwRegisterCrdt<T>
where
    T: CrdtNode,
{
    fn apply(&mut self, op: Op<JsonValue>) -> OpState {
        self.apply(op.into())
    }

    fn view(&self) -> JsonValue {
        self.view().into()
    }

    fn new(id: AuthorId, path: Vec<PathSegment>) -> Self {
        Self::new(id, path)
    }
}

impl<T> DebugView for LwwRegisterCrdt<T>
where
    T: CrdtNode + DebugView,
{
    fn debug_view(&self, indent: usize) -> String {
        let spacing = " ".repeat(indent);
        let path_str = print_path(self.path.clone());
        let inner = self.value.debug_view(indent + 2);
        format!("LWW Register CRDT @ /{path_str}\n{spacing}{inner}")
    }
}

impl<T> Debug for LwwRegisterCrdt<T>
where
    T: CrdtNode,
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{:?}", self.value.id)
    }
}

#[cfg(test)]
mod test {
    use super::LwwRegisterCrdt;
    use crate::{json_crdt::OpState, keypair::make_author};

    #[test]
    fn test_lww_simple() {
        let mut register = LwwRegisterCrdt::new(make_author(1), vec![]);
        assert_eq!(register.view(), None);
        register.set(1);
        assert_eq!(register.view(), Some(1));
        register.set(99);
        assert_eq!(register.view(), Some(99));
    }

    #[test]
    fn test_lww_multiple_writer() {
        let mut register1 = LwwRegisterCrdt::new(make_author(1), vec![]);
        let mut register2 = LwwRegisterCrdt::new(make_author(2), vec![]);
        let _a = register1.set('a');
        let _b = register1.set('b');
        let _c = register2.set('c');
        assert_eq!(register2.view(), Some('c'));
        assert_eq!(register1.apply(_c), OpState::Ok);
        assert_eq!(register2.apply(_b), OpState::Ok);
        assert_eq!(register2.apply(_a), OpState::Ok);
        assert_eq!(register1.view(), Some('b'));
        assert_eq!(register2.view(), Some('b'));
    }

    #[test]
    fn test_lww_idempotence() {
        let mut register = LwwRegisterCrdt::new(make_author(1), vec![]);
        let op = register.set(1);
        for _ in 1..10 {
            assert_eq!(register.apply(op.clone()), OpState::Ok);
        }
        assert_eq!(register.view(), Some(1));
    }

    #[test]
    fn test_advance_seq_resumes_from_floor() {
        let mut register = LwwRegisterCrdt::<i64>::new(make_author(1), vec![]);
        register.advance_seq(100);
        assert_eq!(register.our_seq(), 100);
        let op = register.set(42);
        assert_eq!(
            op.seq, 101,
            "first op after advance_seq(100) must have seq=101"
        );
    }

    #[test]
    fn test_lww_consistent_tiebreak() {
        let mut register1 = LwwRegisterCrdt::new(make_author(1), vec![]);
        let mut register2 = LwwRegisterCrdt::new(make_author(2), vec![]);
        let _a = register1.set('a');
        let _b = register2.set('b');
        assert_eq!(register1.apply(_b), OpState::Ok);
        assert_eq!(register2.apply(_a), OpState::Ok);
        let _c = register1.set('c');
        let _d = register2.set('d');
        assert_eq!(register2.apply(_c), OpState::Ok);
        assert_eq!(register1.apply(_d), OpState::Ok);
        assert_eq!(register1.view(), register2.view());
        assert_eq!(register1.view(), Some('c'));
    }
}
