// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! A utility class for implementing "snapshottable" things; a snapshottable data structure permits
//! you to take a snapshot (via `start_snapshot`) and then, after making some changes, elect either
//! to rollback to the start of the snapshot or commit those changes.
//!
//! This vector is intended to be used as part of an abstraction, not serve as a complete
//! abstraction on its own. As such, while it will roll back most changes on its own, it also
//! supports a `get_mut` operation that gives you an arbitrary mutable pointer into the vector. To
//! ensure that any changes you make this with this pointer are rolled back, you must invoke
//! `record` to record any changes you make and also supplying a delegate capable of reversing
//! those changes.

use self::UndoLog::*;

use std::fmt;
use std::mem;
use std::ops;

#[derive(Debug)]
pub enum UndoLog<D: SnapshotVecDelegate> {
    /// New variable with given index was created.
    NewElem(usize),

    /// Variable with given index was changed *from* the given value.
    SetElem(usize, D::Value),

    /// Extensible set of actions
    Other(D::Undo),
}

pub struct SnapshotVec<D: SnapshotVecDelegate> {
    values: Vec<D::Value>,
    undo_log: Vec<UndoLog<D>>,
    num_open_snapshots: usize,
}

impl<D> fmt::Debug for SnapshotVec<D>
    where D: SnapshotVecDelegate,
          D: fmt::Debug,
          D::Undo: fmt::Debug,
          D::Value: fmt::Debug
{
    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt.debug_struct("SnapshotVec")
            .field("values", &self.values)
            .field("undo_log", &self.undo_log)
            .field("num_open_snapshots", &self.num_open_snapshots)
            .finish()
    }
}

// Snapshots are tokens that should be created/consumed linearly.
pub struct Snapshot {
    // Number of values at the time the snapshot was taken.
    pub(crate) value_count: usize,
    // Length of the undo log at the time the snapshot was taken.
    undo_len: usize,
}

pub trait SnapshotVecDelegate {
    type Value;
    type Undo;

    fn reverse(values: &mut Vec<Self::Value>, action: Self::Undo);
}

// HACK(eddyb) manual impl avoids `Default` bound on `D`.
impl<D: SnapshotVecDelegate> Default for SnapshotVec<D> {
    fn default() -> Self {
        SnapshotVec {
            values: Vec::new(),
            undo_log: Vec::new(),
            num_open_snapshots: 0,
        }
    }
}

impl<D: SnapshotVecDelegate> SnapshotVec<D> {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn with_capacity(c: usize) -> SnapshotVec<D> {
        SnapshotVec {
            values: Vec::with_capacity(c),
            undo_log: Vec::new(),
            num_open_snapshots: 0,
        }
    }

    fn in_snapshot(&self) -> bool {
        self.num_open_snapshots > 0
    }

    pub fn record(&mut self, action: D::Undo) {
        if self.in_snapshot() {
            self.undo_log.push(Other(action));
        }
    }

    pub fn len(&self) -> usize {
        self.values.len()
    }

    pub fn push(&mut self, elem: D::Value) -> usize {
        let len = self.values.len();
        self.values.push(elem);

        if self.in_snapshot() {
            self.undo_log.push(NewElem(len));
        }

        len
    }

    pub fn get(&self, index: usize) -> &D::Value {
        &self.values[index]
    }

    /// Reserve space for new values, just like an ordinary vec.
    pub fn reserve(&mut self, additional: usize) {
        // This is not affected by snapshots or anything.
        self.values.reserve(additional);
    }

    /// Returns a mutable pointer into the vec; whatever changes you make here cannot be undone
    /// automatically, so you should be sure call `record()` with some sort of suitable undo
    /// action.
    pub fn get_mut(&mut self, index: usize) -> &mut D::Value {
        &mut self.values[index]
    }

    /// Updates the element at the given index. The old value will saved (and perhaps restored) if
    /// a snapshot is active.
    pub fn set(&mut self, index: usize, new_elem: D::Value) {
        let old_elem = mem::replace(&mut self.values[index], new_elem);
        if self.in_snapshot() {
            self.undo_log.push(SetElem(index, old_elem));
        }
    }

    /// Updates all elements. Potentially more efficient -- but
    /// otherwise equivalent to -- invoking `set` for each element.
    pub fn set_all(&mut self, mut new_elems: impl FnMut(usize) -> D::Value) {
        if !self.in_snapshot() {
            for (index, slot) in self.values.iter_mut().enumerate() {
                *slot = new_elems(index);
            }
        } else {
            for i in 0..self.values.len() {
                self.set(i, new_elems(i));
            }
        }
    }

    pub fn update<OP>(&mut self, index: usize, op: OP)
    where
        OP: FnOnce(&mut D::Value),
        D::Value: Clone,
    {
        if self.in_snapshot() {
            let old_elem = self.values[index].clone();
            self.undo_log.push(SetElem(index, old_elem));
        }
        op(&mut self.values[index]);
    }

    pub fn start_snapshot(&mut self) -> Snapshot {
        self.num_open_snapshots += 1;
        Snapshot {
            value_count: self.values.len(),
            undo_len: self.undo_log.len(),
        }
    }

    pub fn actions_since_snapshot(&self, snapshot: &Snapshot) -> &[UndoLog<D>] {
        &self.undo_log[snapshot.undo_len..]
    }

    fn assert_open_snapshot(&self, snapshot: &Snapshot) {
        // Failures here may indicate a failure to follow a stack discipline.
        assert!(self.undo_log.len() >= snapshot.undo_len);
        assert!(self.num_open_snapshots > 0);
    }

    pub fn rollback_to(&mut self, snapshot: Snapshot) {
        debug!("rollback_to({})", snapshot.undo_len);

        self.assert_open_snapshot(&snapshot);

        while self.undo_log.len() > snapshot.undo_len {
            match self.undo_log.pop().unwrap() {
                NewElem(i) => {
                    self.values.pop();
                    assert!(self.values.len() == i);
                }

                SetElem(i, v) => {
                    self.values[i] = v;
                }

                Other(u) => {
                    D::reverse(&mut self.values, u);
                }
            }
        }

        self.num_open_snapshots -= 1;
    }

    /// Commits all changes since the last snapshot. Of course, they
    /// can still be undone if there is a snapshot further out.
    pub fn commit(&mut self, snapshot: Snapshot) {
        debug!("commit({})", snapshot.undo_len);

        self.assert_open_snapshot(&snapshot);

        if self.num_open_snapshots == 1 {
            // The root snapshot. It's safe to clear the undo log because
            // there's no snapshot further out that we might need to roll back
            // to.
            assert!(snapshot.undo_len == 0);
            self.undo_log.clear();
        }

        self.num_open_snapshots -= 1;
    }
}

impl<D: SnapshotVecDelegate> ops::Deref for SnapshotVec<D> {
    type Target = [D::Value];
    fn deref(&self) -> &[D::Value] {
        &*self.values
    }
}

impl<D: SnapshotVecDelegate> ops::DerefMut for SnapshotVec<D> {
    fn deref_mut(&mut self) -> &mut [D::Value] {
        &mut *self.values
    }
}

impl<D: SnapshotVecDelegate> ops::Index<usize> for SnapshotVec<D> {
    type Output = D::Value;
    fn index(&self, index: usize) -> &D::Value {
        self.get(index)
    }
}

impl<D: SnapshotVecDelegate> ops::IndexMut<usize> for SnapshotVec<D> {
    fn index_mut(&mut self, index: usize) -> &mut D::Value {
        self.get_mut(index)
    }
}

impl<D: SnapshotVecDelegate> Extend<D::Value> for SnapshotVec<D> {
    fn extend<T>(&mut self, iterable: T)
    where
        T: IntoIterator<Item = D::Value>,
    {
        let initial_len = self.values.len();
        self.values.extend(iterable);
        let final_len = self.values.len();

        if self.in_snapshot() {
            self.undo_log.extend((initial_len..final_len).map(|len| NewElem(len)));
        }
    }
}

impl<D: SnapshotVecDelegate> Clone for SnapshotVec<D>
where
    D::Value: Clone,
    D::Undo: Clone,
{
    fn clone(&self) -> Self {
        SnapshotVec {
            values: self.values.clone(),
            undo_log: self.undo_log.clone(),
            num_open_snapshots: self.num_open_snapshots,
        }
    }
}

impl<D: SnapshotVecDelegate> Clone for UndoLog<D>
where
    D::Value: Clone,
    D::Undo: Clone,
{
    fn clone(&self) -> Self {
        match *self {
            NewElem(i) => NewElem(i),
            SetElem(i, ref v) => SetElem(i, v.clone()),
            Other(ref u) => Other(u.clone()),
        }
    }
}

impl SnapshotVecDelegate for i32 {
    type Value = i32;
    type Undo = ();

    fn reverse(_: &mut Vec<i32>, _: ()) {}
}

#[test]
fn basic() {
    let mut vec: SnapshotVec<i32> = SnapshotVec::default();
    assert!(!vec.in_snapshot());
    assert_eq!(vec.len(), 0);
    vec.push(22);
    vec.push(33);
    assert_eq!(vec.len(), 2);
    assert_eq!(*vec.get(0), 22);
    assert_eq!(*vec.get(1), 33);
    vec.set(1, 34);
    assert_eq!(vec.len(), 2);
    assert_eq!(*vec.get(0), 22);
    assert_eq!(*vec.get(1), 34);

    let snapshot = vec.start_snapshot();
    assert!(vec.in_snapshot());

    vec.push(44);
    vec.push(55);
    vec.set(1, 35);
    assert_eq!(vec.len(), 4);
    assert_eq!(*vec.get(0), 22);
    assert_eq!(*vec.get(1), 35);
    assert_eq!(*vec.get(2), 44);
    assert_eq!(*vec.get(3), 55);

    vec.rollback_to(snapshot);
    assert!(!vec.in_snapshot());

    assert_eq!(vec.len(), 2);
    assert_eq!(*vec.get(0), 22);
    assert_eq!(*vec.get(1), 34);
}

#[test]
#[should_panic]
fn out_of_order() {
    let mut vec: SnapshotVec<i32> = SnapshotVec::default();
    vec.push(22);
    let snapshot1 = vec.start_snapshot();
    vec.push(33);
    let snapshot2 = vec.start_snapshot();
    vec.push(44);
    vec.rollback_to(snapshot1); // bogus, but accepted
    vec.rollback_to(snapshot2); // asserts
}

#[test]
fn nested_commit_then_rollback() {
    let mut vec: SnapshotVec<i32> = SnapshotVec::default();
    vec.push(22);
    let snapshot1 = vec.start_snapshot();
    let snapshot2 = vec.start_snapshot();
    vec.set(0, 23);
    vec.commit(snapshot2);
    assert_eq!(*vec.get(0), 23);
    vec.rollback_to(snapshot1);
    assert_eq!(*vec.get(0), 22);
}
