use crate::{IntoPatterns, Resource, ResourceDef};
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct ResourceId(pub u16);
pub struct Router<T, U = ()> {
    routes: Vec<(ResourceDef, T, U)>,
}
impl<T, U> Router<T, U> {
    pub fn build() -> RouterBuilder<T, U> {
        RouterBuilder { routes: Vec::new() }
    }
    pub fn recognize<R>(&self, resource: &mut R) -> Option<(&T, ResourceId)>
    where
        R: Resource,
    {
        self.recognize_fn(resource, |_, _| true)
    }
    pub fn recognize_mut<R>(&mut self, resource: &mut R) -> Option<(&mut T, ResourceId)>
    where
        R: Resource,
    {
        self.recognize_mut_fn(resource, |_, _| true)
    }
    pub fn recognize_fn<R, F>(&self, resource: &mut R, mut check: F) -> Option<(&T, ResourceId)>
    where
        R: Resource,
        F: FnMut(&R, &U) -> bool,
    {
        for (rdef, val, ctx) in self.routes.iter() {
            if rdef.capture_match_info_fn(resource, |res| check(res, ctx)) {
                return Some((val, ResourceId(rdef.id())));
            }
        }
        None
    }
    pub fn recognize_mut_fn<R, F>(
        &mut self,
        resource: &mut R,
        mut check: F,
    ) -> Option<(&mut T, ResourceId)>
    where
        R: Resource,
        F: FnMut(&R, &U) -> bool,
    {
        for (rdef, val, ctx) in self.routes.iter_mut() {
            if rdef.capture_match_info_fn(resource, |res| check(res, ctx)) {
                return Some((val, ResourceId(rdef.id())));
            }
        }
        None
    }
}
pub struct RouterBuilder<T, U = ()> {
    routes: Vec<(ResourceDef, T, U)>,
}
impl<T, U> RouterBuilder<T, U> {
    pub fn push(
        &mut self,
        rdef: ResourceDef,
        val: T,
        ctx: U,
    ) -> (&mut ResourceDef, &mut T, &mut U) {
        self.routes.push((rdef, val, ctx));
        #[allow(clippy::map_identity)] self.routes
            .last_mut()
            .map(|(rdef, val, ctx)| (rdef, val, ctx))
            .unwrap()
    }
    pub fn finish(self) -> Router<T, U> {
        Router {
            routes: self.routes,
        }
    }
}
impl<T, U> RouterBuilder<T, U>
where
    U: Default,
{
    pub fn path(&mut self, path: impl IntoPatterns, val: T) -> (&mut ResourceDef, &mut T, &mut U) {
        self.push(ResourceDef::new(path), val, U::default())
    }
    pub fn prefix(
        &mut self,
        prefix: impl IntoPatterns,
        val: T,
    ) -> (&mut ResourceDef, &mut T, &mut U) {
        self.push(ResourceDef::prefix(prefix), val, U::default())
    }
    pub fn rdef(&mut self, rdef: ResourceDef, val: T) -> (&mut ResourceDef, &mut T, &mut U) {
        self.push(rdef, val, U::default())
    }
}
#[cfg(test)]
mod tests {
    use crate::{
        path::Path,
        router::{ResourceId, Router},
    };
    #[allow(clippy::cognitive_complexity)]
    #[test]
    fn test_recognizer_1() {
        let mut router = Router::<usize>::build();
        router.path("/name", 10).0.set_id(0);
        router.path("/name/{val}", 11).0.set_id(1);
        router.path("/name/{val}/index.html", 12).0.set_id(2);
        router.path("/file/{file}.{ext}", 13).0.set_id(3);
        router.path("/v{val}/{val2}/index.html", 14).0.set_id(4);
        router.path("/v/{tail:.*}", 15).0.set_id(5);
        router.path("/test2/{test}.html", 16).0.set_id(6);
        router.path("/{test}/index.html", 17).0.set_id(7);
        let mut router = router.finish();
        let mut path = Path::new("/unknown");
        assert!(router.recognize_mut(&mut path).is_none());
        let mut path = Path::new("/name");
        let (h, info) = router.recognize_mut(&mut path).unwrap();
        assert_eq!(*h, 10);
        assert_eq!(info, ResourceId(0));
        assert!(path.is_empty());
        let mut path = Path::new("/name/value");
        let (h, info) = router.recognize_mut(&mut path).unwrap();
        assert_eq!(*h, 11);
        assert_eq!(info, ResourceId(1));
        assert_eq!(path.get("val").unwrap(), "value");
        assert_eq!(&path["val"], "value");
        let mut path = Path::new("/name/value2/index.html");
        let (h, info) = router.recognize_mut(&mut path).unwrap();
        assert_eq!(*h, 12);
        assert_eq!(info, ResourceId(2));
        assert_eq!(path.get("val").unwrap(), "value2");
        let mut path = Path::new("/file/file.gz");
        let (h, info) = router.recognize_mut(&mut path).unwrap();
        assert_eq!(*h, 13);
        assert_eq!(info, ResourceId(3));
        assert_eq!(path.get("file").unwrap(), "file");
        assert_eq!(path.get("ext").unwrap(), "gz");
        let mut path = Path::new("/v2/ttt/index.html");
        let (h, info) = router.recognize_mut(&mut path).unwrap();
        assert_eq!(*h, 14);
        assert_eq!(info, ResourceId(4));
        assert_eq!(path.get("val").unwrap(), "2");
        assert_eq!(path.get("val2").unwrap(), "ttt");
        let mut path = Path::new("/v/blah-blah/index.html");
        let (h, info) = router.recognize_mut(&mut path).unwrap();
        assert_eq!(*h, 15);
        assert_eq!(info, ResourceId(5));
        assert_eq!(path.get("tail").unwrap(), "blah-blah/index.html");
        let mut path = Path::new("/test2/index.html");
        let (h, info) = router.recognize_mut(&mut path).unwrap();
        assert_eq!(*h, 16);
        assert_eq!(info, ResourceId(6));
        assert_eq!(path.get("test").unwrap(), "index");
        let mut path = Path::new("/bbb/index.html");
        let (h, info) = router.recognize_mut(&mut path).unwrap();
        assert_eq!(*h, 17);
        assert_eq!(info, ResourceId(7));
        assert_eq!(path.get("test").unwrap(), "bbb");
    }
    #[test]
    fn test_recognizer_2() {
        let mut router = Router::<usize>::build();
        router.path("/index.json", 10);
        router.path("/{source}.json", 11);
        let mut router = router.finish();
        let mut path = Path::new("/index.json");
        let (h, _) = router.recognize_mut(&mut path).unwrap();
        assert_eq!(*h, 10);
        let mut path = Path::new("/test.json");
        let (h, _) = router.recognize_mut(&mut path).unwrap();
        assert_eq!(*h, 11);
    }
    #[test]
    fn test_recognizer_with_prefix() {
        let mut router = Router::<usize>::build();
        router.path("/name", 10).0.set_id(0);
        router.path("/name/{val}", 11).0.set_id(1);
        let mut router = router.finish();
        let mut path = Path::new("/name");
        path.skip(5);
        assert!(router.recognize_mut(&mut path).is_none());
        let mut path = Path::new("/test/name");
        path.skip(5);
        let (h, _) = router.recognize_mut(&mut path).unwrap();
        assert_eq!(*h, 10);
        let mut path = Path::new("/test/name/value");
        path.skip(5);
        let (h, id) = router.recognize_mut(&mut path).unwrap();
        assert_eq!(*h, 11);
        assert_eq!(id, ResourceId(1));
        assert_eq!(path.get("val").unwrap(), "value");
        assert_eq!(&path["val"], "value");
        let mut router = Router::<usize>::build();
        router.path("/name", 10);
        router.path("/name/{val}", 11);
        let mut router = router.finish();
        let mut path = Path::new("/name");
        path.skip(6);
        assert!(router.recognize_mut(&mut path).is_none());
        let mut path = Path::new("/test2/name");
        path.skip(6);
        let (h, _) = router.recognize_mut(&mut path).unwrap();
        assert_eq!(*h, 10);
        let mut path = Path::new("/test2/name-test");
        path.skip(6);
        assert!(router.recognize_mut(&mut path).is_none());
        let mut path = Path::new("/test2/name/ttt");
        path.skip(6);
        let (h, _) = router.recognize_mut(&mut path).unwrap();
        assert_eq!(*h, 11);
        assert_eq!(&path["val"], "ttt");
    }
}