use std::mem::size_of;
use std::os::raw::c_double;

use pyo3::exceptions::asyncio::InvalidStateError;
use pyo3::prelude::*;

use paste::paste;

use crate::PyAnySerde;

macro_rules! define_primitive_communication {
    ($type:ty) => {
        paste! {
            pub fn [<append_ $type>](buf: &mut [u8], offset: usize, val: $type) -> usize {
                let end = offset + size_of::<$type>();
                buf[offset..end].copy_from_slice(&val.to_ne_bytes());
                end
            }

            pub fn [<append_ $type _vec>](v: &mut Vec<u8>, val: $type) {
                v.extend_from_slice(&val.to_ne_bytes());
            }

            pub fn [<retrieve_ $type>](buf: &[u8], offset: usize) -> PyResult<($type, usize)> {
                let end = offset + size_of::<$type>();
                Ok(($type::from_ne_bytes(buf[offset..end].try_into()?), end))
            }
        }
    };
}

define_primitive_communication!(usize);
define_primitive_communication!(c_double);
define_primitive_communication!(i64);
define_primitive_communication!(u64);
define_primitive_communication!(f32);
define_primitive_communication!(f64);

pub fn append_bool(buf: &mut [u8], offset: usize, val: bool) -> usize {
    let end = offset + size_of::<u8>();
    buf[offset..end].copy_from_slice(&(val as u8).to_ne_bytes());
    end
}

pub fn append_bool_vec(v: &mut Vec<u8>, val: bool) {
    v.extend_from_slice(&(val as u8).to_ne_bytes());
}

pub fn retrieve_bool(buf: &[u8], offset: usize) -> PyResult<(bool, usize)> {
    let end = offset + size_of::<bool>();
    let val = match u8::from_ne_bytes(buf[offset..end].try_into()?) {
        0 => Ok(false),
        1 => Ok(true),
        v => Err(InvalidStateError::new_err(format!(
            "tried to retrieve bool from shared_memory but got value {v}"
        ))),
    }?;
    Ok((val, end))
}

pub fn append_bytes_vec(v: &mut Vec<u8>, bytes: &[u8]) {
    append_usize_vec(v, bytes.len());
    v.extend_from_slice(bytes);
}

pub fn append_string_vec(v: &mut Vec<u8>, s: &String) {
    append_bytes_vec(v, s.as_bytes());
}

pub fn retrieve_string(buf: &[u8], offset: usize) -> PyResult<(String, usize)> {
    let (string_bytes, offset) = retrieve_bytes(buf, offset)?;
    Ok((String::from_utf8(string_bytes.to_vec())?, offset))
}

pub fn insert_bytes(buf: &mut [u8], offset: usize, bytes: &[u8]) -> usize {
    let end = offset + bytes.len();
    buf[offset..end].copy_from_slice(bytes);
    end
}

pub fn append_bytes(buf: &mut [u8], offset: usize, bytes: &[u8]) -> usize {
    let bytes_len = bytes.len();
    let start = append_usize(buf, offset, bytes_len);
    let end = start + bytes.len();
    buf[start..end].copy_from_slice(bytes);
    end
}

pub fn retrieve_bytes(buf: &[u8], offset: usize) -> PyResult<(&[u8], usize)> {
    let (len, start) = retrieve_usize(buf, offset)?;
    let end = start + len;
    Ok((&buf[start..end], end))
}

pub fn append_python_option_bound<'py, F>(
    buf: &mut [u8],
    mut offset: usize,
    obj_option: &Option<&Bound<'py, PyAny>>,
    serde_option: &mut Option<&mut Box<dyn PyAnySerde>>,
    err: F,
) -> PyResult<usize>
where
    F: FnOnce() -> PyErr,
{
    if let Some(obj) = obj_option {
        offset = append_bool(buf, offset, true);
        offset = serde_option
            .as_deref_mut()
            .ok_or_else(err)?
            .append(buf, offset, obj)?;
    } else {
        offset = append_bool(buf, offset, false);
    }
    Ok(offset)
}

pub fn append_python_option<'py, F>(
    py: Python<'py>,
    buf: &mut [u8],
    mut offset: usize,
    obj_option: &Option<&Py<PyAny>>,
    serde_option: &mut Option<&mut Box<dyn PyAnySerde>>,
    err: F,
) -> PyResult<usize>
where
    F: FnOnce() -> PyErr,
{
    if let Some(obj) = obj_option {
        offset = append_bool(buf, offset, true);
        offset = serde_option
            .as_deref_mut()
            .ok_or_else(err)?
            .append(buf, offset, obj.bind(py))?;
    } else {
        offset = append_bool(buf, offset, false);
    }
    Ok(offset)
}

pub fn retrieve_python_option<'py, F>(
    py: Python<'py>,
    buf: &mut [u8],
    mut offset: usize,
    serde_option: &mut Option<&mut Box<dyn PyAnySerde>>,
    err: F,
) -> PyResult<(Option<Bound<'py, PyAny>>, usize)>
where
    F: FnOnce() -> PyErr,
{
    let is_some;
    (is_some, offset) = retrieve_bool(buf, offset)?;
    let obj_option = if is_some {
        let obj;
        (obj, offset) = serde_option
            .as_deref_mut()
            .ok_or_else(err)?
            .retrieve(py, buf, offset)?;
        Some(obj)
    } else {
        None
    };
    Ok((obj_option, offset))
}
