use std::{
    io::{Error, ErrorKind},
    marker::PhantomData,
};

use crate::HaskellSize;

/*******************************************************************************
  Main class definition
*******************************************************************************/

const ERROR_NOT_ALL_BYTES_READ: &str = "Not all bytes read";

pub trait FromHaskell<Tag>: Sized {
    /// Deserialize data sent from Haskell
    ///
    /// This is the analogue of `BorshDeserialize::deserialize`.
    //
    /// See `ToHaskell` for a detailed discussion of the `tag` argument.
    fn from_haskell(buf: &mut &[u8], tag: PhantomData<Tag>) -> Result<Self, Error>;

    fn from_haskell_slice(slice: &[u8], tag: PhantomData<Tag>) -> Result<Self, Error> {
        let mut slice_mut = slice;
        let result = Self::from_haskell(&mut slice_mut, tag)?;
        if !slice_mut.is_empty() {
            return Err(Error::new(ErrorKind::InvalidData, ERROR_NOT_ALL_BYTES_READ));
        }
        Ok(result)
    }
}

/*******************************************************************************
  Derived functionality

  See comments in `to_haskell` for why these functions do not live inside the
  trait.
*******************************************************************************/

/// Marshall value with variable-sized encoding
pub fn marshall_from_haskell_var<Tag, T>(inp: *const u8, len: usize, tag: PhantomData<Tag>) -> T
where
    T: FromHaskell<Tag>,
{
    let mut vec: Vec<u8> = vec![0; len];
    unsafe {
        std::ptr::copy(inp, vec.as_mut_ptr(), len);
    }
    match T::from_haskell_slice(vec.as_ref(), tag) {
        Ok(t) => t,
        Err(e) => panic!("{}", e),
    }
}

/// Marshall value with fixed-size encoding
///
/// The `len` argument here is only to verify that the Haskell-side and
/// Rust-side agree on the size of the encoding.
pub fn marshall_from_haskell_fixed<Tag, T>(
    inp: *const u8,
    inp_len: usize,
    tag: PhantomData<Tag>,
) -> T
where
    T: FromHaskell<Tag> + HaskellSize<Tag>,
{
    let expected_len = T::haskell_size(tag);

    if inp_len != expected_len {
        panic!(
            "expected buffer of size {}, but got {}",
            expected_len, inp_len
        )
    } else {
        marshall_from_haskell_var(inp, inp_len, tag)
    }
}