use std::cell::{Ref, RefCell, RefMut};
use super::commit::Commit;

#[derive(Debug, Default)]
pub struct NoCommit {}

/// The list of commits indexable by `CommitIndex` with interior mutability.
#[derive(Default)]
pub(crate) struct Commits(RefCell<Vec<Commit>>);

/// The index to `Commits`. Can be also "no index", that means, no commit. Indexing into
/// `Commits` with no index leads to `NoCommit`. Internally, "no index" is represented by -1.
/// No other negative values are valid for the internal value.
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub(crate) struct CommitIndex(isize);

pub(crate) struct CommitsIterator<'a> {
    commits: Ref<'a, Vec<Commit>>,
    index: usize,
}

impl From<NoCommit> for String {
    fn from(_: NoCommit) -> String {
        "No current commit".to_string()
    }
}

impl Commits {
    /// Get the number of the contained commits.
    pub(crate) fn len(&self) -> usize {
        self.0.borrow().len()
    }

    /// Push the given commit and return the new length.
    pub(crate) fn push(&self, commit: Commit) -> usize {
        let mut commits = self.0.borrow_mut();
        commits.push(commit);
        return commits.len();
    }

    /// Insert the given commit to the given position. Does nothing if no index.
    pub(crate) fn insert(&self, index: CommitIndex, commit: Commit) {
        if let Some(index) = index.try_index() {
            let mut commits = self.0.borrow_mut();
            commits.insert(index, commit);
        }
    }

    /// Remove and return the commit at the given position. Return Err(NoCommit) if no index.
    pub(crate) fn remove(&self, index: CommitIndex) -> Result<Commit, NoCommit> {
        match index.try_index() {
            Some(i) => {
                let mut commits = self.0.borrow_mut();
                Ok(commits.remove(i))
            }
            None => Err(NoCommit::default()),
        }
    }

    /// Get the commit with the given index. Return `Err(NoCommit)` if no index. Panics if the
    /// index is too large.
    pub(crate) fn get(&self, index: CommitIndex) -> Result<Ref<'_, Commit>, NoCommit> {
        match index.try_index() {
            Some(i) => Ok(Ref::map(
                self.0.borrow(),
                |commits| &commits[i],
            )),
            None => Err(NoCommit::default()),
        }
    }

    pub(crate) fn get_mut(&self, index: CommitIndex) -> Result<RefMut<'_, Commit>, NoCommit> {
        match index.try_index() {
            Some(i) => Ok(RefMut::map(
                self.0.borrow_mut(),
                |commits| &mut commits[i],
            )),
            None => Err(NoCommit::default()),
        }
    }

    /// Iterate over all commits.
    pub(crate) fn iter(&self) -> CommitsIterator<'_> {
        CommitsIterator {
            commits: self.0.borrow(),
            index: 0,
        }
    }
}

impl CommitIndex {
    /// Get the actual index or None if no index.
    pub(crate) fn try_index(&self) -> Option<usize> {
        usize::try_from(self.0).ok()
    }

    /// Get the actual index. Panics in case of no index.
    pub(crate) fn index(&self) -> usize {
        self.try_index().unwrap_or_else(|| panic!("CommitIndex with no index set"))
    }

    /// True if there's no index set.
    pub(crate) fn no_index(&self) -> bool {
        self.0 < 0
    }

    /// Advance to the next index. If no index, advance to the first index (0).
    pub(crate) fn next(&self) -> Self {
        Self(self.0 + 1)
    }

    /// Move to the previous index. If at the first index, return no index. If at no index,
    /// stay there.
    pub(crate) fn prev(&self) -> Self {
        Self(if self.0 <= 0 { -1 } else { self.0 - 1 })
    }

    /// Iterate from 0 to our index (exclusive). Returns an empty iterator if no index.
    pub(crate) fn iter_until(&self) -> impl DoubleEndedIterator<Item = Self> {
        (0 .. self.0).map(Self)
    }

    /// Iterate from the given index (exclusive) to our index (inclusive). If the given index
    /// is no index, start from 0. If the given index is equal to or larger than our index,
    /// nothing is iterated.
    pub(crate) fn iter_after(&self, prev: Self) -> impl DoubleEndedIterator<Item = Self> {
        (prev.0 + 1 ..= self.0).map(Self)
    }

    /// True if the index is within the passed bound (exclusive), that is, less than `max`.
    pub(crate) fn within(&self, max: usize) -> bool {
        match self.try_index() {
            Some(i) => i < max,
            None => true,
        }
    }
}

impl Default for CommitIndex {
    fn default() -> Self {
        Self(-1)
    }
}

impl From<usize> for CommitIndex {
    fn from(val: usize) -> CommitIndex {
        CommitIndex(val as isize)
    }
}

impl<'a> Iterator for CommitsIterator<'a> {
    type Item = Ref<'a, Commit>;

    fn next(&mut self) -> Option<Self::Item> {
        let index = self.index;
        if index >= self.commits.len() {
            return None;
        }
        self.index += 1;
        Some(Ref::map(
            Ref::clone(&self.commits),
            |commits| &commits[index],
        ))
    }
}
