use crate::{Uri, WitNode, WitValue};

pub trait WitValueExtractor<'a, 'b> {
    fn u8(&'a self) -> Option<u8>;
    fn u16(&'a self) -> Option<u16>;
    fn u32(&'a self) -> Option<u32>;
    fn u64(&'a self) -> Option<u64>;
    fn s8(&'a self) -> Option<i8>;
    fn s16(&'a self) -> Option<i16>;
    fn s32(&'a self) -> Option<i32>;
    fn s64(&'a self) -> Option<i64>;
    fn f32(&'a self) -> Option<f32>;
    fn f64(&'a self) -> Option<f64>;
    fn char(&'a self) -> Option<char>;
    fn bool(&'a self) -> Option<bool>;
    fn string(&'a self) -> Option<&'b str>;
    fn field(&'a self, field_idx: usize) -> Option<WitNodePointer<'b>>;
    fn variant(&'a self) -> Option<(u32, Option<WitNodePointer<'b>>)>;
    fn enum_value(&'a self) -> Option<u32>;
    fn flags(&'a self) -> Option<&'b [bool]>;
    fn tuple_element(&'a self, element_idx: usize) -> Option<WitNodePointer<'b>>;
    fn list_elements<R>(&'a self, f: impl Fn(WitNodePointer<'b>) -> R) -> Option<Vec<R>>;
    fn option(&'a self) -> Option<Option<WitNodePointer<'b>>>;
    fn result(&'a self) -> Option<Result<Option<WitNodePointer<'b>>, Option<WitNodePointer<'b>>>>;

    fn handle(&'a self) -> Option<(Uri, u64)>;
}

impl<'a: 'b, 'b> WitValueExtractor<'a, 'b> for WitValue {
    fn u8(&self) -> Option<u8> {
        WitNodePointer::new(self, 0).u8()
    }

    fn u16(&self) -> Option<u16> {
        WitNodePointer::new(self, 0).u16()
    }

    fn u32(&self) -> Option<u32> {
        WitNodePointer::new(self, 0).u32()
    }

    fn u64(&self) -> Option<u64> {
        WitNodePointer::new(self, 0).u64()
    }

    fn s8(&self) -> Option<i8> {
        WitNodePointer::new(self, 0).s8()
    }

    fn s16(&self) -> Option<i16> {
        WitNodePointer::new(self, 0).s16()
    }

    fn s32(&self) -> Option<i32> {
        WitNodePointer::new(self, 0).s32()
    }

    fn s64(&self) -> Option<i64> {
        WitNodePointer::new(self, 0).s64()
    }

    fn f32(&self) -> Option<f32> {
        WitNodePointer::new(self, 0).f32()
    }

    fn f64(&self) -> Option<f64> {
        WitNodePointer::new(self, 0).f64()
    }

    fn char(&self) -> Option<char> {
        WitNodePointer::new(self, 0).char()
    }

    fn bool(&self) -> Option<bool> {
        WitNodePointer::new(self, 0).bool()
    }

    fn string(&'a self) -> Option<&'b str> {
        let ptr = WitNodePointer::<'a>::new(self, 0);
        ptr.string()
    }

    fn field(&'a self, field_idx: usize) -> Option<WitNodePointer<'b>> {
        WitNodePointer::new(self, 0).field(field_idx)
    }

    fn variant(&'a self) -> Option<(u32, Option<WitNodePointer<'b>>)> {
        WitNodePointer::new(self, 0).variant()
    }

    fn enum_value(&'a self) -> Option<u32> {
        WitNodePointer::new(self, 0).enum_value()
    }

    fn flags(&'a self) -> Option<&'b [bool]> {
        WitNodePointer::new(self, 0).flags()
    }

    fn tuple_element(&'a self, element_idx: usize) -> Option<WitNodePointer<'b>> {
        WitNodePointer::new(self, 0).tuple_element(element_idx)
    }

    fn list_elements<R>(&'a self, f: impl Fn(WitNodePointer<'b>) -> R) -> Option<Vec<R>> {
        WitNodePointer::new(self, 0).list_elements(f)
    }

    fn option(&'a self) -> Option<Option<WitNodePointer<'b>>> {
        WitNodePointer::new(self, 0).option()
    }

    fn result(&'a self) -> Option<Result<Option<WitNodePointer<'b>>, Option<WitNodePointer<'b>>>> {
        WitNodePointer::new(self, 0).result()
    }

    fn handle(&'a self) -> Option<(Uri, u64)> {
        WitNodePointer::new(self, 0).handle()
    }
}

pub struct WitNodePointer<'a> {
    value: &'a WitValue,
    idx: usize,
}

impl<'a> WitNodePointer<'a> {
    fn new(value: &'a WitValue, idx: usize) -> Self {
        assert!(idx < value.nodes.len());
        Self { value, idx }
    }

    fn node(&self) -> &'a WitNode {
        &self.value.nodes[self.idx]
    }

    fn u8(&self) -> Option<u8> {
        if let WitNode::PrimU8(value) = self.node() {
            Some(*value)
        } else {
            None
        }
    }

    fn u16(&self) -> Option<u16> {
        if let WitNode::PrimU16(value) = self.node() {
            Some(*value)
        } else {
            None
        }
    }

    fn u32(&self) -> Option<u32> {
        if let WitNode::PrimU32(value) = self.node() {
            Some(*value)
        } else {
            None
        }
    }

    fn u64(&self) -> Option<u64> {
        if let WitNode::PrimU64(value) = self.node() {
            Some(*value)
        } else {
            None
        }
    }

    fn s8(&self) -> Option<i8> {
        if let WitNode::PrimS8(value) = self.node() {
            Some(*value)
        } else {
            None
        }
    }

    fn s16(&self) -> Option<i16> {
        if let WitNode::PrimS16(value) = self.node() {
            Some(*value)
        } else {
            None
        }
    }

    fn s32(&self) -> Option<i32> {
        if let WitNode::PrimS32(value) = self.node() {
            Some(*value)
        } else {
            None
        }
    }

    fn s64(&self) -> Option<i64> {
        if let WitNode::PrimS64(value) = self.node() {
            Some(*value)
        } else {
            None
        }
    }

    fn f32(&self) -> Option<f32> {
        if let WitNode::PrimFloat32(value) = self.node() {
            Some(*value)
        } else {
            None
        }
    }

    fn f64(&self) -> Option<f64> {
        if let WitNode::PrimFloat64(value) = self.node() {
            Some(*value)
        } else {
            None
        }
    }

    fn char(&self) -> Option<char> {
        if let WitNode::PrimChar(value) = self.node() {
            Some(*value)
        } else {
            None
        }
    }

    fn bool(&self) -> Option<bool> {
        if let WitNode::PrimBool(value) = self.node() {
            Some(*value)
        } else {
            None
        }
    }

    fn string(&self) -> Option<&'a str> {
        if let WitNode::PrimString(value) = self.node() {
            Some(value)
        } else {
            None
        }
    }

    fn field(&self, field_idx: usize) -> Option<WitNodePointer<'a>> {
        if let WitNode::RecordValue(fields) = self.node() {
            fields
                .get(field_idx)
                .map(|idx| WitNodePointer::new(self.value, *idx as usize))
        } else {
            None
        }
    }

    fn variant(&self) -> Option<(u32, Option<WitNodePointer<'a>>)> {
        if let WitNode::VariantValue((case, value)) = self.node() {
            let value = value.map(|idx| WitNodePointer::new(self.value, idx as usize));
            Some((*case, value))
        } else {
            None
        }
    }

    fn enum_value(&self) -> Option<u32> {
        if let WitNode::EnumValue(value) = self.node() {
            Some(*value)
        } else {
            None
        }
    }

    fn flags(&self) -> Option<&'a [bool]> {
        if let WitNode::FlagsValue(value) = self.node() {
            Some(value)
        } else {
            None
        }
    }

    fn tuple_element(&self, element_idx: usize) -> Option<WitNodePointer<'a>> {
        if let WitNode::TupleValue(elements) = self.node() {
            elements
                .get(element_idx)
                .map(|idx| WitNodePointer::new(self.value, *idx as usize))
        } else {
            None
        }
    }

    fn list_elements<R>(&self, f: impl Fn(WitNodePointer<'a>) -> R) -> Option<Vec<R>> {
        if let WitNode::ListValue(elements) = self.node() {
            Some(
                elements
                    .iter()
                    .map(|idx| f(WitNodePointer::new(self.value, *idx as usize)))
                    .collect(),
            )
        } else {
            None
        }
    }

    fn option(&self) -> Option<Option<WitNodePointer<'a>>> {
        if let WitNode::OptionValue(value) = self.node() {
            Some(value.map(|idx| WitNodePointer::new(self.value, idx as usize)))
        } else {
            None
        }
    }

    fn result(&self) -> Option<Result<Option<WitNodePointer<'a>>, Option<WitNodePointer<'a>>>> {
        if let WitNode::ResultValue(value) = self.node() {
            Some(match value {
                Ok(idx) => Ok(idx.map(|idx| WitNodePointer::new(self.value, idx as usize))),
                Err(idx) => Err(idx.map(|idx| WitNodePointer::new(self.value, idx as usize))),
            })
        } else {
            None
        }
    }

    fn handle(&self) -> Option<(Uri, u64)> {
        if let WitNode::Handle((uri, idx)) = self.node() {
            Some((uri.clone(), *idx))
        } else {
            None
        }
    }
}

impl<'a, 'b> WitValueExtractor<'a, 'b> for WitNodePointer<'b> {
    fn u8(&'a self) -> Option<u8> {
        self.u8()
    }

    fn u16(&'a self) -> Option<u16> {
        self.u16()
    }

    fn u32(&'a self) -> Option<u32> {
        self.u32()
    }

    fn u64(&'a self) -> Option<u64> {
        self.u64()
    }

    fn s8(&'a self) -> Option<i8> {
        self.s8()
    }

    fn s16(&'a self) -> Option<i16> {
        self.s16()
    }

    fn s32(&'a self) -> Option<i32> {
        self.s32()
    }

    fn s64(&'a self) -> Option<i64> {
        self.s64()
    }

    fn f32(&'a self) -> Option<f32> {
        self.f32()
    }

    fn f64(&'a self) -> Option<f64> {
        self.f64()
    }

    fn char(&'a self) -> Option<char> {
        self.char()
    }

    fn bool(&'a self) -> Option<bool> {
        self.bool()
    }

    fn string(&'a self) -> Option<&'b str> {
        self.string()
    }

    fn field(&'a self, field_idx: usize) -> Option<WitNodePointer<'b>> {
        self.field(field_idx)
    }

    fn variant(&'a self) -> Option<(u32, Option<WitNodePointer<'b>>)> {
        self.variant()
    }

    fn enum_value(&'a self) -> Option<u32> {
        self.enum_value()
    }

    fn flags(&'a self) -> Option<&'b [bool]> {
        self.flags()
    }

    fn tuple_element(&'a self, element_idx: usize) -> Option<WitNodePointer<'b>> {
        self.tuple_element(element_idx)
    }

    fn list_elements<R>(&'a self, f: impl Fn(WitNodePointer<'b>) -> R) -> Option<Vec<R>> {
        self.list_elements(f)
    }

    fn option(&'a self) -> Option<Option<WitNodePointer<'b>>> {
        self.option()
    }

    fn result(&'a self) -> Option<Result<Option<WitNodePointer<'b>>, Option<WitNodePointer<'b>>>> {
        self.result()
    }

    fn handle(&'a self) -> Option<(Uri, u64)> {
        self.handle()
    }
}

#[cfg(test)]
mod tests {
    use test_r::test;

    use crate::*;

    #[test]
    fn primitive() {
        let value = WitValue::builder().s32(42);
        assert_eq!(value.s32(), Some(42));
    }

    #[test]
    fn single_record() {
        let value = WitValue::builder()
            .record()
            .item()
            .u8(1)
            .item()
            .enum_value(2)
            .item()
            .flags(vec![true, false, true])
            .finish();
        assert_eq!(value.field(0).unwrap().u8(), Some(1));
        assert_eq!(value.field(1).unwrap().enum_value(), Some(2));
        assert_eq!(
            value.field(2).unwrap().flags().unwrap(),
            &[true, false, true]
        );
    }

    #[test]
    fn single_record_generic() {
        fn u8_field<'a, 'b>(extractor: &'a impl WitValueExtractor<'a, 'b>) -> Option<u8> {
            extractor.u8()
        }

        let value = WitValue::builder()
            .record()
            .item()
            .u8(1)
            .item()
            .enum_value(2)
            .item()
            .flags(vec![true, false, true])
            .finish();
        assert_eq!(u8_field(&value.field(0).unwrap()), Some(1));
        assert_eq!(value.field(1).unwrap().enum_value(), Some(2));
        assert_eq!(
            value.field(2).unwrap().flags().unwrap(),
            &[true, false, true]
        );
    }

    #[test]
    fn deep_record() {
        let value = WitValue::builder()
            .record()
            .item()
            .record()
            .item()
            .s32(10)
            .item()
            .string("hello")
            .finish()
            .finish();

        let inner = value.field(0).unwrap();

        assert_eq!(inner.field(0).unwrap().s32(), Some(10));
        assert_eq!(inner.field(1).unwrap().string(), Some("hello"));
    }

    #[test]
    fn variant1() {
        let value = WitValue::builder().variant(2).s32(42).finish();
        assert_eq!(value.variant().unwrap().0, 2);
        assert_eq!(value.variant().unwrap().1.unwrap().s32(), Some(42));
    }

    #[test]
    fn variant2() {
        let value = WitValue::builder().variant_unit(0);
        assert_eq!(value.variant().unwrap().0, 0);
        assert!(value.variant().unwrap().1.is_none());
    }

    #[test]
    fn enum1() {
        let value = WitValue::builder().enum_value(2);
        assert_eq!(value.enum_value(), Some(2));
    }

    #[test]
    fn flags() {
        let value = WitValue::builder().flags(vec![true, false, true]);
        assert_eq!(value.flags().unwrap(), &[true, false, true]);
    }

    #[test]
    fn tuple() {
        let value = WitValue::builder()
            .tuple()
            .item()
            .s32(42)
            .item()
            .string("hello")
            .item()
            .record()
            .item()
            .string("world")
            .finish()
            .finish();
        assert_eq!(value.tuple_element(0).unwrap().s32(), Some(42));
        assert_eq!(value.tuple_element(1).unwrap().string(), Some("hello"));
        assert_eq!(
            value.tuple_element(2).unwrap().field(0).unwrap().string(),
            Some("world")
        );
    }

    #[test]
    fn list() {
        let value =
            WitValue::builder().list_fn(&[1, 2, 3, 4], |n, item_builder| item_builder.s32(*n));

        assert_eq!(
            value.list_elements(|v| v.s32().unwrap()).unwrap(),
            vec![1, 2, 3, 4]
        );
    }

    #[test]
    fn option1() {
        let value = WitValue::builder().option_none();
        assert!(value.option().unwrap().is_none());
    }

    #[test]
    fn option2() {
        let value = WitValue::builder().option_some().s32(42).finish();
        assert_eq!(value.option().unwrap().unwrap().s32(), Some(42));
    }

    #[test]
    fn result1() {
        let value = WitValue::builder().result_ok().s32(42).finish();
        assert_eq!(
            value.result().unwrap().ok().unwrap().unwrap().s32(),
            Some(42)
        );
    }

    #[test]
    fn result2() {
        let value = WitValue::builder().result_err().s32(42).finish();
        assert_eq!(
            value.result().unwrap().err().unwrap().unwrap().s32(),
            Some(42)
        );
    }

    #[test]
    fn result3() {
        let value = WitValue::builder().result_ok_unit();
        assert!(value.result().unwrap().ok().unwrap().is_none());
    }

    #[test]
    fn result4() {
        let value = WitValue::builder().result_err_unit();
        assert!(value.result().unwrap().err().unwrap().is_none());
    }

    #[test]
    fn handle() {
        let value = WitValue::builder().handle(
            Uri {
                value: "wit://test".to_string(),
            },
            42,
        );
        assert_eq!(
            value.handle().unwrap(),
            (
                Uri {
                    value: "wit://test".to_string()
                },
                42
            )
        );
    }

    #[test]
    fn extraction_is_composable() {
        // (42, "hello", { "world" })
        let value = WitValue::builder()
            .tuple()
            .item()
            .s32(42)
            .item()
            .string("hello")
            .item()
            .record()
            .item()
            .string("world")
            .finish()
            .finish();

        let world = value
            .tuple_element(2)
            .unwrap()
            .field(0)
            .unwrap()
            .string()
            .unwrap();
        assert_eq!(world, "world");
    }

    #[test]
    fn extraction_is_composable_using_flags() {
        // (42, "hello", { `101` })
        let value = WitValue::builder()
            .tuple()
            .item()
            .s32(42)
            .item()
            .string("hello")
            .item()
            .record()
            .item()
            .flags(vec![true, false, true])
            .finish()
            .finish();

        let flags = value
            .tuple_element(2)
            .map(|record| record.field(0).unwrap().flags().unwrap())
            .unwrap();

        if flags[0] {
            println!("Flag 0 is set");
        }

        assert!(flags[0]);
        assert!(!flags[1]);
        assert!(flags[2]);
    }
}
