/*
 * SPDX-FileCopyrightText: 2023 Inria
 *
 * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
 */

use epserde::{deser::DeserInner, prelude::*, ser::SerType};
use std::marker::PhantomData;
#[derive(Epserde, Debug, PartialEq, Eq, Clone)]
struct Data<A: PartialEq = usize, const Q: usize = 3> {
    a: A,
    b: [i32; Q],
}

#[test]
fn test_inner_param_full() -> anyhow::Result<()> {
    // Create a new value to serialize
    let person = Data {
        a: vec![0x89; 6],
        b: [0xbadf00d; 2],
    };
    let mut cursor = <AlignedCursor<Aligned16>>::new();
    // Serialize
    let _bytes_written = unsafe { person.serialize(&mut cursor)? };

    // Do a full-copy deserialization
    cursor.set_position(0);
    let full = unsafe { <Data<Vec<usize>, 2>>::deserialize_full(&mut cursor)? };
    assert_eq!(full, person);

    println!();

    // Do an ε-copy deserialization
    let eps = unsafe { <Data<Vec<usize>, 2>>::deserialize_eps(cursor.as_bytes())? };
    assert_eq!(eps.a, person.a);
    assert_eq!(eps.b, person.b);
    Ok(())
}

#[derive(Epserde, Debug, PartialEq, Eq, Clone)]
struct Data2<P: TypeHash, B> {
    a: B,
    // this should be ignored, but contains P in the type name so it might
    // be erroneously matched
    _marker2: PhantomData<()>,
    _marker: std::marker::PhantomData<P>,
}

#[test]
fn test_inner_param_eps() -> anyhow::Result<()> {
    // Create a new value to serialize
    let data = Data2::<usize, Vec<usize>> {
        a: vec![0x89; 6],
        _marker2: PhantomData,
        _marker: PhantomData,
    };

    let mut cursor = <AlignedCursor<Aligned16>>::new();
    // Serialize
    let _bytes_written = unsafe { data.serialize(&mut cursor)? };

    // Do a full-copy deserialization
    cursor.set_position(0);
    let full = unsafe { <Data2<usize, Vec<usize>>>::deserialize_full(&mut cursor)? };
    assert_eq!(full, data);
    // Do an ε-copy deserialization

    let eps = unsafe { <Data2<usize, Vec<usize>>>::deserialize_eps(cursor.as_bytes())? };
    assert_eq!(eps.a, data.a);
    Ok(())
}

#[derive(Epserde, Debug, PartialEq, Eq, Clone, Copy)]
#[epserde(zero_copy)]
#[repr(C)]
struct Data3<const N: usize = 10>;

#[test]
fn test_consts() -> anyhow::Result<()> {
    // Create a new value to serialize
    let data = Data3::<11> {};

    let mut cursor = <AlignedCursor<Aligned16>>::new();
    // Serialize
    let _bytes_written = unsafe { data.serialize(&mut cursor)? };
    cursor.set_position(0);

    // with a different const the deserialization should fail
    let eps = unsafe { <Data3<12>>::deserialize_full(&mut cursor) };
    assert!(eps.is_err());

    // Do a full-copy deserialization
    cursor.set_position(0);
    let full = unsafe { <Data3<11>>::deserialize_full(&mut cursor)? };
    assert_eq!(full, data);

    // Do an ε-copy deserialization
    let eps = unsafe { <Data3<11>>::deserialize_eps(cursor.as_bytes())? };
    assert_eq!(eps, &data);

    // with a different const the deserialization should fail
    let eps = unsafe { <Data3<12>>::deserialize_eps(cursor.as_bytes()) };
    assert!(eps.is_err());
    Ok(())
}

#[derive(Epserde, Copy, Debug, PartialEq, Eq, Clone, Default)]
struct DeepCopyParam<T> {
    data: T,
}

#[test]
fn test_types_deep_copy_param() {
    let _test_usize: SerType<DeepCopyParam<usize>> = DeepCopyParam { data: 0 };
    let _test: DeserType<'_, DeepCopyParam<usize>> = DeepCopyParam { data: 0 };
    let _test_array: SerType<DeepCopyParam<[i32; 4]>> = DeepCopyParam { data: [1, 2, 3, 4] };
    let _test: DeserType<'_, DeepCopyParam<[i32; 4]>> = DeepCopyParam {
        data: &[1, 2, 3, 4],
    };
}

#[derive(Epserde, Copy, Debug, PartialEq, Eq, Clone, Default)]
#[repr(C)]
#[epserde(zero_copy)]
struct ZeroCopyParam<T: ZeroCopy> {
    data: T,
}

#[test]
fn test_types_zero_copy_param() {
    let _test_usize: SerType<ZeroCopyParam<usize>> = ZeroCopyParam { data: 0 };
    let _test: DeserType<'_, ZeroCopyParam<usize>> = &ZeroCopyParam { data: 0 };
    let _test_array: SerType<ZeroCopyParam<[i32; 4]>> = ZeroCopyParam { data: [1, 2, 3, 4] };
    let _test: DeserType<'_, ZeroCopyParam<[i32; 4]>> = &ZeroCopyParam { data: [1, 2, 3, 4] };
}

// Check that bounds are propagated to associated (de)serialization types.
#[allow(dead_code)]
#[derive(Epserde, Copy, Debug, PartialEq, Eq, Clone)]
#[repr(align(8))]
#[repr(align(16))]
enum DeepCopyEnumParam<T: ZeroCopy> {
    A(T),
}

// Test #[epserde(bound(...))] for pinning associated types.
trait HasWord {
    type Word: epserde::ser::SerInner + DeserInner + Copy + 'static;
}

impl HasWord for Vec<usize> {
    type Word = usize;
}

impl HasWord for Box<[usize]> {
    type Word = usize;
}

impl HasWord for &[usize] {
    type Word = usize;
}

#[derive(Epserde, Debug, PartialEq, Eq, Clone)]
#[epserde(bound(deser = "for<'__a> <B as DeserInner>::DeserType<'__a>: HasWord<Word = B::Word>"))]
struct WithAssocType<B: HasWord> {
    data: B,
    word: B::Word,
}

// A relaxed bound on a type parameter must not be transplanted onto the
// substituted forms in the generated where clauses: relaxed bounds are legal
// only on the parameters of the item itself.
#[derive(Epserde, Debug, PartialEq, Eq, Clone)]
struct MaybeUnsizedHolder<K: ?Sized, A> {
    a: A,
    _marker: PhantomData<K>,
}

// The holder keeps K full-copy (it occurs only in its PhantomData slot), so
// the parameter must be pinned to full-copy here.
#[derive(Epserde, Debug, PartialEq, Eq, Clone)]
#[epserde(full_copy(K))]
struct UsesMaybeUnsizedHolder<K: ?Sized, A> {
    h: MaybeUnsizedHolder<K, A>,
}

#[test]
fn test_relaxed_bound_param() -> anyhow::Result<()> {
    let val = UsesMaybeUnsizedHolder::<usize, Vec<usize>> {
        h: MaybeUnsizedHolder {
            a: vec![1, 2, 3],
            _marker: PhantomData,
        },
    };

    let mut cursor = <AlignedCursor<Aligned16>>::new();
    let _bytes_written = unsafe { val.serialize(&mut cursor)? };

    // Full-copy deserialization
    cursor.set_position(0);
    let full =
        unsafe { <UsesMaybeUnsizedHolder<usize, Vec<usize>>>::deserialize_full(&mut cursor)? };
    assert_eq!(full, val);

    // ε-copy deserialization
    let eps =
        unsafe { <UsesMaybeUnsizedHolder<usize, Vec<usize>>>::deserialize_eps(cursor.as_bytes())? };
    assert_eq!(eps.h.a, val.h.a);
    Ok(())
}

// A parameter declared phantom must be left completely untouched: no
// SerType/DeserType substitution and no SerInner/DeserInner bounds. This
// makes it possible to instantiate the parameter with a type that is not
// serializable at all, such as str, when nested field types hold the
// parameter only in PhantomData slots the derive cannot see.
#[derive(Epserde, Debug, PartialEq, Eq, Clone)]
#[epserde(phantom(K))]
struct PhantomParamOuter<K: ?Sized, A> {
    h: MaybeUnsizedHolder<K, A>,
}

#[test]
fn test_phantom_param() -> anyhow::Result<()> {
    let val = PhantomParamOuter::<str, Vec<usize>> {
        h: MaybeUnsizedHolder {
            a: vec![1, 2, 3],
            _marker: PhantomData,
        },
    };

    let mut cursor = <AlignedCursor<Aligned16>>::new();
    let _bytes_written = unsafe { val.serialize(&mut cursor)? };

    // Full-copy deserialization
    cursor.set_position(0);
    let full = unsafe { <PhantomParamOuter<str, Vec<usize>>>::deserialize_full(&mut cursor)? };
    assert_eq!(full, val);

    // ε-copy deserialization
    let eps = unsafe { <PhantomParamOuter<str, Vec<usize>>>::deserialize_eps(cursor.as_bytes())? };
    assert_eq!(eps.h.a, val.h.a);
    Ok(())
}

// A const parameter forwarded as a generic argument of a field type must be
// left untouched by the substitution machinery.
#[derive(Epserde, Debug, PartialEq, Eq, Clone)]
struct ConstParamInner<A, const S: bool> {
    a: A,
}

#[derive(Epserde, Debug, PartialEq, Eq, Clone)]
struct ConstParamOuter<A, const S: bool = true> {
    inner: ConstParamInner<A, S>,
}

#[test]
fn test_forwarded_const_param() -> anyhow::Result<()> {
    let val = ConstParamOuter::<Vec<usize>> {
        inner: ConstParamInner { a: vec![1, 2, 3] },
    };

    let mut cursor = <AlignedCursor<Aligned16>>::new();
    let _bytes_written = unsafe { val.serialize(&mut cursor)? };

    // Full-copy deserialization
    cursor.set_position(0);
    let full = unsafe { <ConstParamOuter<Vec<usize>>>::deserialize_full(&mut cursor)? };
    assert_eq!(full, val);

    // ε-copy deserialization
    let eps = unsafe { <ConstParamOuter<Vec<usize>>>::deserialize_eps(cursor.as_bytes())? };
    assert_eq!(eps.inner.a, val.inner.a);
    Ok(())
}

#[test]
fn test_bound_attr() -> anyhow::Result<()> {
    let val = WithAssocType::<Vec<usize>> {
        data: vec![1, 2, 3],
        word: 42,
    };

    let mut cursor = <AlignedCursor<Aligned16>>::new();
    let _bytes_written = unsafe { val.serialize(&mut cursor)? };

    // Full-copy deserialization
    cursor.set_position(0);
    let full = unsafe { <WithAssocType<Vec<usize>>>::deserialize_full(&mut cursor)? };
    assert_eq!(full, val);

    // ε-copy deserialization
    let eps = unsafe { <WithAssocType<Vec<usize>>>::deserialize_eps(cursor.as_bytes())? };
    assert_eq!(eps.data, val.data);
    assert_eq!(eps.word, val.word);
    Ok(())
}
