use std::sync::Arc;
use bytes::Bytes;
use pyo3::prelude::*;
use pyo3::types::PyBytes;
use crate::python::error::into_py_err;
use crate::python::image::{Op, PyImageTransform, Value, apply_all, object_of, value_of};
use crate::{error::Result, python::pool::attach, transform::Transform};
pub(crate) enum PyItem {
Object(Py<PyAny>),
Native(Value),
}
impl PyItem {
pub(crate) fn into_object(self, py: Python<'_>) -> PyResult<Py<PyAny>> {
match self {
Self::Object(object) => Ok(object),
Self::Native(value) => object_of(py, value),
}
}
}
pub(crate) enum PyTransform {
Identity,
Function(Py<PyAny>),
Native(Arc<[Op]>),
}
impl PyTransform {
pub(crate) fn new(transform: Option<&Bound<'_, PyAny>>) -> Self {
match transform {
None => Self::Identity,
Some(t) => match t.cast::<PyImageTransform>() {
Ok(native) => Self::Native(native.get().ops()),
Err(_) => Self::Function(t.clone().unbind()),
},
}
}
pub(crate) fn function(&self) -> Option<&Py<PyAny>> {
match self {
Self::Function(f) => Some(f),
_ => None,
}
}
pub(crate) fn clone_ref(&self, py: Python<'_>) -> Self {
match self {
Self::Identity => Self::Identity,
Self::Function(f) => Self::Function(f.clone_ref(py)),
Self::Native(ops) => Self::Native(Arc::clone(ops)),
}
}
pub(crate) fn call(&self, py: Python<'_>, index: usize, item: Py<PyAny>) -> PyResult<PyItem> {
match self {
Self::Identity => Ok(PyItem::Object(item)),
Self::Function(f) => Ok(PyItem::Object(f.call1(py, (index, item))?)),
Self::Native(ops) => {
let value = value_of(item.bind(py))?;
let value = py
.detach(|| apply_all(ops, index, value))
.map_err(into_py_err)?;
Ok(PyItem::Native(value))
}
}
}
}
impl Transform<Py<PyAny>> for PyTransform {
type Output = PyItem;
fn apply(&self, index: usize, item: Py<PyAny>) -> Result<PyItem> {
match self {
Self::Identity => Ok(PyItem::Object(item)),
Self::Function(f) => Ok(PyItem::Object(attach(|py| f.call1(py, (index, item)))?)),
Self::Native(ops) => {
let value = attach(|py| value_of(item.bind(py)))?;
Ok(PyItem::Native(apply_all(ops, index, value)?))
}
}
}
fn is_identity(&self) -> bool {
matches!(self, Self::Identity)
}
}
impl Transform<Bytes> for PyTransform {
type Output = PyItem;
fn apply(&self, index: usize, item: Bytes) -> Result<PyItem> {
if let Self::Native(ops) = self {
return Ok(PyItem::Native(apply_all(ops, index, Value::Bytes(item))?));
}
Ok(PyItem::Object(attach(|py| {
let item = PyBytes::new(py, &item).into_any().unbind();
self.call(py, index, item)
.and_then(|item| item.into_object(py))
})?))
}
}