use crate::WasmValue;

use super::{Type, Value};

#[test]
fn simple_value_round_trips() {
    for val in [
        Value::make_bool(true),
        Value::make_u8(u8::MAX),
        Value::make_u16(u16::MAX),
        Value::make_u32(u32::MAX),
        Value::make_u64(u64::MAX),
        Value::make_s8(i8::MIN),
        Value::make_s16(i16::MIN),
        Value::make_s32(i32::MIN),
        Value::make_s64(i64::MIN),
        Value::make_char('☃'),
        Value::make_string("str".into()),
    ] {
        test_value_round_trip(val)
    }
}

#[test]
fn float_round_trips() {
    for (float32, float64) in [
        (0.0, 0.0),
        (f32::MIN, f64::MIN),
        (f32::MIN_POSITIVE, f64::MIN_POSITIVE),
        (f32::MAX, f64::MAX),
        (f32::EPSILON, f64::EPSILON),
        (f32::INFINITY, f64::INFINITY),
        (f32::NEG_INFINITY, f64::NEG_INFINITY),
    ] {
        test_value_round_trip(Value::make_f32(float32));
        test_value_round_trip(Value::make_f64(float64));
    }
}

#[test]
fn list_round_trips() {
    let ty = Type::list(Type::U8);
    test_value_round_trip(Value::make_list(&ty, []).unwrap());
    test_value_round_trip(Value::make_list(&ty, [Value::make_u8(1)]).unwrap());
    test_value_round_trip(Value::make_list(&ty, [Value::make_u8(1), Value::make_u8(2)]).unwrap());
}

#[test]
fn record_round_trip() {
    let option_ty = Type::option(Type::U8);
    let record_ty =
        Type::record([("field-a", Type::BOOL), ("field-b", option_ty.clone())]).unwrap();
    let record_val = Value::make_record(
        &record_ty,
        [
            ("field-a", Value::make_bool(true)),
            ("field-b", Value::make_option(&option_ty, None).unwrap()),
        ],
    )
    .unwrap();
    test_value_round_trip(record_val)
}

#[test]
fn tuple_round_trip() {
    let ty = Type::tuple([Type::BOOL, Type::U8]).unwrap();
    let val = Value::make_tuple(&ty, [Value::make_bool(true), Value::make_u8(1)]).unwrap();
    test_value_round_trip(val);
}

#[test]
fn variant_round_trips() {
    let ty = Type::variant([("off", None), ("on", Some(Type::U8))]).unwrap();
    test_value_round_trip(Value::make_variant(&ty, "off", None).unwrap());
    test_value_round_trip(Value::make_variant(&ty, "on", Some(Value::make_u8(1))).unwrap());
}

#[test]
fn enum_round_trips() {
    let ty = Type::enum_ty(["north", "east", "south", "west"]).unwrap();
    test_value_round_trip(Value::make_enum(&ty, "north").unwrap());
    test_value_round_trip(Value::make_enum(&ty, "south").unwrap());
}

#[test]
fn option_round_trips() {
    let ty = Type::option(Type::U8);
    test_value_round_trip(Value::make_option(&ty, Some(Value::make_u8(1))).unwrap());
    test_value_round_trip(Value::make_option(&ty, None).unwrap());
}

#[test]
fn result_round_trips() {
    let no_payloads = Type::result(None, None);
    let both_payloads = Type::result(Some(Type::U8), Some(Type::STRING));
    let ok_only = Type::result(Some(Type::U8), None);
    let err_only = Type::result(None, Some(Type::STRING));
    for (ty, payload) in [
        (&no_payloads, Ok(None)),
        (&no_payloads, Err(None)),
        (&both_payloads, Ok(Some(Value::make_u8(1)))),
        (&both_payloads, Err(Some(Value::make_string("oops".into())))),
        (&ok_only, Ok(Some(Value::make_u8(1)))),
        (&ok_only, Err(None)),
        (&err_only, Ok(None)),
        (&err_only, Err(Some(Value::make_string("oops".into())))),
    ] {
        let val = Value::make_result(ty, payload).unwrap();
        test_value_round_trip(val);
    }
}

#[test]
fn flags_round_trips() {
    let ty = Type::flags(["read", "write", "execute"]).unwrap();
    test_value_round_trip(Value::make_flags(&ty, []).unwrap());
    test_value_round_trip(Value::make_flags(&ty, ["write"]).unwrap());
    test_value_round_trip(Value::make_flags(&ty, ["read", "execute"]).unwrap());
}

fn local_ty(val: &Value) -> Type {
    use crate::value::{TypeEnum, ValueEnum};
    match &val.0 {
        ValueEnum::Bool(_) => Type::BOOL,
        ValueEnum::S8(_) => Type::S8,
        ValueEnum::S16(_) => Type::S16,
        ValueEnum::S32(_) => Type::S32,
        ValueEnum::S64(_) => Type::S64,
        ValueEnum::U8(_) => Type::U8,
        ValueEnum::U16(_) => Type::U16,
        ValueEnum::U32(_) => Type::U32,
        ValueEnum::U64(_) => Type::U64,
        ValueEnum::F32(_) => Type::F32,
        ValueEnum::F64(_) => Type::F64,
        ValueEnum::Char(_) => Type::CHAR,
        ValueEnum::String(_) => Type::STRING,
        ValueEnum::List(inner) => Type(TypeEnum::List(inner.ty.clone())),
        ValueEnum::Record(inner) => Type(TypeEnum::Record(inner.ty.clone())),
        ValueEnum::Tuple(inner) => Type(TypeEnum::Tuple(inner.ty.clone())),
        ValueEnum::Variant(inner) => Type(TypeEnum::Variant(inner.ty.clone())),
        ValueEnum::Enum(inner) => Type(TypeEnum::Enum(inner.ty.clone())),
        ValueEnum::Option(inner) => Type(TypeEnum::Option(inner.ty.clone())),
        ValueEnum::Result(inner) => Type(TypeEnum::Result(inner.ty.clone())),
        ValueEnum::Flags(inner) => Type(TypeEnum::Flags(inner.ty.clone())),
    }
}

fn test_value_round_trip(val: Value) {
    let ty = local_ty(&val);
    let serialized = crate::to_string(&val).unwrap();
    let deserialized: Value = crate::from_str(&ty, &serialized)
        .unwrap_or_else(|err| panic!("failed to deserialize {serialized:?}: {err}"));
    assert_eq!(deserialized, val, "for {val:?}");
}
