use candid::CandidType;
use core::fmt;
use serde::{Deserialize, Serialize};
use std::ops::{Range, RangeBounds};

#[derive(Serialize, Deserialize, CandidType, Copy, Clone, Default, PartialEq, Eq, Hash)]
#[doc(alias = "..")]
pub struct CandidRange<Idx: CandidType> {
    pub start: Idx,
    pub end: Idx,
}

impl<Idx: fmt::Debug + CandidType> fmt::Debug for CandidRange<Idx> {
    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.start.fmt(fmt)?;
        write!(fmt, "..")?;
        self.end.fmt(fmt)?;
        Ok(())
    }
}

impl<Idx: PartialOrd<Idx> + CandidType> RangeBounds<Idx> for CandidRange<Idx> {
    fn start_bound(&self) -> std::ops::Bound<&Idx> {
        std::ops::Bound::Included(&self.start)
    }

    fn end_bound(&self) -> std::ops::Bound<&Idx> {
        std::ops::Bound::Excluded(&self.end)
    }
}

impl<Idx: PartialOrd<Idx> + CandidType> CandidRange<Idx> {
    pub fn contains<U>(&self, item: &U) -> bool
    where
        Idx: PartialOrd<U>,
        U: ?Sized + PartialOrd<Idx>,
    {
        <Self as RangeBounds<Idx>>::contains(self, item)
    }

    pub fn is_empty(&self) -> bool {
        !(self.start < self.end)
    }
}

impl<Idx: PartialOrd<Idx> + CandidType, Idx2> From<CandidRange<Idx>> for Range<Idx2>
where
    Idx: Into<Idx2>,
{
    fn from(range: CandidRange<Idx>) -> Self {
        Range {
            start: range.start.into(),
            end: range.end.into(),
        }
    }
}

impl<Idx, Idx2: PartialOrd<Idx> + CandidType> From<Range<Idx>> for CandidRange<Idx2>
where
    Idx: Into<Idx2>,
{
    fn from(range: Range<Idx>) -> Self {
        CandidRange {
            start: range.start.into(),
            end: range.end.into(),
        }
    }
}
