#![deny(warnings)]

use beve::{Complex, from_slice, to_vec, to_vec_complex64_slice, to_vec_typed_slice};
use proptest::collection::vec;
use proptest::prelude::*;
use proptest::sample::select;
use serde::{Deserialize, Serialize};

fn finite_f64() -> BoxedStrategy<f64> {
    any::<f64>()
        .prop_filter("finite f64", |v| v.is_finite())
        .boxed()
}

fn complex64_strategy() -> BoxedStrategy<Complex<f64>> {
    (finite_f64(), finite_f64())
        .prop_map(|(re, im)| Complex { re, im })
        .boxed()
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct Message {
    id: u32,
    payload: Payload,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
enum Payload {
    Telemetry(Vec<f64>),
    MetaOnly(Meta),
    Snapshot { label: String, counters: Vec<u32> },
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
enum Meta {
    Unit,
    Codes(Vec<u32>),
    Window { start: u64, metrics: Vec<f32> },
}

fn meta_strategy() -> BoxedStrategy<Meta> {
    let window = (any::<u64>(), vec(any::<f32>(), 0..24))
        .prop_map(|(start, metrics)| Meta::Window { start, metrics })
        .boxed();
    prop_oneof![
        Just(Meta::Unit).boxed(),
        vec(any::<u32>(), 0..24).prop_map(Meta::Codes).boxed(),
        window,
    ]
    .boxed()
}

fn label_strategy() -> BoxedStrategy<String> {
    let alphabet: Vec<char> = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-"
        .chars()
        .collect();
    prop::collection::vec(select(alphabet), 1..25)
        .prop_map(|chars| chars.into_iter().collect())
        .boxed()
}

fn payload_strategy() -> BoxedStrategy<Payload> {
    prop_oneof![
        vec(finite_f64(), 0..48)
            .prop_map(Payload::Telemetry)
            .boxed(),
        meta_strategy().prop_map(Payload::MetaOnly).boxed(),
        (label_strategy(), vec(any::<u32>(), 0..32))
            .prop_map(|(label, counters)| Payload::Snapshot { label, counters })
            .boxed(),
    ]
    .boxed()
}

fn message_strategy() -> BoxedStrategy<Message> {
    (any::<u32>(), payload_strategy())
        .prop_map(|(id, payload)| Message { id, payload })
        .boxed()
}

proptest! {
    #[test]
    fn prop_numeric_roundtrip_u64(values in vec(any::<u64>(), 0..96)) {
        let bytes = to_vec_typed_slice(&values);
        let decoded: Vec<u64> = from_slice(&bytes).expect("decode u64 slice");
        prop_assert_eq!(decoded, values);
    }
}

proptest! {
    #[test]
    fn prop_numeric_roundtrip_f64(values in vec(finite_f64(), 0..96)) {
        let bytes = to_vec_typed_slice(&values);
        let decoded: Vec<f64> = from_slice(&bytes).expect("decode f64 slice");
        prop_assert_eq!(decoded, values);
    }
}

proptest! {
    #[test]
    fn prop_complex_roundtrip(values in vec(complex64_strategy(), 0..48)) {
        let bytes = to_vec_complex64_slice(&values);
        let decoded: Vec<Complex<f64>> = from_slice(&bytes).expect("decode complex64 slice");
        prop_assert_eq!(decoded, values);
    }
}

proptest! {
    #[test]
    fn prop_nested_enum_roundtrip(message in message_strategy()) {
        let bytes = to_vec(&message).expect("serialize message");
        let decoded: Message = from_slice(&bytes).expect("decode message");
        prop_assert_eq!(decoded, message);
    }
}
