use std::cmp::Ordering;
use std::convert::Infallible;
#[cfg(feature = "arbitrary")]
use arbitrary::Arbitrary;
use jiff::{SignedDuration, Timestamp};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::intervals::absolute::{
AbsBoundPair,
AbsEndBound,
AbsInterval,
AbsStartBound,
BoundedAbsInterval,
EmptiableAbsBoundPair,
HalfBoundedAbsInterval,
HasAbsBoundPair,
HasEmptiableAbsBoundPair,
};
use crate::intervals::meta::{BoundInclusivity, HasBoundInclusivity};
use crate::intervals::relative::{
BoundedRelInterval,
EmptiableRelBoundPair,
HalfBoundedRelInterval,
HasEmptiableRelBoundPair,
HasRelBoundPair,
RelBoundPair,
RelEndBound,
RelInterval,
RelStartBound,
};
use crate::intervals::special::{EmptyInterval, UnboundedInterval};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum PointContainmentPosition {
OutsideBefore,
OutsideAfter,
Outside,
OnStart(BoundInclusivity),
OnEnd(BoundInclusivity),
Inside,
}
impl PointContainmentPosition {
#[must_use]
pub fn strip(self) -> DisambiguatedPointContainmentPosition {
match self {
Self::OutsideBefore => DisambiguatedPointContainmentPosition::OutsideBefore,
Self::OutsideAfter => DisambiguatedPointContainmentPosition::OutsideAfter,
Self::Outside => DisambiguatedPointContainmentPosition::Outside,
Self::OnStart(_) => DisambiguatedPointContainmentPosition::OnStart,
Self::OnEnd(_) => DisambiguatedPointContainmentPosition::OnEnd,
Self::Inside => DisambiguatedPointContainmentPosition::Inside,
}
}
#[must_use]
pub fn disambiguate_using_rule_set(
self,
rule_set: PointContainmentRuleSet,
) -> DisambiguatedPointContainmentPosition {
rule_set.disambiguate(self)
}
#[must_use]
pub fn disambiguate_using<F>(self, f: F) -> DisambiguatedPointContainmentPosition
where
F: FnOnce(PointContainmentPosition) -> DisambiguatedPointContainmentPosition,
{
(f)(self)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum DisambiguatedPointContainmentPosition {
OutsideBefore,
OutsideAfter,
Outside,
OnStart,
OnEnd,
Inside,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum PointContainmentRuleSet {
#[default]
Strict,
Lenient,
}
impl PointContainmentRuleSet {
#[must_use]
pub fn disambiguate(
&self,
containment_position: PointContainmentPosition,
) -> DisambiguatedPointContainmentPosition {
match self {
Self::Strict => strict_containment_rule_set_disambiguation(containment_position),
Self::Lenient => lenient_containment_rule_set_disambiguation(containment_position),
}
}
}
#[must_use]
pub fn strict_containment_rule_set_disambiguation(
containment_position: PointContainmentPosition,
) -> DisambiguatedPointContainmentPosition {
match containment_position {
PointContainmentPosition::OutsideBefore | PointContainmentPosition::OnStart(BoundInclusivity::Exclusive) => {
DisambiguatedPointContainmentPosition::OutsideBefore
},
PointContainmentPosition::OutsideAfter | PointContainmentPosition::OnEnd(BoundInclusivity::Exclusive) => {
DisambiguatedPointContainmentPosition::OutsideAfter
},
PointContainmentPosition::Outside => DisambiguatedPointContainmentPosition::Outside,
PointContainmentPosition::OnStart(BoundInclusivity::Inclusive) => {
DisambiguatedPointContainmentPosition::OnStart
},
PointContainmentPosition::OnEnd(BoundInclusivity::Inclusive) => DisambiguatedPointContainmentPosition::OnEnd,
PointContainmentPosition::Inside => DisambiguatedPointContainmentPosition::Inside,
}
}
#[must_use]
pub fn lenient_containment_rule_set_disambiguation(
containment_position: PointContainmentPosition,
) -> DisambiguatedPointContainmentPosition {
match containment_position {
PointContainmentPosition::OutsideBefore => DisambiguatedPointContainmentPosition::OutsideBefore,
PointContainmentPosition::OutsideAfter => DisambiguatedPointContainmentPosition::OutsideAfter,
PointContainmentPosition::Outside => DisambiguatedPointContainmentPosition::Outside,
PointContainmentPosition::OnStart(_) => DisambiguatedPointContainmentPosition::OnStart,
PointContainmentPosition::OnEnd(_) => DisambiguatedPointContainmentPosition::OnEnd,
PointContainmentPosition::Inside => DisambiguatedPointContainmentPosition::Inside,
}
}
pub const DEFAULT_POINT_CONTAINMENT_RULES: [PointContainmentRule; 1] = [PointContainmentRule::AllowOnBounds];
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum PointContainmentRule {
AllowOnStart,
AllowOnEnd,
AllowOnBounds,
DenyOnStart,
DenyOnEnd,
DenyOnBounds,
}
impl PointContainmentRule {
#[must_use]
pub fn counts_as_contained(&self, running: bool, disambiguated_pos: DisambiguatedPointContainmentPosition) -> bool {
match self {
Self::AllowOnStart => allow_on_start_containment_rule_counts_as_contained(running, disambiguated_pos),
Self::AllowOnEnd => allow_on_end_containment_rule_counts_as_contained(running, disambiguated_pos),
Self::AllowOnBounds => allow_on_bounds_containment_rule_counts_as_contained(running, disambiguated_pos),
Self::DenyOnStart => deny_on_start_containment_rule_counts_as_contained(running, disambiguated_pos),
Self::DenyOnEnd => deny_on_end_containment_rule_counts_as_contained(running, disambiguated_pos),
Self::DenyOnBounds => deny_on_bounds_containment_rule_counts_as_contained(running, disambiguated_pos),
}
}
}
#[must_use]
pub fn check_point_containment_rules<'a, RI>(
disambiguated_pos: DisambiguatedPointContainmentPosition,
rules: RI,
) -> bool
where
RI: IntoIterator<Item = &'a PointContainmentRule>,
{
let common = matches!(disambiguated_pos, DisambiguatedPointContainmentPosition::Inside);
rules.into_iter().fold(common, |is_contained, rule| {
rule.counts_as_contained(is_contained, disambiguated_pos)
})
}
#[must_use]
pub fn allow_on_start_containment_rule_counts_as_contained(
running: bool,
disambiguated_pos: DisambiguatedPointContainmentPosition,
) -> bool {
running || matches!(disambiguated_pos, DisambiguatedPointContainmentPosition::OnStart)
}
#[must_use]
pub fn allow_on_end_containment_rule_counts_as_contained(
running: bool,
disambiguated_pos: DisambiguatedPointContainmentPosition,
) -> bool {
running || matches!(disambiguated_pos, DisambiguatedPointContainmentPosition::OnEnd)
}
#[must_use]
pub fn allow_on_bounds_containment_rule_counts_as_contained(
running: bool,
disambiguated_pos: DisambiguatedPointContainmentPosition,
) -> bool {
running
|| matches!(
disambiguated_pos,
DisambiguatedPointContainmentPosition::OnStart | DisambiguatedPointContainmentPosition::OnEnd
)
}
#[must_use]
pub fn deny_on_start_containment_rule_counts_as_contained(
running: bool,
disambiguated_pos: DisambiguatedPointContainmentPosition,
) -> bool {
running && !matches!(disambiguated_pos, DisambiguatedPointContainmentPosition::OnStart)
}
#[must_use]
pub fn deny_on_end_containment_rule_counts_as_contained(
running: bool,
disambiguated_pos: DisambiguatedPointContainmentPosition,
) -> bool {
running && !matches!(disambiguated_pos, DisambiguatedPointContainmentPosition::OnEnd)
}
#[must_use]
pub fn deny_on_bounds_containment_rule_counts_as_contained(
running: bool,
disambiguated_pos: DisambiguatedPointContainmentPosition,
) -> bool {
running
&& !matches!(
disambiguated_pos,
DisambiguatedPointContainmentPosition::OnStart | DisambiguatedPointContainmentPosition::OnEnd,
)
}
pub trait CanPositionPointContainment<P> {
type Error;
fn point_containment_position(&self, positionable: P) -> Result<PointContainmentPosition, Self::Error>;
fn disambiguated_point_containment_position(
&self,
positionable: P,
rule_set: PointContainmentRuleSet,
) -> Result<DisambiguatedPointContainmentPosition, Self::Error> {
self.point_containment_position(positionable)
.map(|containment_position| rule_set.disambiguate(containment_position))
}
#[must_use]
fn simple_contains_point(&self, positionable: P) -> bool {
self.contains_point(
positionable,
PointContainmentRuleSet::default(),
&DEFAULT_POINT_CONTAINMENT_RULES,
)
}
#[must_use]
fn contains_point<'a, RI>(&self, positionable: P, rule_set: PointContainmentRuleSet, rules: RI) -> bool
where
RI: IntoIterator<Item = &'a PointContainmentRule>,
{
self.disambiguated_point_containment_position(positionable, rule_set)
.is_ok_and(|disambiguated_containment_position| {
check_point_containment_rules(disambiguated_containment_position, rules)
})
}
#[must_use]
fn contains_point_using<F>(&self, positionable: P, f: F) -> bool
where
F: FnOnce(PointContainmentPosition) -> bool,
{
self.point_containment_position(positionable).is_ok_and(f)
}
#[must_use]
fn contains_point_using_disambiguated<F>(&self, positionable: P, rule_set: PointContainmentRuleSet, f: F) -> bool
where
F: FnOnce(DisambiguatedPointContainmentPosition) -> bool,
{
self.disambiguated_point_containment_position(positionable, rule_set)
.is_ok_and(f)
}
}
impl CanPositionPointContainment<Timestamp> for AbsBoundPair {
type Error = Infallible;
fn point_containment_position(&self, positionable: Timestamp) -> Result<PointContainmentPosition, Self::Error> {
Ok(point_containment_position_abs_bound_pair(self, positionable))
}
}
impl CanPositionPointContainment<Timestamp> for EmptiableAbsBoundPair {
type Error = Infallible;
fn point_containment_position(&self, positionable: Timestamp) -> Result<PointContainmentPosition, Self::Error> {
let EmptiableAbsBoundPair::Bound(bounds) = self else {
return Ok(PointContainmentPosition::Outside);
};
Ok(point_containment_position_abs_bound_pair(bounds, positionable))
}
}
impl CanPositionPointContainment<Timestamp> for AbsInterval {
type Error = Infallible;
fn point_containment_position(&self, positionable: Timestamp) -> Result<PointContainmentPosition, Self::Error> {
let EmptiableAbsBoundPair::Bound(bounds) = self.emptiable_abs_bound_pair() else {
return Ok(PointContainmentPosition::Outside);
};
Ok(point_containment_position_abs_bound_pair(&bounds, positionable))
}
}
impl CanPositionPointContainment<Timestamp> for BoundedAbsInterval {
type Error = Infallible;
fn point_containment_position(&self, positionable: Timestamp) -> Result<PointContainmentPosition, Self::Error> {
Ok(point_containment_position_abs_bound_pair(
&self.abs_bound_pair(),
positionable,
))
}
}
impl CanPositionPointContainment<Timestamp> for HalfBoundedAbsInterval {
type Error = Infallible;
fn point_containment_position(&self, positionable: Timestamp) -> Result<PointContainmentPosition, Self::Error> {
Ok(point_containment_position_abs_bound_pair(
&self.abs_bound_pair(),
positionable,
))
}
}
impl CanPositionPointContainment<SignedDuration> for RelBoundPair {
type Error = Infallible;
fn point_containment_position(
&self,
positionable: SignedDuration,
) -> Result<PointContainmentPosition, Self::Error> {
Ok(point_containment_position_rel_bound_pair(self, positionable))
}
}
impl CanPositionPointContainment<SignedDuration> for EmptiableRelBoundPair {
type Error = Infallible;
fn point_containment_position(
&self,
positionable: SignedDuration,
) -> Result<PointContainmentPosition, Self::Error> {
let EmptiableRelBoundPair::Bound(bounds) = self else {
return Ok(PointContainmentPosition::Outside);
};
Ok(point_containment_position_rel_bound_pair(bounds, positionable))
}
}
impl CanPositionPointContainment<SignedDuration> for RelInterval {
type Error = Infallible;
fn point_containment_position(
&self,
positionable: SignedDuration,
) -> Result<PointContainmentPosition, Self::Error> {
let EmptiableRelBoundPair::Bound(bounds) = self.emptiable_rel_bound_pair() else {
return Ok(PointContainmentPosition::Outside);
};
Ok(point_containment_position_rel_bound_pair(&bounds, positionable))
}
}
impl CanPositionPointContainment<SignedDuration> for BoundedRelInterval {
type Error = Infallible;
fn point_containment_position(
&self,
positionable: SignedDuration,
) -> Result<PointContainmentPosition, Self::Error> {
Ok(point_containment_position_rel_bound_pair(
&self.rel_bound_pair(),
positionable,
))
}
}
impl CanPositionPointContainment<SignedDuration> for HalfBoundedRelInterval {
type Error = Infallible;
fn point_containment_position(
&self,
positionable: SignedDuration,
) -> Result<PointContainmentPosition, Self::Error> {
Ok(point_containment_position_rel_bound_pair(
&self.rel_bound_pair(),
positionable,
))
}
}
impl CanPositionPointContainment<Timestamp> for UnboundedInterval {
type Error = Infallible;
fn point_containment_position(&self, _positionable: Timestamp) -> Result<PointContainmentPosition, Self::Error> {
Ok(PointContainmentPosition::Inside)
}
}
impl CanPositionPointContainment<SignedDuration> for UnboundedInterval {
type Error = Infallible;
fn point_containment_position(
&self,
_positionable: SignedDuration,
) -> Result<PointContainmentPosition, Self::Error> {
Ok(PointContainmentPosition::Inside)
}
}
impl CanPositionPointContainment<Timestamp> for EmptyInterval {
type Error = Infallible;
fn point_containment_position(&self, _positionable: Timestamp) -> Result<PointContainmentPosition, Self::Error> {
Ok(PointContainmentPosition::Outside)
}
}
impl CanPositionPointContainment<SignedDuration> for EmptyInterval {
type Error = Infallible;
fn point_containment_position(
&self,
_positionable: SignedDuration,
) -> Result<PointContainmentPosition, Self::Error> {
Ok(PointContainmentPosition::Outside)
}
}
#[must_use]
pub fn point_containment_position_abs_bound_pair(bounds: &AbsBoundPair, time: Timestamp) -> PointContainmentPosition {
type StartB = AbsStartBound;
type EndB = AbsEndBound;
type ContPos = PointContainmentPosition;
match (bounds.abs_start(), bounds.abs_end()) {
(StartB::InfinitePast, EndB::InfiniteFuture) => ContPos::Inside,
(StartB::InfinitePast, EndB::Finite(finite_bound_position)) => {
match time.cmp(&finite_bound_position.pos().time()) {
Ordering::Less => ContPos::Inside,
Ordering::Equal => ContPos::OnEnd(finite_bound_position.pos().inclusivity()),
Ordering::Greater => ContPos::OutsideAfter,
}
},
(StartB::Finite(finite_bound_position), EndB::InfiniteFuture) => {
match time.cmp(&finite_bound_position.pos().time()) {
Ordering::Less => ContPos::OutsideBefore,
Ordering::Equal => ContPos::OnStart(finite_bound_position.pos().inclusivity()),
Ordering::Greater => ContPos::Inside,
}
},
(StartB::Finite(start_bound), EndB::Finite(end_bound)) => {
match (time.cmp(&start_bound.pos().time()), time.cmp(&end_bound.pos().time())) {
(Ordering::Less, _) => ContPos::OutsideBefore,
(Ordering::Equal, _) => ContPos::OnStart(start_bound.pos().inclusivity()),
(_, Ordering::Less) => ContPos::Inside,
(_, Ordering::Equal) => ContPos::OnEnd(end_bound.pos().inclusivity()),
(_, Ordering::Greater) => ContPos::OutsideAfter,
}
},
}
}
#[must_use]
pub fn point_containment_position_rel_bound_pair(
bounds: &RelBoundPair,
offset: SignedDuration,
) -> PointContainmentPosition {
type StartB = RelStartBound;
type EndB = RelEndBound;
type ContPos = PointContainmentPosition;
match (bounds.rel_start(), bounds.rel_end()) {
(StartB::InfinitePast, EndB::InfiniteFuture) => ContPos::Inside,
(StartB::InfinitePast, EndB::Finite(finite_bound_position)) => {
match offset.cmp(&finite_bound_position.pos().offset()) {
Ordering::Less => ContPos::Inside,
Ordering::Equal => ContPos::OnEnd(finite_bound_position.pos().inclusivity()),
Ordering::Greater => ContPos::OutsideAfter,
}
},
(StartB::Finite(finite_bound_position), EndB::InfiniteFuture) => {
match offset.cmp(&finite_bound_position.pos().offset()) {
Ordering::Less => ContPos::OutsideBefore,
Ordering::Equal => ContPos::OnStart(finite_bound_position.pos().inclusivity()),
Ordering::Greater => ContPos::Inside,
}
},
(StartB::Finite(start_bound), EndB::Finite(end_bound)) => {
match (
offset.cmp(&start_bound.pos().offset()),
offset.cmp(&end_bound.pos().offset()),
) {
(Ordering::Less, _) => ContPos::OutsideBefore,
(Ordering::Equal, _) => ContPos::OnStart(start_bound.pos().inclusivity()),
(_, Ordering::Less) => ContPos::Inside,
(_, Ordering::Equal) => ContPos::OnEnd(end_bound.pos().inclusivity()),
(_, Ordering::Greater) => ContPos::OutsideAfter,
}
},
}
}