<commit_before>use compact::{CDict, CVec, CHashMap};
use kay::{ID, ActorSystem, Fate, World};
use kay::swarm::{Swarm, SubActor};
use stagemaster::geometry::AnyShape;
use descartes::Band;
use super::lane::{Lane, TransferLane};
use super::connectivity::{Interaction, InteractionKind, OverlapKind};

pub mod trip;

#[derive(Compact, Clone, Default)]
pub struct PathfindingInfo {
    pub as_destination: Option<Destination>,
    pub hops_from_landmark: u8,
    pub learned_landmark_from: Option<ID>,
    pub routes: CHashMap<Destination, RoutingInfo>,
    pub routes_changed: bool,
    pub tell_to_forget_next_tick: CVec<Destination>,
    pub query_routes_next_tick: bool,
    pub routing_timeout: u16,
}

#[derive(Copy, Clone, PartialEq, Eq, Default, Hash, Debug)]
pub struct Destination {
    pub landmark: ID,
    pub node: ID,
}

impl Destination {
    fn landmark(landmark: ID) -> Self {
        Destination { landmark: landmark, node: landmark }
    }
    pub fn is_landmark(&self) -> bool {
        self.landmark == self.node
    }
    pub fn landmark_destination(&self) -> Self {
        Self::landmark(self.landmark)
    }
}

#[derive(Copy, Clone, Default, Debug)]
pub struct RoutingInfo {
    pub outgoing_idx: u8,
    pub distance: f32,
    distance_hops: u8,
    learned_from: ID,
    fresh: bool,
}

use stagemaster::{UserInterface, AddInteractable};
const DEBUG_CARS_ON_LANES: bool = false;

pub fn on_build(lane: &mut Lane, world: &mut World) {
    lane.pathfinding.as_destination = None;
    if DEBUG_CARS_ON_LANES {
        world.send_to_id_of::<UserInterface, _>(
                   AddInteractable(lane.id(),
                                   AnyShape::Band(Band::new(lane.construction.path.clone(), 3.0)),
                                   5));
    }
}

pub fn on_connect(lane: &mut Lane) {
    lane.pathfinding.routing_timeout = ROUTING_TIMEOUT_AFTER_CHANGE;
}

pub fn on_disconnect(lane: &mut Lane, disconnected_id: ID) {
    let new_routes = lane.pathfinding
        .routes
        .pairs()
        .filter_map(|(destination, route)| if route.learned_from == disconnected_id {
                        None
                    } else {
                        Some((*destination, *route))
                    })
        .collect();
    lane.pathfinding.routes = new_routes;
    lane.pathfinding.routes_changed = true;
    lane.pathfinding.query_routes_next_tick = true;
}

const MIN_LANDMARK_INCOMING: usize = 3;
const ROUTING_TIMEOUT_AFTER_CHANGE: u16 = 15;

pub fn tick(lane: &mut Lane, world: &mut World) {
    if let Some(as_destination) = lane.pathfinding.as_destination {
        for successor in successors(lane) {
            world.send(successor,
                       JoinLandmark {
                           from: lane.id(),
                           join_as: Destination {
                               landmark: as_destination.landmark,
                               node: successor,
                           },
                           hops_from_landmark: lane.pathfinding.hops_from_landmark + 1,
                       })
        }
    } else if !lane.connectivity.on_intersection &&
              predecessors(lane).count() >= MIN_LANDMARK_INCOMING {
        lane.pathfinding = PathfindingInfo {
            as_destination: Some(Destination::landmark(lane.id())),
            hops_from_landmark: 0,
            learned_landmark_from: Some(lane.id()),
            routes: CHashMap::new(),
            routes_changed: true,
            query_routes_next_tick: false,
            tell_to_forget_next_tick: CVec::new(),
            routing_timeout: ROUTING_TIMEOUT_AFTER_CHANGE,
        }
    }

    if lane.pathfinding.routing_timeout > 0 {
        lane.pathfinding.routing_timeout -= 1;
    } else {
        if lane.pathfinding.query_routes_next_tick {
            for successor in successors(lane) {
                world.send(successor,
                           QueryRoutes { requester: lane.id(), is_transfer: false });
            }
            lane.pathfinding.query_routes_next_tick = false;
        }

        if !lane.pathfinding.tell_to_forget_next_tick.is_empty() {
            for (_, predecessor, _) in predecessors(lane) {
                world.send(predecessor,
                           ForgetRoutes {
                               forget: lane.pathfinding.tell_to_forget_next_tick.clone(),
                               from: lane.id(),
                           })
            }
            lane.pathfinding.tell_to_forget_next_tick.clear();
        }

        if lane.pathfinding.routes_changed {
            for (_, predecessor, is_transfer) in predecessors(lane) {
                let self_cost = if is_transfer {
                    0.0
                } else {
                    lane.construction.length
                };
                world.send(
                    predecessor,
                    ShareRoutes {
                        new_routes: lane.pathfinding
                            .routes
                            .pairs()
                            .filter_map(|(&destination,
                              &RoutingInfo { distance, distance_hops, .. })| {
                                if true
                                // fresh
                                {
                                    Some((destination, (distance + self_cost, distance_hops + 1)))
                                } else {
                                    None
                                }
                            })
                            .chain(
                                if lane.connectivity.on_intersection {
                                    None
                                } else {
                                    lane.pathfinding
                                        .as_destination
                                        .map(|destination| (destination, (self_cost, 0)))
                                },
                            )
                            .collect(),
                        from: lane.id(),
                    },
                );
            }
            for routing_info in lane.pathfinding.routes.values_mut() {
                routing_info.fresh = false;
            }
            lane.pathfinding.routes_changed = false;
        }
    }
}

#[allow(needless_lifetimes)]
fn successors<'a>(lane: &'a Lane) -> impl Iterator<Item = ID> + 'a {
    lane.connectivity
        .interactions
        .iter()
        .filter_map(|interaction| match *interaction {
                        Interaction {
                            partner_lane,
                            kind: InteractionKind::Overlap { kind: OverlapKind::Transfer, .. },
                            ..
                        } |
                        Interaction {
                            partner_lane,
                            kind: InteractionKind::Next { .. },
                            ..
                        } => Some(partner_lane),
                        _ => None,
                    })
}

#[allow(needless_lifetimes)]
fn predecessors<'a>(lane: &'a Lane) -> impl Iterator<Item = (u8, ID, bool)> + 'a {
    lane.connectivity
        .interactions
        .iter()
        .enumerate()
        .filter_map(|(i, interaction)| match *interaction {
                        Interaction {
                            partner_lane,
                            kind: InteractionKind::Overlap { kind: OverlapKind::Transfer, .. },
                            ..
                        } => Some((i as u8, partner_lane, true)),
                        Interaction {
                            partner_lane,
                            kind: InteractionKind::Previous { .. },
                            ..
                        } => Some((i as u8, partner_lane, false)),
                        _ => None,
                    })
}

#[derive(Copy, Clone)]
pub struct JoinLandmark {
    from: ID,
    join_as: Destination,
    hops_from_landmark: u8,
}

const IDEAL_LANDMARK_RADIUS: u8 = 3;

pub fn setup(system: &mut ActorSystem) {
    system.extend(Swarm::<Lane>::subactors(|mut each_lane| {
        each_lane.on(|&JoinLandmark { join_as, hops_from_landmark, from },
         lane,
         _| {
            let join = lane.pathfinding
                .as_destination
                .map(|self_destination| {
                    join_as != self_destination &&
                    (if self_destination.is_landmark() {
                         hops_from_landmark < IDEAL_LANDMARK_RADIUS &&
                         join_as.landmark.sub_actor_id < lane.id().sub_actor_id
                     } else {
                         hops_from_landmark < lane.pathfinding.hops_from_landmark ||
                         lane.pathfinding
                             .learned_landmark_from
                             .map(|learned_from| learned_from == from)
                             .unwrap_or(false)
                     })
                })
                .unwrap_or(true);
            if join {
                let tell_to_forget_next_tick = lane.pathfinding
                        .routes
                        .keys()
                        .cloned()
                        .chain(lane.pathfinding.as_destination.into_iter())
                        .collect();
                lane.pathfinding = PathfindingInfo {
                    as_destination: Some(join_as),
                    learned_landmark_from: Some(from),
                    hops_from_landmark: hops_from_landmark,
                    routes: CHashMap::new(),
                    routes_changed: true,
                    query_routes_next_tick: true,
                    tell_to_forget_next_tick: tell_to_forget_next_tick,
                    routing_timeout: ROUTING_TIMEOUT_AFTER_CHANGE,
                };
            }
            Fate::Live
        });

        each_lane.on(|&QueryRoutes { requester, is_transfer }, lane, world| {
            let self_cost = if is_transfer {
                0.0
            } else {
                lane.construction.length
            };
            world.send(
                requester,
                ShareRoutes {
                    new_routes: lane.pathfinding
                        .routes
                        .pairs()
                        .map(|(&destination,
                          &RoutingInfo { distance, distance_hops, .. })| {
                            (destination, (distance + self_cost, distance_hops + 1))
                        })
                        .chain(
                            if lane.connectivity.on_intersection {
                                None
                            } else {
                                lane.pathfinding
                                    .as_destination
                                    .map(|destination| (destination, (self_cost, 0)))
                            },
                        )
                        .collect(),
                    from: lane.id(),
                },
            );
            Fate::Live
        });

        each_lane.on(|&ShareRoutes { ref new_routes, from }, lane, _| {
            if let Some(from_interaction_idx) =
                lane.connectivity
                    .interactions
                    .iter()
                    .position(|interaction| interaction.partner_lane == from) {
                for (&destination, &(new_distance, new_distance_hops)) in new_routes.pairs() {
                    if destination.is_landmark() || new_distance_hops <= IDEAL_LANDMARK_RADIUS ||
                       lane.pathfinding
                           .as_destination
                           .map(|self_dest| self_dest.landmark == destination.landmark)
                           .unwrap_or(false) {
                        let insert = lane.pathfinding
                            .routes
                            .get_mru(destination)
                            .map(|&RoutingInfo { distance, .. }| new_distance < distance)
                            .unwrap_or(true);
                        if insert {
                            println!("path finding {:?}", lane.pathfinding.routes.display());
                            lane.pathfinding.routes.insert(destination,
                                                           RoutingInfo {
                                                               distance: new_distance,
                                                               distance_hops: new_distance_hops,
                                                               outgoing_idx: from_interaction_idx as
                                                                             u8,
                                                               learned_from: from,
                                                               fresh: true,
                                                           });
                            lane.pathfinding.routes_changed = true;
                        }
                    }
                }
            } else {
                println!("{:?} not yet connected to {:?}", lane.id(), from);
            }
            Fate::Live
        });

        each_lane.on(|&ForgetRoutes { ref forget, from }, lane, _| {
            let mut forgotten = CVec::<Destination>::new();
            for destination_to_forget in forget.iter() {
                let forget = if let Some(routing_info) =
                    lane.pathfinding.routes.get(*destination_to_forget) {
                    routing_info.learned_from == from
                } else {
                    false
                };
                if forget {
                    lane.pathfinding.routes.remove(*destination_to_forget);
                    if destination_to_forget.is_landmark() {
                        lane.microtraffic.cars.retain(|car| {
                            car.destination.landmark != destination_to_forget.landmark
                        })
                    } else {
                        lane.microtraffic
                            .cars
                            .retain(|car| &car.destination != destination_to_forget)
                    }
                    forgotten.push(*destination_to_forget);
                }
            }
            lane.pathfinding.tell_to_forget_next_tick = forgotten;
            Fate::Live
        });

        each_lane.on(|&QueryAsDestination { requester }, lane, world| {
            world.send(requester,
                       TellAsDestination {
                           id: lane.id(),
                           as_destination: lane.pathfinding.as_destination,
                       });
            Fate::Live
        })
    }));

    system.extend(Swarm::<TransferLane>::subactors(|mut each_t_lane| {
        each_t_lane.on(|&JoinLandmark { join_as, hops_from_landmark, from },
         lane,
         world| {
            world.send(lane.other_side(from),
                       JoinLandmark {
                           join_as: Destination {
                               landmark: join_as.landmark,
                               node: lane.other_side(from),
                           },
                           hops_from_landmark: hops_from_landmark,
                           from: lane.id(),
                       });
            Fate::Live
        });

        each_t_lane.on(|&QueryRoutes { requester, .. }, lane, world| {
            world.send(lane.other_side(requester),
                       QueryRoutes { requester: lane.id(), is_transfer: true });
            Fate::Live
        });

        each_t_lane.on(|&ShareRoutes { ref new_routes, from }, lane, world| {
            world.send(
                lane.other_side(from),
                ShareRoutes {
                    new_routes: new_routes
                        .pairs()
                        .map(|(&destination, &(distance, hops))| {
                            (destination,
                             (distance +
                              if from == lane.connectivity.left.expect("should have left").0 {
                                  LANE_CHANGE_COST_RIGHT
                              } else {
                                  LANE_CHANGE_COST_LEFT
                              },
                              hops))
                        })
                        .collect(),
                    from: lane.id(),
                },
            );
            Fate::Live
        });

        each_t_lane.on(|&ForgetRoutes { ref forget, from }, lane, world| {
            world.send(lane.other_side(from),
                       ForgetRoutes { forget: forget.clone(), from: lane.id() });
            Fate::Live
        })
    }));

    trip::setup(system);
}

#[derive(Copy, Clone)]
pub struct QueryRoutes {
    requester: ID,
    is_transfer: bool,
}

#[derive(Compact, Clone)]
pub struct ShareRoutes {
    new_routes: CDict<Destination, (f32, u8)>,
    from: ID,
}

const LANE_CHANGE_COST_LEFT: f32 = 5.0;
const LANE_CHANGE_COST_RIGHT: f32 = 3.0;

#[derive(Compact, Clone)]
pub struct ForgetRoutes {
    forget: CVec<Destination>,
    from: ID,
}

#[derive(Copy, Clone)]
pub struct QueryAsDestination {
    requester: ID,
}
#[derive(Copy, Clone)]
pub struct TellAsDestination {
    id: ID,
    as_destination: Option<Destination>,
}
<commit_msg>removed debug output<commit_after>use compact::{CDict, CVec, CHashMap};
use kay::{ID, ActorSystem, Fate, World};
use kay::swarm::{Swarm, SubActor};
use stagemaster::geometry::AnyShape;
use descartes::Band;
use super::lane::{Lane, TransferLane};
use super::connectivity::{Interaction, InteractionKind, OverlapKind};

pub mod trip;

#[derive(Compact, Clone, Default)]
pub struct PathfindingInfo {
    pub as_destination: Option<Destination>,
    pub hops_from_landmark: u8,
    pub learned_landmark_from: Option<ID>,
    pub routes: CHashMap<Destination, RoutingInfo>,
    pub routes_changed: bool,
    pub tell_to_forget_next_tick: CVec<Destination>,
    pub query_routes_next_tick: bool,
    pub routing_timeout: u16,
}

#[derive(Copy, Clone, PartialEq, Eq, Default, Hash, Debug)]
pub struct Destination {
    pub landmark: ID,
    pub node: ID,
}

impl Destination {
    fn landmark(landmark: ID) -> Self {
        Destination { landmark: landmark, node: landmark }
    }
    pub fn is_landmark(&self) -> bool {
        self.landmark == self.node
    }
    pub fn landmark_destination(&self) -> Self {
        Self::landmark(self.landmark)
    }
}

#[derive(Copy, Clone, Default, Debug)]
pub struct RoutingInfo {
    pub outgoing_idx: u8,
    pub distance: f32,
    distance_hops: u8,
    learned_from: ID,
    fresh: bool,
}

use stagemaster::{UserInterface, AddInteractable};
const DEBUG_CARS_ON_LANES: bool = false;

pub fn on_build(lane: &mut Lane, world: &mut World) {
    lane.pathfinding.as_destination = None;
    if DEBUG_CARS_ON_LANES {
        world.send_to_id_of::<UserInterface, _>(
                   AddInteractable(lane.id(),
                                   AnyShape::Band(Band::new(lane.construction.path.clone(), 3.0)),
                                   5));
    }
}

pub fn on_connect(lane: &mut Lane) {
    lane.pathfinding.routing_timeout = ROUTING_TIMEOUT_AFTER_CHANGE;
}

pub fn on_disconnect(lane: &mut Lane, disconnected_id: ID) {
    let new_routes = lane.pathfinding
        .routes
        .pairs()
        .filter_map(|(destination, route)| if route.learned_from == disconnected_id {
                        None
                    } else {
                        Some((*destination, *route))
                    })
        .collect();
    lane.pathfinding.routes = new_routes;
    lane.pathfinding.routes_changed = true;
    lane.pathfinding.query_routes_next_tick = true;
}

const MIN_LANDMARK_INCOMING: usize = 3;
const ROUTING_TIMEOUT_AFTER_CHANGE: u16 = 15;

pub fn tick(lane: &mut Lane, world: &mut World) {
    if let Some(as_destination) = lane.pathfinding.as_destination {
        for successor in successors(lane) {
            world.send(successor,
                       JoinLandmark {
                           from: lane.id(),
                           join_as: Destination {
                               landmark: as_destination.landmark,
                               node: successor,
                           },
                           hops_from_landmark: lane.pathfinding.hops_from_landmark + 1,
                       })
        }
    } else if !lane.connectivity.on_intersection &&
              predecessors(lane).count() >= MIN_LANDMARK_INCOMING {
        lane.pathfinding = PathfindingInfo {
            as_destination: Some(Destination::landmark(lane.id())),
            hops_from_landmark: 0,
            learned_landmark_from: Some(lane.id()),
            routes: CHashMap::new(),
            routes_changed: true,
            query_routes_next_tick: false,
            tell_to_forget_next_tick: CVec::new(),
            routing_timeout: ROUTING_TIMEOUT_AFTER_CHANGE,
        }
    }

    if lane.pathfinding.routing_timeout > 0 {
        lane.pathfinding.routing_timeout -= 1;
    } else {
        if lane.pathfinding.query_routes_next_tick {
            for successor in successors(lane) {
                world.send(successor,
                           QueryRoutes { requester: lane.id(), is_transfer: false });
            }
            lane.pathfinding.query_routes_next_tick = false;
        }

        if !lane.pathfinding.tell_to_forget_next_tick.is_empty() {
            for (_, predecessor, _) in predecessors(lane) {
                world.send(predecessor,
                           ForgetRoutes {
                               forget: lane.pathfinding.tell_to_forget_next_tick.clone(),
                               from: lane.id(),
                           })
            }
            lane.pathfinding.tell_to_forget_next_tick.clear();
        }

        if lane.pathfinding.routes_changed {
            for (_, predecessor, is_transfer) in predecessors(lane) {
                let self_cost = if is_transfer {
                    0.0
                } else {
                    lane.construction.length
                };
                world.send(
                    predecessor,
                    ShareRoutes {
                        new_routes: lane.pathfinding
                            .routes
                            .pairs()
                            .filter_map(|(&destination,
                              &RoutingInfo { distance, distance_hops, .. })| {
                                if true
                                // fresh
                                {
                                    Some((destination, (distance + self_cost, distance_hops + 1)))
                                } else {
                                    None
                                }
                            })
                            .chain(
                                if lane.connectivity.on_intersection {
                                    None
                                } else {
                                    lane.pathfinding
                                        .as_destination
                                        .map(|destination| (destination, (self_cost, 0)))
                                },
                            )
                            .collect(),
                        from: lane.id(),
                    },
                );
            }
            for routing_info in lane.pathfinding.routes.values_mut() {
                routing_info.fresh = false;
            }
            lane.pathfinding.routes_changed = false;
        }
    }
}

#[allow(needless_lifetimes)]
fn successors<'a>(lane: &'a Lane) -> impl Iterator<Item = ID> + 'a {
    lane.connectivity
        .interactions
        .iter()
        .filter_map(|interaction| match *interaction {
                        Interaction {
                            partner_lane,
                            kind: InteractionKind::Overlap { kind: OverlapKind::Transfer, .. },
                            ..
                        } |
                        Interaction {
                            partner_lane,
                            kind: InteractionKind::Next { .. },
                            ..
                        } => Some(partner_lane),
                        _ => None,
                    })
}

#[allow(needless_lifetimes)]
fn predecessors<'a>(lane: &'a Lane) -> impl Iterator<Item = (u8, ID, bool)> + 'a {
    lane.connectivity
        .interactions
        .iter()
        .enumerate()
        .filter_map(|(i, interaction)| match *interaction {
                        Interaction {
                            partner_lane,
                            kind: InteractionKind::Overlap { kind: OverlapKind::Transfer, .. },
                            ..
                        } => Some((i as u8, partner_lane, true)),
                        Interaction {
                            partner_lane,
                            kind: InteractionKind::Previous { .. },
                            ..
                        } => Some((i as u8, partner_lane, false)),
                        _ => None,
                    })
}

#[derive(Copy, Clone)]
pub struct JoinLandmark {
    from: ID,
    join_as: Destination,
    hops_from_landmark: u8,
}

const IDEAL_LANDMARK_RADIUS: u8 = 3;

pub fn setup(system: &mut ActorSystem) {
    system.extend(Swarm::<Lane>::subactors(|mut each_lane| {
        each_lane.on(|&JoinLandmark { join_as, hops_from_landmark, from },
         lane,
         _| {
            let join = lane.pathfinding
                .as_destination
                .map(|self_destination| {
                    join_as != self_destination &&
                    (if self_destination.is_landmark() {
                         hops_from_landmark < IDEAL_LANDMARK_RADIUS &&
                         join_as.landmark.sub_actor_id < lane.id().sub_actor_id
                     } else {
                         hops_from_landmark < lane.pathfinding.hops_from_landmark ||
                         lane.pathfinding
                             .learned_landmark_from
                             .map(|learned_from| learned_from == from)
                             .unwrap_or(false)
                     })
                })
                .unwrap_or(true);
            if join {
                let tell_to_forget_next_tick = lane.pathfinding
                        .routes
                        .keys()
                        .cloned()
                        .chain(lane.pathfinding.as_destination.into_iter())
                        .collect();
                lane.pathfinding = PathfindingInfo {
                    as_destination: Some(join_as),
                    learned_landmark_from: Some(from),
                    hops_from_landmark: hops_from_landmark,
                    routes: CHashMap::new(),
                    routes_changed: true,
                    query_routes_next_tick: true,
                    tell_to_forget_next_tick: tell_to_forget_next_tick,
                    routing_timeout: ROUTING_TIMEOUT_AFTER_CHANGE,
                };
            }
            Fate::Live
        });

        each_lane.on(|&QueryRoutes { requester, is_transfer }, lane, world| {
            let self_cost = if is_transfer {
                0.0
            } else {
                lane.construction.length
            };
            world.send(
                requester,
                ShareRoutes {
                    new_routes: lane.pathfinding
                        .routes
                        .pairs()
                        .map(|(&destination,
                          &RoutingInfo { distance, distance_hops, .. })| {
                            (destination, (distance + self_cost, distance_hops + 1))
                        })
                        .chain(
                            if lane.connectivity.on_intersection {
                                None
                            } else {
                                lane.pathfinding
                                    .as_destination
                                    .map(|destination| (destination, (self_cost, 0)))
                            },
                        )
                        .collect(),
                    from: lane.id(),
                },
            );
            Fate::Live
        });

        each_lane.on(|&ShareRoutes { ref new_routes, from }, lane, _| {
            if let Some(from_interaction_idx) =
                lane.connectivity
                    .interactions
                    .iter()
                    .position(|interaction| interaction.partner_lane == from) {
                for (&destination, &(new_distance, new_distance_hops)) in new_routes.pairs() {
                    if destination.is_landmark() || new_distance_hops <= IDEAL_LANDMARK_RADIUS ||
                       lane.pathfinding
                           .as_destination
                           .map(|self_dest| self_dest.landmark == destination.landmark)
                           .unwrap_or(false) {
                        let insert = lane.pathfinding
                            .routes
                            .get_mru(destination)
                            .map(|&RoutingInfo { distance, .. }| new_distance < distance)
                            .unwrap_or(true);
                        if insert {
                            lane.pathfinding.routes.insert(destination,
                                                           RoutingInfo {
                                                               distance: new_distance,
                                                               distance_hops: new_distance_hops,
                                                               outgoing_idx: from_interaction_idx as
                                                                             u8,
                                                               learned_from: from,
                                                               fresh: true,
                                                           });
                            lane.pathfinding.routes_changed = true;
                        }
                    }
                }
            } else {
                println!("{:?} not yet connected to {:?}", lane.id(), from);
            }
            Fate::Live
        });

        each_lane.on(|&ForgetRoutes { ref forget, from }, lane, _| {
            let mut forgotten = CVec::<Destination>::new();
            for destination_to_forget in forget.iter() {
                let forget = if let Some(routing_info) =
                    lane.pathfinding.routes.get(*destination_to_forget) {
                    routing_info.learned_from == from
                } else {
                    false
                };
                if forget {
                    lane.pathfinding.routes.remove(*destination_to_forget);
                    if destination_to_forget.is_landmark() {
                        lane.microtraffic.cars.retain(|car| {
                            car.destination.landmark != destination_to_forget.landmark
                        })
                    } else {
                        lane.microtraffic
                            .cars
                            .retain(|car| &car.destination != destination_to_forget)
                    }
                    forgotten.push(*destination_to_forget);
                }
            }
            lane.pathfinding.tell_to_forget_next_tick = forgotten;
            Fate::Live
        });

        each_lane.on(|&QueryAsDestination { requester }, lane, world| {
            world.send(requester,
                       TellAsDestination {
                           id: lane.id(),
                           as_destination: lane.pathfinding.as_destination,
                       });
            Fate::Live
        })
    }));

    system.extend(Swarm::<TransferLane>::subactors(|mut each_t_lane| {
        each_t_lane.on(|&JoinLandmark { join_as, hops_from_landmark, from },
         lane,
         world| {
            world.send(lane.other_side(from),
                       JoinLandmark {
                           join_as: Destination {
                               landmark: join_as.landmark,
                               node: lane.other_side(from),
                           },
                           hops_from_landmark: hops_from_landmark,
                           from: lane.id(),
                       });
            Fate::Live
        });

        each_t_lane.on(|&QueryRoutes { requester, .. }, lane, world| {
            world.send(lane.other_side(requester),
                       QueryRoutes { requester: lane.id(), is_transfer: true });
            Fate::Live
        });

        each_t_lane.on(|&ShareRoutes { ref new_routes, from }, lane, world| {
            world.send(
                lane.other_side(from),
                ShareRoutes {
                    new_routes: new_routes
                        .pairs()
                        .map(|(&destination, &(distance, hops))| {
                            (destination,
                             (distance +
                              if from == lane.connectivity.left.expect("should have left").0 {
                                  LANE_CHANGE_COST_RIGHT
                              } else {
                                  LANE_CHANGE_COST_LEFT
                              },
                              hops))
                        })
                        .collect(),
                    from: lane.id(),
                },
            );
            Fate::Live
        });

        each_t_lane.on(|&ForgetRoutes { ref forget, from }, lane, world| {
            world.send(lane.other_side(from),
                       ForgetRoutes { forget: forget.clone(), from: lane.id() });
            Fate::Live
        })
    }));

    trip::setup(system);
}

#[derive(Copy, Clone)]
pub struct QueryRoutes {
    requester: ID,
    is_transfer: bool,
}

#[derive(Compact, Clone)]
pub struct ShareRoutes {
    new_routes: CDict<Destination, (f32, u8)>,
    from: ID,
}

const LANE_CHANGE_COST_LEFT: f32 = 5.0;
const LANE_CHANGE_COST_RIGHT: f32 = 3.0;

#[derive(Compact, Clone)]
pub struct ForgetRoutes {
    forget: CVec<Destination>,
    from: ID,
}

#[derive(Copy, Clone)]
pub struct QueryAsDestination {
    requester: ID,
}
#[derive(Copy, Clone)]
pub struct TellAsDestination {
    id: ID,
    as_destination: Option<Destination>,
}
<|endoftext|>


<commit_before>

#[cfg(windows)]
mod mmap {
    use libc::{CreateFileW, CreateFileMappingW, MapViewOfFile, CloseHandle, UnmapViewOfFile,
               GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, INVALID_HANDLE_VALUE, PAGE_READONLY,
               FILE_MAP_READ, HANDLE, TRUE};
    use libc::consts::os::extra::{FILE_FLAG_RANDOM_ACCESS};
    use libc::types::os::arch::extra::{LPCVOID};
    use std::ptr;
    struct Handle {
        hand: HANDLE,
    }
    impl Drop for Handle {
        fn drop(&mut self) {
            assert_eq!(unsafe { CloseHandle(self.hand) }, TRUE);
        }
    }
    struct MapView {
        data: LPCVOID,
    }
    impl Drop for MapView {
        fn drop(&mut self) {
            assert_eq!(unsafe { UnmapViewOfFile(self.data) }, TRUE);
        }
    }
    pub struct MapFile {
        view: MapView,
        map: Handle,
        file: Handle,
    }
    impl MapFile {
        pub fn data(&self) -> LPCVOID { self.view.data }
    }
    fn create_file(path: &Path) -> Result<Handle, &'static str> {
        let name = path.as_str().unwrap().to_utf16().append([0]);
        let file = unsafe {
            CreateFileW(name.as_ptr(), GENERIC_READ, FILE_SHARE_READ, ptr::mut_null(),
                        OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, ptr::mut_null())
        };
        if file.to_uint() == INVALID_HANDLE_VALUE as uint { return Err("Failed to open file"); }
        Ok(Handle{hand: file})
    }
    fn create_mapping(file: &Handle) -> Result<Handle, &'static str> {
        let map = unsafe {
            CreateFileMappingW(file.hand, ptr::mut_null(), PAGE_READONLY, 0, 0, ptr::null())
        };
        if map.is_null() { return Err("Failed to create file mapping"); }
        Ok(Handle{hand: map})
    }
    fn map_view(map: &Handle) -> Result<MapView, &'static str> {
        let view = unsafe { MapViewOfFile(map.hand, FILE_MAP_READ, 0, 0, 0) };
        if view.is_null() { return Err("Failed to map view of file"); }
        Ok(MapView{data: view as *_})
    }
    pub fn open(path: &Path) -> Result<MapFile, &'static str> {
        let file = try!(create_file(path));
        let map = try!(create_mapping(&file));
        let view = try!(map_view(&map));
        Ok(MapFile{file: file, map: map, view: view})
    }
}
#[cfg(unix)]
mod mmap {
    use libc::{open, close, fstat, mmap, O_RDONLY, PROT_READ, MAP_SHARED, stat, c_int};
    use std::mem::{uninitialized, transmute};
    use std::ptr;
    use std::io::fs::stat;
    struct Handle {
        hand: c_int,
    }
    impl Drop for Handle {
        fn drop(&mut self) {
            assert_eq!(unsafe { close(self.hand) }, 0);
        }
    }
    struct Mapping {
        data: *mut c_void,
        size: u64,
    }
    impl Drop for Mapping {
        fn drop(&mut self) {
            assert_eq!(unsafe { munmap(self.data, self.size) }, 0);
        }
    }
    struct MapFile {
        map: Mapping,
        file: Handle,
    }
    impl MapFile {
        pub fn data(&self) -> * c_void { self.map.data as *_ }
    }
    fn open_file(path: &Path) -> Result<Handle, &'static str> {
        let name = path.to_c_str();
        let handle = open(name.unwrap(), O_RDONLY, 0);
        if (handle == -1) { return Err("Failed to open file") }
        Ok(Handle{hand: handle})
    }
    fn file_size(path: &Path) -> Result<u64, &'static str> {
        let stat = try!(path.stat());
        Ok(stat.size);
    }
    fn map_file(file: &Handle, size: u64) -> Result<Mapping, &'static str> {
        let map = mmap(ptr::null(), size as u64, PROT_READ, MAP_SHARED, file.hand, 0);
        if map.to_uint() == -1 { return Err("Failed to map file"); }
        Ok(Mapping{data: map, size: size})
    }
    pub fn open(path: &Path) -> Result<MapFile, &'static str> {
        let file = try!(open_file(path));
        let size = try!(file_size(path));
        let map = try!(map_file(file, size));
        Ok(MapFile{file: file, map: map})
    }
}
pub struct File {
    map: mmap::MapFile,
    data: *u8,
    header: *Header,
    nodetable: *NodeData,
    stringtable: *u64,
}
impl File {
    pub fn open(path: &Path) -> Result<File, &'static str> {
        use std::mem::transmute;
        let map = try!(mmap::open(path));
        let data = map.data();
        let header: *Header = unsafe{ transmute(data) };
        if unsafe { (*header).magic } != 0x34474B50 {
            return Err("Not a valid NX PKG4 file");
        }
        unsafe {
            Ok(File{map: map, data: transmute(data), header: header,
                    nodetable: transmute(data.offset((*header).nodeoffset as int)),
                    stringtable: transmute(data.offset((*header).stringoffset as int))})
        }
    }
    fn header(&self) -> &Header {
        use std::mem::transmute;
        unsafe { transmute(self.header) }
    }
    pub fn root<'a>(&'a self) -> Node<'a> {
        unsafe { Node{data: &*self.nodetable, file: self,} }
    }
    fn get_str<'a>(&'a self, index: u32) -> Option<&'a str> {
        use std::str;
        use std::slice::raw;
        use std::mem::transmute;
        unsafe {
            let off = *self.stringtable.offset(index as int);
            let ptr = self.data.offset(off as int);
            let size: *u16 = transmute(ptr);
            raw::buf_as_slice(ptr.offset(2), (*size) as uint, |buf| {
                let bytes: &'a [u8] = transmute(buf);
                str::from_utf8(bytes)
            })
        }
    }
}
#[packed]
struct Header {
    magic: u32,
    nodecount: u32,
    nodeoffset: u64,
    stringcount: u32,
    stringoffset: u64,
    bitmapcount: u32,
    bitmapoffset: u64,
    audiocount: u32,
    audiooffset: u64,
}
#[packed]
struct String {
    length: u16,
}
pub struct Node<'a> {
    data: &'a NodeData,
    file: &'a File,
}
impl <'a> Node<'a> {
    pub fn iter(&self) -> NodeIterator<'a> {
        unsafe {
            let data = self.file.nodetable.offset(self.data.children as int);
            NodeIterator{data: data, count: self.data.count, file: self.file,}
        }
    }
    pub fn name(&self) -> Option<&'a str> { self.file.get_str(self.data.name) }
    pub fn empty(&self) -> bool { self.data.count == 0 }
}
struct NodeIterator<'a> {
    data: *NodeData,
    count: u16,
    file: &'a File,
}
impl <'a> Iterator<Node<'a>> for NodeIterator<'a> {
    fn next(&mut self) -> Option<Node<'a>> {
        match self.count {
            0 => None,
            _ => {
                self.count -= 1;
                let node = Node{data: unsafe { &*self.data }, file: self.file,};
                self.data = unsafe { self.data.offset(1) };
                Some(node)
            }
        }
    }
}
#[packed]
struct NodeData {
    name: u32,
    children: u32,
    count: u16,
    dtype: u16,
    data: u64,
}
#[packed]
struct NodeInteger {
    value: i64,
}
#[packed]
struct NodeFloat {
    value: f64,
}
#[packed]
struct NodeString {
    index: u32,
    unused: u32,
}
#[packed]
struct NodeVector {
    x: i32,
    y: i32,
}
#[packed]
struct NodeBitmap {
    index: u32,
    width: u16,
    height: u16,
}
#[packed]
struct NodeAudio {
    index: u32,
    length: u32,
}
<commit_msg>nx::mmap[unix]: Fix up use imports<commit_after>

#[cfg(windows)]
mod mmap {
    use libc::{CreateFileW, CreateFileMappingW, MapViewOfFile, CloseHandle, UnmapViewOfFile,
               GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, INVALID_HANDLE_VALUE, PAGE_READONLY,
               FILE_MAP_READ, HANDLE, TRUE};
    use libc::consts::os::extra::{FILE_FLAG_RANDOM_ACCESS};
    use libc::types::os::arch::extra::{LPCVOID};
    use std::ptr;
    struct Handle {
        hand: HANDLE,
    }
    impl Drop for Handle {
        fn drop(&mut self) {
            assert_eq!(unsafe { CloseHandle(self.hand) }, TRUE);
        }
    }
    struct MapView {
        data: LPCVOID,
    }
    impl Drop for MapView {
        fn drop(&mut self) {
            assert_eq!(unsafe { UnmapViewOfFile(self.data) }, TRUE);
        }
    }
    pub struct MapFile {
        view: MapView,
        map: Handle,
        file: Handle,
    }
    impl MapFile {
        pub fn data(&self) -> LPCVOID { self.view.data }
    }
    fn create_file(path: &Path) -> Result<Handle, &'static str> {
        let name = path.as_str().unwrap().to_utf16().append([0]);
        let file = unsafe {
            CreateFileW(name.as_ptr(), GENERIC_READ, FILE_SHARE_READ, ptr::mut_null(),
                        OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, ptr::mut_null())
        };
        if file.to_uint() == INVALID_HANDLE_VALUE as uint { return Err("Failed to open file"); }
        Ok(Handle{hand: file})
    }
    fn create_mapping(file: &Handle) -> Result<Handle, &'static str> {
        let map = unsafe {
            CreateFileMappingW(file.hand, ptr::mut_null(), PAGE_READONLY, 0, 0, ptr::null())
        };
        if map.is_null() { return Err("Failed to create file mapping"); }
        Ok(Handle{hand: map})
    }
    fn map_view(map: &Handle) -> Result<MapView, &'static str> {
        let view = unsafe { MapViewOfFile(map.hand, FILE_MAP_READ, 0, 0, 0) };
        if view.is_null() { return Err("Failed to map view of file"); }
        Ok(MapView{data: view as *_})
    }
    pub fn open(path: &Path) -> Result<MapFile, &'static str> {
        let file = try!(create_file(path));
        let map = try!(create_mapping(&file));
        let view = try!(map_view(&map));
        Ok(MapFile{file: file, map: map, view: view})
    }
}
#[cfg(unix)]
mod mmap {
    use libc::{close, fstat, mmap, munmap, O_RDONLY, PROT_READ, stat, c_int, c_void};
    use libc::consts::os::posix88::{MAP_SHARED};
    use posix_open = libc::open;
    use std::mem::{uninitialized, transmute};
    use std::ptr;
    use std::io::fs::stat;
    struct Handle {
        hand: c_int,
    }
    impl Drop for Handle {
        fn drop(&mut self) {
            assert_eq!(unsafe { close(self.hand) }, 0);
        }
    }
    struct Mapping {
        data: *mut c_void,
        size: u64,
    }
    impl Drop for Mapping {
        fn drop(&mut self) {
            assert_eq!(unsafe { munmap(self.data, self.size) }, 0);
        }
    }
    struct MapFile {
        map: Mapping,
        file: Handle,
    }
    impl MapFile {
        pub fn data(&self) -> * c_void { self.map.data as *_ }
    }
    fn open_file(path: &Path) -> Result<Handle, &'static str> {
        let name = path.to_c_str();
        let handle = posix_open(name.unwrap(), O_RDONLY, 0);
        if (handle == -1) { return Err("Failed to open file") }
        Ok(Handle{hand: handle})
    }
    fn file_size(path: &Path) -> Result<u64, &'static str> {
        let stat = try!(path.stat());
        Ok(stat.size);
    }
    fn map_file(file: &Handle, size: u64) -> Result<Mapping, &'static str> {
        let map = mmap(ptr::null(), size as u64, PROT_READ, MAP_SHARED, file.hand, 0);
        if map.to_uint() == -1 { return Err("Failed to map file"); }
        Ok(Mapping{data: map, size: size})
    }
    pub fn open(path: &Path) -> Result<MapFile, &'static str> {
        let file = try!(open_file(path));
        let size = try!(file_size(path));
        let map = try!(map_file(file, size));
        Ok(MapFile{file: file, map: map})
    }
}
pub struct File {
    map: mmap::MapFile,
    data: *u8,
    header: *Header,
    nodetable: *NodeData,
    stringtable: *u64,
}
impl File {
    pub fn open(path: &Path) -> Result<File, &'static str> {
        use std::mem::transmute;
        let map = try!(mmap::open(path));
        let data = map.data();
        let header: *Header = unsafe{ transmute(data) };
        if unsafe { (*header).magic } != 0x34474B50 {
            return Err("Not a valid NX PKG4 file");
        }
        unsafe {
            Ok(File{map: map, data: transmute(data), header: header,
                    nodetable: transmute(data.offset((*header).nodeoffset as int)),
                    stringtable: transmute(data.offset((*header).stringoffset as int))})
        }
    }
    fn header(&self) -> &Header {
        use std::mem::transmute;
        unsafe { transmute(self.header) }
    }
    pub fn root<'a>(&'a self) -> Node<'a> {
        unsafe { Node{data: &*self.nodetable, file: self,} }
    }
    fn get_str<'a>(&'a self, index: u32) -> Option<&'a str> {
        use std::str;
        use std::slice::raw;
        use std::mem::transmute;
        unsafe {
            let off = *self.stringtable.offset(index as int);
            let ptr = self.data.offset(off as int);
            let size: *u16 = transmute(ptr);
            raw::buf_as_slice(ptr.offset(2), (*size) as uint, |buf| {
                let bytes: &'a [u8] = transmute(buf);
                str::from_utf8(bytes)
            })
        }
    }
}
#[packed]
struct Header {
    magic: u32,
    nodecount: u32,
    nodeoffset: u64,
    stringcount: u32,
    stringoffset: u64,
    bitmapcount: u32,
    bitmapoffset: u64,
    audiocount: u32,
    audiooffset: u64,
}
#[packed]
struct String {
    length: u16,
}
pub struct Node<'a> {
    data: &'a NodeData,
    file: &'a File,
}
impl <'a> Node<'a> {
    pub fn iter(&self) -> NodeIterator<'a> {
        unsafe {
            let data = self.file.nodetable.offset(self.data.children as int);
            NodeIterator{data: data, count: self.data.count, file: self.file,}
        }
    }
    pub fn name(&self) -> Option<&'a str> { self.file.get_str(self.data.name) }
    pub fn empty(&self) -> bool { self.data.count == 0 }
}
struct NodeIterator<'a> {
    data: *NodeData,
    count: u16,
    file: &'a File,
}
impl <'a> Iterator<Node<'a>> for NodeIterator<'a> {
    fn next(&mut self) -> Option<Node<'a>> {
        match self.count {
            0 => None,
            _ => {
                self.count -= 1;
                let node = Node{data: unsafe { &*self.data }, file: self.file,};
                self.data = unsafe { self.data.offset(1) };
                Some(node)
            }
        }
    }
}
#[packed]
struct NodeData {
    name: u32,
    children: u32,
    count: u16,
    dtype: u16,
    data: u64,
}
#[packed]
struct NodeInteger {
    value: i64,
}
#[packed]
struct NodeFloat {
    value: f64,
}
#[packed]
struct NodeString {
    index: u32,
    unused: u32,
}
#[packed]
struct NodeVector {
    x: i32,
    y: i32,
}
#[packed]
struct NodeBitmap {
    index: u32,
    width: u16,
    height: u16,
}
#[packed]
struct NodeAudio {
    index: u32,
    length: u32,
}
<|endoftext|>


<commit_before>#![feature(plugin_registrar, quote, rustc_private)]

extern crate syntax;
extern crate rustc;

use syntax::codemap::Span;
use syntax::parse::token;
use syntax::ast::{TokenTree, Ident};
use syntax::ext::base::{ExtCtxt, MacResult, DummyResult, MacEager, IdentTT, get_single_str_from_tts};
use syntax::util::small_vector::SmallVector;
use rustc::plugin::Registry;

fn expand_mod_path<'a>(cx: &'a mut ExtCtxt, sp: Span, ident: Ident, tts: Vec<TokenTree>)
            -> Box<MacResult + 'a> {
    let path = match get_single_str_from_tts(cx, sp, &*tts, "mod_path!") {
        Some(string) => string,
        None => return DummyResult::expr(sp),
    };
    let path = &*path;

    MacEager::items(SmallVector::one(quote_item!(cx,

        #[path = $path]
        pub mod $ident;

    ).unwrap()))
}

#[plugin_registrar]
pub fn plugin_registrar(reg: &mut Registry) {
    reg.register_syntax_extension(token::intern("mod_path"), IdentTT(Box::new(expand_mod_path), None, false));
}
<commit_msg>Update mod_path for Rust nightly<commit_after>#![feature(plugin_registrar, quote, rustc_private)]

extern crate syntax;
extern crate rustc;
extern crate rustc_plugin;

use syntax::codemap::Span;
use syntax::parse::token;
use syntax::ast::{TokenTree, Ident};
use syntax::ext::base::{ExtCtxt, MacResult, DummyResult, MacEager, IdentTT, get_single_str_from_tts};
use syntax::util::small_vector::SmallVector;
use rustc_plugin::Registry;

fn expand_mod_path<'a>(cx: &'a mut ExtCtxt, sp: Span, ident: Ident, tts: Vec<TokenTree>)
            -> Box<MacResult + 'a> {
    let path = match get_single_str_from_tts(cx, sp, &*tts, "mod_path!") {
        Some(string) => string,
        None => return DummyResult::expr(sp),
    };
    let path = &*path;

    MacEager::items(SmallVector::one(quote_item!(cx,

        #[path = $path]
        pub mod $ident;

    ).unwrap()))
}

#[plugin_registrar]
pub fn plugin_registrar(reg: &mut Registry) {
    reg.register_syntax_extension(token::intern("mod_path"), IdentTT(Box::new(expand_mod_path), None, false));
}
<|endoftext|>


<commit_before>use std::fmt::{Debug};

pub trait TruthValue : Clone + PartialEq + Debug {
    type Dual: Clone + PartialEq + Debug;
    fn default() -> Self;
    fn dual_default() -> Self::Dual;
    fn parse(&str) -> Option<(Self, &str)>;
    fn parse_dual(&str) -> Option<(Self::Dual, &str)>;
    fn merge(a: &Self, b: &Self) -> Self;
    fn finalize(dual: &Self::Dual, a: &Self) -> Self;
    fn join(a: &Self, b: &Self) -> Self;
    fn as_datalog(&self) -> String;
    fn dual_as_datalog(dual: &Self::Dual) -> String;
}

impl TruthValue for () {
    type Dual = ();
    fn default() -> Self {}
    fn dual_default() -> Self::Dual {}
    fn join(_: &Self, _: &Self) -> Self {}
    fn merge(_: &Self, _: &Self) -> Self {}
    fn finalize(_: &Self::Dual, _: &Self) -> Self {}
    fn parse(_: &str) -> Option<(Self, &str)> { None }
    fn parse_dual(_: &str) -> Option<(Self::Dual, &str)> { None }
    fn as_datalog(&self) -> String {
        return "".to_owned();
    }
    fn dual_as_datalog(_: &Self::Dual) -> String {
        return "".to_owned();
    }
}

#[derive(Copy, Clone, PartialEq, Debug)]
pub struct MaxFloat64(f64);

impl TruthValue for MaxFloat64 {
    type Dual = f64;

    fn default() -> Self {
        MaxFloat64(1.0)
    }

    fn dual_default() -> Self::Dual {
        1.0
    }

    fn join(a: &Self, b: &Self) -> Self {
        MaxFloat64(f64::max(a.0, b.0))
    }

    fn merge(a: &Self, b: &Self) -> Self {
        MaxFloat64(f64::min(a.0, b.0))
    }

    fn finalize(dual: &Self::Dual, a: &Self) -> Self {
        MaxFloat64(dual * a.0)
    }

    fn parse(src: &str) -> Option<(Self, &str)> {
        Self::parse_dual(src).map(|(f, rest)| (MaxFloat64(f), rest))
    }

    fn parse_dual(src: &str) -> Option<(Self::Dual, &str)> {
        if let Some(end) = src.find(')') {
            use std::str::FromStr;
            let (f_src, rest) = src.split_at(end);
            if let Ok(f) = f64::from_str(f_src) {
                return Some((f, rest));
            }
        }
        return None;
    }

    fn as_datalog(&self) -> String {
        return format!("confidence({}) ", self.0);
    }

    fn dual_as_datalog(dual: &Self::Dual) -> String {
        return format!("weight({}) ", dual);
    }
}
<commit_msg>Added back propagation like interface to TruthValue.<commit_after>use std::fmt::{Debug};

pub trait TruthValue : Clone + PartialEq + Debug {
    type Dual: Clone + PartialEq + Debug;
    fn default() -> Self;
    fn dual_default() -> Self::Dual;
    fn parse(&str) -> Option<(Self, &str)>;
    fn parse_dual(&str) -> Option<(Self::Dual, &str)>;
    fn join(a: &Self, b: &Self) -> Self;
    fn back_join(a: &Self, b: &Self, err: &Self) -> (Self, Self);
    fn merge(a: &Self, b: &Self) -> Self;
    fn back_merge(a: &Self, b: &Self, err: &Self) -> (Self, Self);
    fn finalize(dual: &Self::Dual, a: &Self) -> Self;
    fn back_finalize(dual: &Self::Dual, a: &Self, err: &Self) -> Self;
    fn as_datalog(&self) -> String;
    fn dual_as_datalog(dual: &Self::Dual) -> String;
}

impl TruthValue for () {
    type Dual = ();
    fn default() -> Self {}
    fn dual_default() -> Self::Dual {}
    fn join(_: &Self, _: &Self) -> Self {}
    fn back_join(_: &Self, _: &Self, _: &Self) -> (Self, Self) { ((), ()) }
    fn merge(_: &Self, _: &Self) -> Self {}
    fn back_merge(_: &Self, _: &Self, _: &Self) -> (Self, Self) { ((), ()) }
    fn finalize(_: &Self::Dual, _: &Self) -> Self {}
    fn back_finalize(_: &Self::Dual, _: &Self, _: &Self) -> Self {}
    fn parse(_: &str) -> Option<(Self, &str)> { None }
    fn parse_dual(_: &str) -> Option<(Self::Dual, &str)> { None }
    fn as_datalog(&self) -> String {
        return "".to_owned();
    }
    fn dual_as_datalog(_: &Self::Dual) -> String {
        return "".to_owned();
    }
}

#[derive(Copy, Clone, PartialEq, Debug)]
pub struct MaxFloat64(f64);

impl TruthValue for MaxFloat64 {
    type Dual = f64;

    fn default() -> Self {
        MaxFloat64(1.0)
    }

    fn dual_default() -> Self::Dual {
        1.0
    }

    fn join(a: &Self, b: &Self) -> Self {
        MaxFloat64(f64::max(a.0, b.0))
    }

    fn back_join(a: &Self, b: &Self, err: &Self) -> (Self, Self) {
        if a.0 >= b.0 {
            (*err, MaxFloat64(0.0))
        } else {
            (MaxFloat64(0.0), *err)
        }
    }

    fn merge(a: &Self, b: &Self) -> Self {
        MaxFloat64(f64::min(a.0, b.0))
    }

    fn back_merge(a: &Self, b: &Self, err: &Self) -> (Self, Self) {
        if a.0 <= b.0 {
            (*err, MaxFloat64(0.0))
        } else {
            (MaxFloat64(0.0), *err)
        }
    }

    fn finalize(dual: &Self::Dual, a: &Self) -> Self {
        MaxFloat64(dual * a.0)
    }

    fn back_finalize(dual: &Self::Dual, _a: &Self, err: &Self) -> Self {
        MaxFloat64(err.0 / dual)
    }

    fn parse(src: &str) -> Option<(Self, &str)> {
        Self::parse_dual(src).map(|(f, rest)| (MaxFloat64(f), rest))
    }

    fn parse_dual(src: &str) -> Option<(Self::Dual, &str)> {
        if let Some(end) = src.find(')') {
            use std::str::FromStr;
            let (f_src, rest) = src.split_at(end);
            if let Ok(f) = f64::from_str(f_src) {
                return Some((f, rest));
            }
        }
        return None;
    }

    fn as_datalog(&self) -> String {
        return format!("confidence({}) ", self.0);
    }

    fn dual_as_datalog(dual: &Self::Dual) -> String {
        return format!("weight({}) ", dual);
    }
}
<|endoftext|>


<commit_before>//! Common types for the [push notifications module][push]
//!
//! [push]: https://matrix.org/docs/spec/client_server/r0.6.1#id89

use std::fmt::{self, Formatter};

use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::value::RawValue as RawJsonValue;

pub use room_member_count_is::{ComparisonOperator, RoomMemberCountIs};

mod room_member_count_is;
mod tweak_serde;

/// A push ruleset scopes a set of rules according to some criteria.
///
/// For example, some rules may only be applied for messages from a particular sender, a particular
/// room, or by default. The push ruleset contains the entire set of scopes and rules.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Ruleset {
    /// These rules configure behavior for (unencrypted) messages that match certain patterns.
    pub content: Vec<PatternedPushRule>,

    /// These user-configured rules are given the highest priority.
    ///
    /// This field is named `override_` instead of `override` because the latter is a reserved
    /// keyword in Rust.
    #[serde(rename = "override")]
    pub override_: Vec<ConditionalPushRule>,

    /// These rules change the behavior of all messages for a given room.
    pub room: Vec<PushRule>,

    /// These rules configure notification behavior for messages from a specific Matrix user ID.
    pub sender: Vec<PushRule>,

    /// These rules are identical to override rules, but have a lower priority than `content`,
    /// `room` and `sender` rules.
    pub underride: Vec<ConditionalPushRule>,
}

/// A push rule is a single rule that states under what conditions an event should be passed onto a
/// push gateway and how the notification should be presented.
///
/// These rules are stored on the user's homeserver. They are manually configured by the user, who
/// can create and view them via the Client/Server API.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct PushRule {
    /// Actions to determine if and how a notification is delivered for events matching this rule.
    pub actions: Vec<Action>,

    /// Whether this is a default rule, or has been set explicitly.
    pub default: bool,

    /// Whether the push rule is enabled or not.
    pub enabled: bool,

    /// The ID of this rule.
    pub rule_id: String,
}

/// Like `PushRule`, but with an additional `conditions` field.
///
/// Only applicable to underride and override rules.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ConditionalPushRule {
    /// Actions to determine if and how a notification is delivered for events matching this rule.
    pub actions: Vec<Action>,

    /// Whether this is a default rule, or has been set explicitly.
    pub default: bool,

    /// Whether the push rule is enabled or not.
    pub enabled: bool,

    /// The ID of this rule.
    pub rule_id: String,

    /// The conditions that must hold true for an event in order for a rule to be applied to an event.
    ///
    /// A rule with no conditions always matches.
    pub conditions: Vec<PushCondition>,
}

/// Like `PushRule`, but with an additional `pattern` field.
///
/// Only applicable to content rules.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct PatternedPushRule {
    /// Actions to determine if and how a notification is delivered for events matching this rule.
    pub actions: Vec<Action>,

    /// Whether this is a default rule, or has been set explicitly.
    pub default: bool,

    /// Whether the push rule is enabled or not.
    pub enabled: bool,

    /// The ID of this rule.
    pub rule_id: String,

    /// The glob-style pattern to match against.
    pub pattern: String,
}

/// A push rule
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AnyPushRule {
    /// The actions to perform when this rule is matched.
    pub actions: Vec<Action>,

    /// Whether this is a default rule, or has been set explicitly.
    pub default: bool,

    /// Whether the push rule is enabled or not.
    pub enabled: bool,

    /// The ID of this rule.
    pub rule_id: String,

    /// The conditions that must hold true for an event in order for a rule to be applied to an event. A rule with no conditions always matches.
    /// Only applicable to underride and override rules.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub conditions: Option<Vec<PushCondition>>,

    /// The glob-style pattern to match against. Only applicable to content rules.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub pattern: Option<String>,
}

/// This represents the different actions that should be taken when a rule is matched, and
/// controls how notifications are delivered to the client.
///
/// See https://matrix.org/docs/spec/client_server/r0.6.0#actions for details.
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum Action {
    /// Causes matching events to generate a notification.
    Notify,

    /// Prevents matching events from generating a notification.
    DontNotify,

    /// Behaves like notify but homeservers may choose to coalesce multiple events
    /// into a single notification.
    Coalesce,

    /// Sets an entry in the 'tweaks' dictionary sent to the push gateway.
    SetTweak(Tweak),
}

/// The `set_tweak` action.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(from = "tweak_serde::Tweak", into = "tweak_serde::Tweak")]
pub enum Tweak {
    /// A string representing the sound to be played when this notification arrives.
    ///
    /// A value of "default" means to play a default sound. A device may choose to alert the user by
    /// some other means if appropriate, eg. vibration.
    Sound(String),

    /// A boolean representing whether or not this message should be highlighted in the UI.
    ///
    /// This will normally take the form of presenting the message in a different color and/or
    /// style. The UI might also be adjusted to draw particular attention to the room in which the
    /// event occurred. If a `highlight` tweak is given with no value, its value is defined to be
    /// `true`. If no highlight tweak is given at all then the value of `highlight` is defined to be
    /// `false`.
    Highlight(#[serde(default = "ruma_serde::default_true")] bool),

    /// A custom tweak
    Custom {
        /// The name of the custom tweak (`set_tweak` field)
        name: String,

        /// The value of the custom tweak
        value: Box<RawJsonValue>,
    },
}

impl<'de> Deserialize<'de> for Action {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        use serde::de::{MapAccess, Visitor};

        struct ActionVisitor;
        impl<'de> Visitor<'de> for ActionVisitor {
            type Value = Action;

            fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
                write!(formatter, "a valid action object")
            }

            /// Match a simple action type
            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
            where
                E: serde::de::Error,
            {
                match v {
                    "notify" => Ok(Action::Notify),
                    "dont_notify" => Ok(Action::DontNotify),
                    "coalesce" => Ok(Action::Coalesce),
                    s => Err(E::unknown_variant(&s, &["notify", "dont_notify", "coalesce"])),
                }
            }

            /// Match the more complex set_tweaks action object as a key-value map
            fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
            where
                A: MapAccess<'de>,
            {
                Tweak::deserialize(serde::de::value::MapAccessDeserializer::new(map))
                    .map(Action::SetTweak)
            }
        }

        deserializer.deserialize_any(ActionVisitor)
    }
}

impl Serialize for Action {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match self {
            Action::Notify => serializer.serialize_unit_variant("Action", 0, "notify"),
            Action::DontNotify => serializer.serialize_unit_variant("Action", 1, "dont_notify"),
            Action::Coalesce => serializer.serialize_unit_variant("Action", 2, "coalesce"),
            Action::SetTweak(kind) => kind.serialize(serializer),
        }
    }
}

/// A condition that must apply for an associated push rule's action to be taken.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum PushCondition {
    /// This is a glob pattern match on a field of the event.
    EventMatch {
        /// The dot-separated field of the event to match.
        key: String,

        /// The glob-style pattern to match against.
        ///
        /// Patterns with no special glob characters should be treated as having asterisks prepended
        /// and appended when testing the condition.
        pattern: String,
    },

    /// This matches unencrypted messages where `content.body` contains the owner's display name in
    /// that room.
    ContainsDisplayName,

    /// This matches the current number of members in the room.
    RoomMemberCount {
        /// The condition on the current number of members in the room.
        is: RoomMemberCountIs,
    },

    /// This takes into account the current power levels in the room, ensuring the sender of the
    /// event has high enough power to trigger the notification.
    SenderNotificationPermission {
        /// The field in the power level event the user needs a minimum power level for.
        ///
        /// Fields must be specified under the `notifications` property in the power level event's
        /// `content`.
        key: String,
    },
}

#[cfg(test)]
mod tests {
    use js_int::uint;
    use matches::assert_matches;
    use serde_json::{from_value as from_json_value, json, to_value as to_json_value};

    use super::{Action, PushCondition, RoomMemberCountIs, Tweak};

    #[test]
    fn serialize_string_action() {
        assert_eq!(to_json_value(&Action::Notify).unwrap(), json!("notify"));
    }

    #[test]
    fn serialize_tweak_sound_action() {
        assert_eq!(
            to_json_value(&Action::SetTweak(Tweak::Sound("default".into()))).unwrap(),
            json!({ "set_tweak": "sound", "value": "default" })
        );
    }

    #[test]
    fn serialize_tweak_highlight_action() {
        assert_eq!(
            to_json_value(&Action::SetTweak(Tweak::Highlight(true))).unwrap(),
            json!({ "set_tweak": "highlight" })
        );

        assert_eq!(
            to_json_value(&Action::SetTweak(Tweak::Highlight(false))).unwrap(),
            json!({ "set_tweak": "highlight", "value": false })
        );
    }

    #[test]
    fn deserialize_string_action() {
        assert_matches!(from_json_value::<Action>(json!("notify")).unwrap(), Action::Notify);
    }

    #[test]
    fn deserialize_tweak_sound_action() {
        let json_data = json!({
            "set_tweak": "sound",
            "value": "default"
        });
        assert_matches!(
            &from_json_value::<Action>(json_data).unwrap(),
            Action::SetTweak(Tweak::Sound(value)) if value == "default"
        );
    }

    #[test]
    fn deserialize_tweak_highlight_action() {
        let json_data = json!({
            "set_tweak": "highlight",
            "value": true
        });
        assert_matches!(
            from_json_value::<Action>(json_data).unwrap(),
            Action::SetTweak(Tweak::Highlight(true))
        );
    }

    #[test]
    fn deserialize_tweak_highlight_action_with_default_value() {
        assert_matches!(
            from_json_value::<Action>(json!({ "set_tweak": "highlight" })).unwrap(),
            Action::SetTweak(Tweak::Highlight(true))
        );
    }

    #[test]
    fn serialize_event_match_condition() {
        let json_data = json!({
            "key": "content.msgtype",
            "kind": "event_match",
            "pattern": "m.notice"
        });
        assert_eq!(
            to_json_value(&PushCondition::EventMatch {
                key: "content.msgtype".into(),
                pattern: "m.notice".into(),
            })
            .unwrap(),
            json_data
        );
    }

    #[test]
    fn serialize_contains_display_name_condition() {
        assert_eq!(
            to_json_value(&PushCondition::ContainsDisplayName).unwrap(),
            json!({ "kind": "contains_display_name" })
        );
    }

    #[test]
    fn serialize_room_member_count_condition() {
        let json_data = json!({
            "is": "2",
            "kind": "room_member_count"
        });
        assert_eq!(
            to_json_value(&PushCondition::RoomMemberCount {
                is: RoomMemberCountIs::from(uint!(2))
            })
            .unwrap(),
            json_data
        );
    }

    #[test]
    fn serialize_sender_notification_permission_condition() {
        let json_data = json!({
            "key": "room",
            "kind": "sender_notification_permission"
        });
        assert_eq!(
            json_data,
            to_json_value(&PushCondition::SenderNotificationPermission { key: "room".into() })
                .unwrap()
        );
    }

    #[test]
    fn deserialize_event_match_condition() {
        let json_data = json!({
            "key": "content.msgtype",
            "kind": "event_match",
            "pattern": "m.notice"
        });
        assert_matches!(
            from_json_value::<PushCondition>(json_data).unwrap(),
            PushCondition::EventMatch { key, pattern }
            if key == "content.msgtype" && pattern == "m.notice"
        );
    }

    #[test]
    fn deserialize_contains_display_name_condition() {
        assert_matches!(
            from_json_value::<PushCondition>(json!({ "kind": "contains_display_name" })).unwrap(),
            PushCondition::ContainsDisplayName
        );
    }

    #[test]
    fn deserialize_room_member_count_condition() {
        let json_data = json!({
            "is": "2",
            "kind": "room_member_count"
        });
        assert_matches!(
            from_json_value::<PushCondition>(json_data).unwrap(),
            PushCondition::RoomMemberCount { is }
            if is == RoomMemberCountIs::from(uint!(2))
        );
    }

    #[test]
    fn deserialize_sender_notification_permission_condition() {
        let json_data = json!({
            "key": "room",
            "kind": "sender_notification_permission"
        });
        assert_matches!(
            from_json_value::<PushCondition>(json_data).unwrap(),
            PushCondition::SenderNotificationPermission {
                key
            } if key == "room"
        );
    }
}
<commit_msg>Implement From and TryFrom for AnyPushRule<commit_after>//! Common types for the [push notifications module][push]
//!
//! [push]: https://matrix.org/docs/spec/client_server/r0.6.1#id89

use std::{
    convert::TryFrom,
    fmt::{self, Formatter},
};

use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::value::RawValue as RawJsonValue;

pub use room_member_count_is::{ComparisonOperator, RoomMemberCountIs};

mod room_member_count_is;
mod tweak_serde;

/// A push ruleset scopes a set of rules according to some criteria.
///
/// For example, some rules may only be applied for messages from a particular sender, a particular
/// room, or by default. The push ruleset contains the entire set of scopes and rules.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Ruleset {
    /// These rules configure behavior for (unencrypted) messages that match certain patterns.
    pub content: Vec<PatternedPushRule>,

    /// These user-configured rules are given the highest priority.
    ///
    /// This field is named `override_` instead of `override` because the latter is a reserved
    /// keyword in Rust.
    #[serde(rename = "override")]
    pub override_: Vec<ConditionalPushRule>,

    /// These rules change the behavior of all messages for a given room.
    pub room: Vec<PushRule>,

    /// These rules configure notification behavior for messages from a specific Matrix user ID.
    pub sender: Vec<PushRule>,

    /// These rules are identical to override rules, but have a lower priority than `content`,
    /// `room` and `sender` rules.
    pub underride: Vec<ConditionalPushRule>,
}

/// A push rule is a single rule that states under what conditions an event should be passed onto a
/// push gateway and how the notification should be presented.
///
/// These rules are stored on the user's homeserver. They are manually configured by the user, who
/// can create and view them via the Client/Server API.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct PushRule {
    /// Actions to determine if and how a notification is delivered for events matching this rule.
    pub actions: Vec<Action>,

    /// Whether this is a default rule, or has been set explicitly.
    pub default: bool,

    /// Whether the push rule is enabled or not.
    pub enabled: bool,

    /// The ID of this rule.
    pub rule_id: String,
}

/// Like `PushRule`, but with an additional `conditions` field.
///
/// Only applicable to underride and override rules.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ConditionalPushRule {
    /// Actions to determine if and how a notification is delivered for events matching this rule.
    pub actions: Vec<Action>,

    /// Whether this is a default rule, or has been set explicitly.
    pub default: bool,

    /// Whether the push rule is enabled or not.
    pub enabled: bool,

    /// The ID of this rule.
    pub rule_id: String,

    /// The conditions that must hold true for an event in order for a rule to be applied to an event.
    ///
    /// A rule with no conditions always matches.
    pub conditions: Vec<PushCondition>,
}

/// Like `PushRule`, but with an additional `pattern` field.
///
/// Only applicable to content rules.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct PatternedPushRule {
    /// Actions to determine if and how a notification is delivered for events matching this rule.
    pub actions: Vec<Action>,

    /// Whether this is a default rule, or has been set explicitly.
    pub default: bool,

    /// Whether the push rule is enabled or not.
    pub enabled: bool,

    /// The ID of this rule.
    pub rule_id: String,

    /// The glob-style pattern to match against.
    pub pattern: String,
}

/// A push rule
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AnyPushRule {
    /// The actions to perform when this rule is matched.
    pub actions: Vec<Action>,

    /// Whether this is a default rule, or has been set explicitly.
    pub default: bool,

    /// Whether the push rule is enabled or not.
    pub enabled: bool,

    /// The ID of this rule.
    pub rule_id: String,

    /// The conditions that must hold true for an event in order for a rule to be applied to an event. A rule with no conditions always matches.
    /// Only applicable to underride and override rules.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub conditions: Option<Vec<PushCondition>>,

    /// The glob-style pattern to match against. Only applicable to content rules.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub pattern: Option<String>,
}

impl From<PushRule> for AnyPushRule {
    fn from(push_rule: PushRule) -> Self {
        let PushRule { actions, default, enabled, rule_id } = push_rule;
        AnyPushRule { actions, default, enabled, rule_id, pattern: None, conditions: None }
    }
}

impl From<PatternedPushRule> for AnyPushRule {
    fn from(push_rule: PatternedPushRule) -> Self {
        let PatternedPushRule { actions, default, enabled, rule_id, pattern } = push_rule;
        AnyPushRule { actions, default, enabled, rule_id, pattern: Some(pattern), conditions: None }
    }
}

impl From<ConditionalPushRule> for AnyPushRule {
    fn from(push_rule: ConditionalPushRule) -> Self {
        let ConditionalPushRule { actions, default, enabled, rule_id, conditions } = push_rule;
        AnyPushRule {
            actions,
            default,
            enabled,
            rule_id,
            pattern: None,
            conditions: Some(conditions),
        }
    }
}

impl From<AnyPushRule> for PushRule {
    fn from(push_rule: AnyPushRule) -> Self {
        let AnyPushRule { actions, default, enabled, rule_id, .. } = push_rule;
        PushRule { actions, default, enabled, rule_id }
    }
}

/// An error that happens when `AnyPushRule` cannot be converted
/// into a more specific push rule type.
#[derive(Debug)]
pub struct AnyPushRuleConversionError;

impl TryFrom<AnyPushRule> for PatternedPushRule {
    type Error = AnyPushRuleConversionError;

    fn try_from(push_rule: AnyPushRule) -> Result<Self, Self::Error> {
        if let AnyPushRule { actions, default, enabled, rule_id, pattern: Some(pattern), .. } =
            push_rule
        {
            Ok(PatternedPushRule { actions, default, enabled, rule_id, pattern })
        } else {
            Err(AnyPushRuleConversionError)
        }
    }
}

impl TryFrom<AnyPushRule> for ConditionalPushRule {
    type Error = AnyPushRuleConversionError;

    fn try_from(push_rule: AnyPushRule) -> Result<Self, Self::Error> {
        if let AnyPushRule {
            actions,
            default,
            enabled,
            rule_id,
            conditions: Some(conditions),
            ..
        } = push_rule
        {
            Ok(ConditionalPushRule { actions, default, enabled, rule_id, conditions })
        } else {
            Err(AnyPushRuleConversionError)
        }
    }
}

/// This represents the different actions that should be taken when a rule is matched, and
/// controls how notifications are delivered to the client.
///
/// See https://matrix.org/docs/spec/client_server/r0.6.0#actions for details.
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum Action {
    /// Causes matching events to generate a notification.
    Notify,

    /// Prevents matching events from generating a notification.
    DontNotify,

    /// Behaves like notify but homeservers may choose to coalesce multiple events
    /// into a single notification.
    Coalesce,

    /// Sets an entry in the 'tweaks' dictionary sent to the push gateway.
    SetTweak(Tweak),
}

/// The `set_tweak` action.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(from = "tweak_serde::Tweak", into = "tweak_serde::Tweak")]
pub enum Tweak {
    /// A string representing the sound to be played when this notification arrives.
    ///
    /// A value of "default" means to play a default sound. A device may choose to alert the user by
    /// some other means if appropriate, eg. vibration.
    Sound(String),

    /// A boolean representing whether or not this message should be highlighted in the UI.
    ///
    /// This will normally take the form of presenting the message in a different color and/or
    /// style. The UI might also be adjusted to draw particular attention to the room in which the
    /// event occurred. If a `highlight` tweak is given with no value, its value is defined to be
    /// `true`. If no highlight tweak is given at all then the value of `highlight` is defined to be
    /// `false`.
    Highlight(#[serde(default = "ruma_serde::default_true")] bool),

    /// A custom tweak
    Custom {
        /// The name of the custom tweak (`set_tweak` field)
        name: String,

        /// The value of the custom tweak
        value: Box<RawJsonValue>,
    },
}

impl<'de> Deserialize<'de> for Action {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        use serde::de::{MapAccess, Visitor};

        struct ActionVisitor;
        impl<'de> Visitor<'de> for ActionVisitor {
            type Value = Action;

            fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
                write!(formatter, "a valid action object")
            }

            /// Match a simple action type
            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
            where
                E: serde::de::Error,
            {
                match v {
                    "notify" => Ok(Action::Notify),
                    "dont_notify" => Ok(Action::DontNotify),
                    "coalesce" => Ok(Action::Coalesce),
                    s => Err(E::unknown_variant(&s, &["notify", "dont_notify", "coalesce"])),
                }
            }

            /// Match the more complex set_tweaks action object as a key-value map
            fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
            where
                A: MapAccess<'de>,
            {
                Tweak::deserialize(serde::de::value::MapAccessDeserializer::new(map))
                    .map(Action::SetTweak)
            }
        }

        deserializer.deserialize_any(ActionVisitor)
    }
}

impl Serialize for Action {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match self {
            Action::Notify => serializer.serialize_unit_variant("Action", 0, "notify"),
            Action::DontNotify => serializer.serialize_unit_variant("Action", 1, "dont_notify"),
            Action::Coalesce => serializer.serialize_unit_variant("Action", 2, "coalesce"),
            Action::SetTweak(kind) => kind.serialize(serializer),
        }
    }
}

/// A condition that must apply for an associated push rule's action to be taken.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum PushCondition {
    /// This is a glob pattern match on a field of the event.
    EventMatch {
        /// The dot-separated field of the event to match.
        key: String,

        /// The glob-style pattern to match against.
        ///
        /// Patterns with no special glob characters should be treated as having asterisks prepended
        /// and appended when testing the condition.
        pattern: String,
    },

    /// This matches unencrypted messages where `content.body` contains the owner's display name in
    /// that room.
    ContainsDisplayName,

    /// This matches the current number of members in the room.
    RoomMemberCount {
        /// The condition on the current number of members in the room.
        is: RoomMemberCountIs,
    },

    /// This takes into account the current power levels in the room, ensuring the sender of the
    /// event has high enough power to trigger the notification.
    SenderNotificationPermission {
        /// The field in the power level event the user needs a minimum power level for.
        ///
        /// Fields must be specified under the `notifications` property in the power level event's
        /// `content`.
        key: String,
    },
}

#[cfg(test)]
mod tests {
    use js_int::uint;
    use matches::assert_matches;
    use serde_json::{from_value as from_json_value, json, to_value as to_json_value};

    use super::{Action, PushCondition, RoomMemberCountIs, Tweak};

    #[test]
    fn serialize_string_action() {
        assert_eq!(to_json_value(&Action::Notify).unwrap(), json!("notify"));
    }

    #[test]
    fn serialize_tweak_sound_action() {
        assert_eq!(
            to_json_value(&Action::SetTweak(Tweak::Sound("default".into()))).unwrap(),
            json!({ "set_tweak": "sound", "value": "default" })
        );
    }

    #[test]
    fn serialize_tweak_highlight_action() {
        assert_eq!(
            to_json_value(&Action::SetTweak(Tweak::Highlight(true))).unwrap(),
            json!({ "set_tweak": "highlight" })
        );

        assert_eq!(
            to_json_value(&Action::SetTweak(Tweak::Highlight(false))).unwrap(),
            json!({ "set_tweak": "highlight", "value": false })
        );
    }

    #[test]
    fn deserialize_string_action() {
        assert_matches!(from_json_value::<Action>(json!("notify")).unwrap(), Action::Notify);
    }

    #[test]
    fn deserialize_tweak_sound_action() {
        let json_data = json!({
            "set_tweak": "sound",
            "value": "default"
        });
        assert_matches!(
            &from_json_value::<Action>(json_data).unwrap(),
            Action::SetTweak(Tweak::Sound(value)) if value == "default"
        );
    }

    #[test]
    fn deserialize_tweak_highlight_action() {
        let json_data = json!({
            "set_tweak": "highlight",
            "value": true
        });
        assert_matches!(
            from_json_value::<Action>(json_data).unwrap(),
            Action::SetTweak(Tweak::Highlight(true))
        );
    }

    #[test]
    fn deserialize_tweak_highlight_action_with_default_value() {
        assert_matches!(
            from_json_value::<Action>(json!({ "set_tweak": "highlight" })).unwrap(),
            Action::SetTweak(Tweak::Highlight(true))
        );
    }

    #[test]
    fn serialize_event_match_condition() {
        let json_data = json!({
            "key": "content.msgtype",
            "kind": "event_match",
            "pattern": "m.notice"
        });
        assert_eq!(
            to_json_value(&PushCondition::EventMatch {
                key: "content.msgtype".into(),
                pattern: "m.notice".into(),
            })
            .unwrap(),
            json_data
        );
    }

    #[test]
    fn serialize_contains_display_name_condition() {
        assert_eq!(
            to_json_value(&PushCondition::ContainsDisplayName).unwrap(),
            json!({ "kind": "contains_display_name" })
        );
    }

    #[test]
    fn serialize_room_member_count_condition() {
        let json_data = json!({
            "is": "2",
            "kind": "room_member_count"
        });
        assert_eq!(
            to_json_value(&PushCondition::RoomMemberCount {
                is: RoomMemberCountIs::from(uint!(2))
            })
            .unwrap(),
            json_data
        );
    }

    #[test]
    fn serialize_sender_notification_permission_condition() {
        let json_data = json!({
            "key": "room",
            "kind": "sender_notification_permission"
        });
        assert_eq!(
            json_data,
            to_json_value(&PushCondition::SenderNotificationPermission { key: "room".into() })
                .unwrap()
        );
    }

    #[test]
    fn deserialize_event_match_condition() {
        let json_data = json!({
            "key": "content.msgtype",
            "kind": "event_match",
            "pattern": "m.notice"
        });
        assert_matches!(
            from_json_value::<PushCondition>(json_data).unwrap(),
            PushCondition::EventMatch { key, pattern }
            if key == "content.msgtype" && pattern == "m.notice"
        );
    }

    #[test]
    fn deserialize_contains_display_name_condition() {
        assert_matches!(
            from_json_value::<PushCondition>(json!({ "kind": "contains_display_name" })).unwrap(),
            PushCondition::ContainsDisplayName
        );
    }

    #[test]
    fn deserialize_room_member_count_condition() {
        let json_data = json!({
            "is": "2",
            "kind": "room_member_count"
        });
        assert_matches!(
            from_json_value::<PushCondition>(json_data).unwrap(),
            PushCondition::RoomMemberCount { is }
            if is == RoomMemberCountIs::from(uint!(2))
        );
    }

    #[test]
    fn deserialize_sender_notification_permission_condition() {
        let json_data = json!({
            "key": "room",
            "kind": "sender_notification_permission"
        });
        assert_matches!(
            from_json_value::<PushCondition>(json_data).unwrap(),
            PushCondition::SenderNotificationPermission {
                key
            } if key == "room"
        );
    }
}
<|endoftext|>