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

use epserde::prelude::*;

#[cfg(not(feature = "std"))]
extern crate alloc;

#[cfg(not(feature = "std"))]
use alloc::rc::Rc;
use core::ops::ControlFlow;
#[cfg(feature = "std")]
use std::{
    hash::{BuildHasherDefault, DefaultHasher},
    ops::{Bound, RangeBounds, RangeInclusive},
    rc::Rc,
};

const TEST_STRS: &[&str] = &["abc\0\x0a🔥\u{0d2bdf}", ""];

#[test]
fn test_box_str() -> anyhow::Result<()> {
    for &test_str in TEST_STRS {
        let s = test_str;
        {
            let mut cursor = <AlignedCursor>::new();

            let mut schema = unsafe { s.serialize_with_schema(&mut cursor)? };
            schema.0.sort_by_key(|a| a.offset);

            cursor.set_position(0);
            let full_copy = unsafe { <Box<str>>::deserialize_full(&mut cursor)? };
            assert_eq!(&*full_copy, s);

            let eps_copy = unsafe { <Box<str>>::deserialize_eps(cursor.as_bytes())? };
            assert_eq!(eps_copy, s);
            let eps_copy = unsafe { String::deserialize_eps(cursor.as_bytes())? };
            assert_eq!(eps_copy, s);
        }
        let s = test_str.to_string();
        {
            let mut cursor = <AlignedCursor>::new();

            let mut schema = unsafe { s.serialize_with_schema(&mut cursor)? };
            schema.0.sort_by_key(|a| a.offset);

            cursor.set_position(0);
            let full_copy = unsafe { String::deserialize_full(&mut cursor)? };
            assert_eq!(full_copy, s);

            let eps_copy = unsafe { <Box<str>>::deserialize_eps(cursor.as_bytes())? };
            assert_eq!(eps_copy, s);
            let eps_copy = unsafe { String::deserialize_eps(cursor.as_bytes())? };
            assert_eq!(eps_copy, s);
        }
        let s = test_str.to_string().into_boxed_str();
        {
            let mut cursor = <AlignedCursor>::new();
            unsafe { s.serialize(&mut cursor)? };

            cursor.set_position(0);
            let full_copy = unsafe { <Box<str>>::deserialize_full(&mut cursor)? };
            assert_eq!(full_copy, s);

            let eps_copy = unsafe { <Box<str>>::deserialize_eps(cursor.as_bytes())? };
            assert_eq!(eps_copy, s.as_ref());
            let eps_copy = unsafe { String::deserialize_eps(cursor.as_bytes())? };
            assert_eq!(eps_copy, s.as_ref());
        }
    }
    Ok(())
}

fn test_generic<T>(s: T) -> anyhow::Result<()>
where
    T: Serialize + Deserialize + PartialEq + core::fmt::Debug,
    for<'a> DeserType<'a, T>: PartialEq<T> + core::fmt::Debug,
{
    {
        let mut cursor = <AlignedCursor<Aligned16>>::new();

        let mut schema = unsafe { s.serialize_with_schema(&mut cursor)? };
        schema.0.sort_by_key(|a| a.offset);

        cursor.set_position(0);
        let full_copy = unsafe { <T>::deserialize_full(&mut cursor)? };
        assert_eq!(full_copy, s);

        let bytes = cursor.as_bytes();
        let eps_copy = unsafe { <T>::deserialize_eps(bytes)? };
        assert_eq!(eps_copy, s);

        let _ = schema.to_csv();
        let _ = schema.to_csv_with_data(bytes);
    }
    {
        let mut cursor = <AlignedCursor<Aligned16>>::new();
        unsafe { s.serialize(&mut cursor)? };

        cursor.set_position(0);
        let full_copy = unsafe { <T>::deserialize_full(&mut cursor)? };
        assert_eq!(full_copy, s);

        let eps_copy = unsafe { <T>::deserialize_eps(cursor.as_bytes())? };
        assert_eq!(eps_copy, s);
    }
    Ok(())
}

#[test]
fn test_range() -> anyhow::Result<()> {
    test_generic::<core::ops::Range<i32>>(0..10)?;

    #[derive(Epserde, PartialEq, Debug)]
    struct Data(core::ops::Range<i32>);
    test_generic(Data(0..10))?;
    Ok(())
}

#[test]
fn test_option() -> anyhow::Result<()> {
    test_generic::<Option<i32>>(Some(42))?;
    test_generic::<Option<i32>>(None)?;

    // The ε-copy deserialization type of Option<Vec<usize>> is
    // Option<&[usize]>.
    let a: Option<Vec<usize>> = Some(vec![1, 2, 3]);
    let mut cursor = <AlignedCursor<Aligned16>>::new();
    unsafe { a.serialize(&mut cursor)? };
    cursor.set_position(0);
    let full = unsafe { <Option<Vec<usize>>>::deserialize_full(&mut cursor)? };
    assert_eq!(full, a);
    let eps = unsafe { <Option<Vec<usize>>>::deserialize_eps(cursor.as_bytes())? };
    assert_eq!(eps, Some(&[1_usize, 2, 3][..]));

    let a: Option<Vec<usize>> = None;
    let mut cursor = <AlignedCursor<Aligned16>>::new();
    unsafe { a.serialize(&mut cursor)? };
    cursor.set_position(0);
    let full = unsafe { <Option<Vec<usize>>>::deserialize_full(&mut cursor)? };
    assert_eq!(full, a);
    let eps = unsafe { <Option<Vec<usize>>>::deserialize_eps(cursor.as_bytes())? };
    assert_eq!(eps, None);
    Ok(())
}

#[test]
fn test_ser_rc_ref() -> anyhow::Result<()> {
    let v = vec![0, 1, 2, 3];
    let mut cursor = <AlignedCursor<Aligned16>>::new();
    unsafe { Serialize::serialize(&Rc::new(v.as_slice()), &mut cursor)? };
    cursor.set_position(0);
    let s = unsafe { <Rc<Box<[i32]>>>::deserialize_eps(cursor.as_bytes())? };
    assert_eq!(&*s, &v);
    Ok(())
}

#[test]
fn test_ref_field() -> anyhow::Result<()> {
    let v = vec![0, 1, 2, 3];
    let mut cursor = <AlignedCursor<Aligned16>>::new();
    #[derive(Epserde, Debug)]
    struct Data<A>(A);
    unsafe { Serialize::serialize(&Rc::new(Data(v.as_slice())), &mut cursor)? };
    cursor.set_position(0);
    let s = unsafe { <Rc<Data<Box<[i32]>>>>::deserialize_eps(cursor.as_bytes())? };
    assert_eq!(s.0, &v);
    Ok(())
}

#[cfg(feature = "std")]
#[test]
fn test_range_bound_deep_copy_idx() -> anyhow::Result<()> {
    let r = RangeInclusive::new("a".to_string(), "b".to_string());
    let mut cursor = <AlignedCursor<Aligned16>>::new();
    unsafe { r.serialize(&mut cursor)? };
    cursor.set_position(0);
    let full = unsafe { RangeInclusive::<String>::deserialize_full(&mut cursor)? };
    assert_eq!(full, r);
    let eps = unsafe { RangeInclusive::<String>::deserialize_eps(cursor.as_bytes())? };
    assert_eq!(eps.start_bound(), Bound::Included(&"a"));
    assert_eq!(eps.end_bound(), Bound::Included(&"b"));
    Ok(())
}

#[test]
fn test_control_flow() -> anyhow::Result<()> {
    test_generic::<ControlFlow<i32, f64>>(ControlFlow::Break(42))?;
    test_generic::<ControlFlow<i32, f64>>(ControlFlow::Continue(1.618))?;
    Ok(())
}

#[test]
fn test_result() -> anyhow::Result<()> {
    test_generic::<Result<i32, f64>>(Ok(42))?;
    test_generic::<Result<i32, f64>>(Err(1.618))?;
    Ok(())
}

#[test]
fn test_bound() -> anyhow::Result<()> {
    test_generic::<core::ops::Bound<i32>>(core::ops::Bound::Unbounded)?;
    test_generic::<core::ops::Bound<i32>>(core::ops::Bound::Included(42))?;
    test_generic::<core::ops::Bound<i32>>(core::ops::Bound::Excluded(42))?;
    Ok(())
}

#[cfg(feature = "std")]
#[test]
fn test_builder_hasher_default() -> anyhow::Result<()> {
    let bhd = BuildHasherDefault::<DefaultHasher>::default();
    let mut cursor = <AlignedCursor<Aligned16>>::new();
    unsafe { bhd.serialize(&mut cursor)? };
    cursor.set_position(0);
    let full = unsafe { BuildHasherDefault::<DefaultHasher>::deserialize_full(&mut cursor)? };
    assert_eq!(&full, &bhd);
    let eps = unsafe { BuildHasherDefault::<DefaultHasher>::deserialize_eps(cursor.as_bytes())? };
    assert_eq!(&eps, &bhd);
    Ok(())
}

#[cfg(feature = "std")]
#[test]
fn test_range_inclusive_exhausted() {
    // An exhausted RangeInclusive whose start cannot advance past its end
    // (here because doing so would overflow) keeps its exhausted flag, which
    // the standard library reports as an excluded end bound. Only start and
    // end are serialized, so reconstructing such a range would yield a
    // non-empty range; serializing it must therefore fail.
    //
    // Note that a range exhausted below the maximum value is represented by a
    // start advanced past the end (e.g., 1..=2 becomes 3..=2), which is a
    // genuinely empty range that serializes and reconstructs faithfully, so it
    // is not a useful case here.
    let mut r = (i32::MAX - 1)..=i32::MAX;
    r.next();
    r.next();
    assert!(r.is_empty());
    assert!(matches!(r.end_bound(), Bound::Excluded(_)));
    let mut cursor = <AlignedCursor<Aligned16>>::new();
    assert!(unsafe { r.serialize(&mut cursor) }.is_err());
}
