#![no_std]

extern crate alloc;
use alloc::collections::BTreeSet;

mod id_gen_error;
pub use id_gen_error::IdGenError;

mod incrementable_id;
pub use incrementable_id::IncrementableId;

/// Internal generator for unique, reusable identifiers
///
/// Generates IDs in a sequential manner, and reuses IDs
/// which have been returned to the system. If you don't
/// want IDs to be reused, don't return them.
/// 
/// ```
/// // create a generator and get some ids
/// use inc_id::IdGenerator;
/// 
/// let mut id_gen = IdGenerator::<u32>::new();
/// let id1 = id_gen.get_id().unwrap();
/// let id2 = id_gen.get_id().unwrap();
/// assert_ne!(id1, id2);
/// 
/// // return the first id
/// id_gen.return_id(id1).unwrap();
/// 
/// // the next call to get_id() will reuse the returned id
/// let id3 = id_gen.get_id().unwrap();
/// 
/// assert_eq!(id1, id3);
/// 
/// // this will continue with the next incremental id
/// let id4 = id_gen.get_id().unwrap();
///
/// assert_ne!(id3, id4);
/// assert_ne!(id2, id4);
/// ```
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct IdGenerator<T> {
    /// Latest generated identifier
    cur_id: Option<T>,
    /// Available identifiers which have been returned
    available_ids: BTreeSet<T>,
}

impl<T> IdGenerator<T>
where T: IncrementableId + Copy + PartialEq + Ord {
    /// Creates a new IdGenerator with the specified maximum id value.
    pub fn new() -> Self {
        IdGenerator {
            cur_id: None,
            available_ids: BTreeSet::new(),
        }
    }

    /// Retrieves the number of ids which have been returned to the generator
    /// and are awaiting reuse.
    /// 
    /// ```
    /// let id_gen = IdGenerator::<u32>::new();
    /// 
    /// let id1 = id_gen.get_id().unwrap();
    /// 
    /// // return the first id
    /// id_gen.return_id(id1).unwrap();
    /// 
    /// // there is now 1 id awaiting reassignment
    /// assert!(id_gen.returned_len(), 1);
    /// ```
    pub fn returned_len(&self) -> usize {
        self.available_ids.len()
    }

    /// Retrieves an available identifier from the generator.
    /// 
    /// Returned IDs will be reissued before new incremental
    /// ones are generated.
    /// 
    /// If a maximum value was provided, and the next generated id would
    /// exceed the defined maximum value, an IdsExhausted error is returned.
    pub fn get_id(&mut self) -> Result<T, IdGenError<T>> {
        match self.available_ids.pop_first() {
            Some(id) => {
                return Ok(id);
            }
            None => {
                if self.cur_id.is_some_and(|cur_id| cur_id == T::max_value()) {
                    return Err(IdGenError::IdsExhausted(T::max_value()));
                }

                self.cur_id = Some(match self.cur_id {
                    None => T::min_value(),
                    Some(prev) => T::next_value(prev),
                });

                return Ok(self.cur_id.unwrap());
            }
        }
    }

    /// Returns an identifier to the generator for later reuse.
    /// 
    /// Returning an id which was already returned will result in a DuplicateIdError.
    /// 
    /// Returning an id with a value greater than the maximum id already generated
    /// will result in an InvalidIdError.
    pub fn return_id(&mut self, id: T) -> Result<(), IdGenError<T>> {
        if self.available_ids.contains(&id) {
            // return an error when an id which was already returned is returned again
            return Err(IdGenError::DuplicateIdReturn(id));
        }

        if self.cur_id.is_none() || self.cur_id.is_some_and(|cur_id| id > cur_id) {
            // return an error when a returned id is greater than the max value already generated
            return Err(IdGenError::InvalidId(id));
        }

        self.available_ids.insert(id);

        Ok(())
    }
}
