use std::mem::ManuallyDrop;

use super::*;
use crate::prelude::*;

pub struct PolarsExtension {
    array: Option<FixedSizeBinaryArray>,
}

impl PolarsExtension {
    pub(crate) unsafe fn new(array: FixedSizeBinaryArray) -> Self {
        Self { array: Some(array) }
    }

    /// Take the Array hold by [`PolarsExtension`] and forget polars extension,
    /// so that drop is not called
    pub(crate) fn take_and_forget(self) -> FixedSizeBinaryArray {
        let mut md = ManuallyDrop::new(self);
        md.array.take().unwrap()
    }

    /// Apply a function with the sentinel, without the sentinels drop being called
    unsafe fn with_sentinel<T, F: FnOnce(&mut ExtensionSentinel) -> T>(&self, fun: F) -> T {
        let mut sentinel = self.get_sentinel();
        let out = fun(&mut sentinel);
        std::mem::forget(sentinel);
        out
    }

    /// Load the sentinel from the heap.
    /// be very careful, this dereferences a raw pointer on the heap,
    unsafe fn get_sentinel(&self) -> Box<ExtensionSentinel> {
        if let ArrowDataType::Extension(ext) = self.array.as_ref().unwrap().dtype() {
            let metadata = ext
                .metadata
                .as_ref()
                .expect("should have metadata in extension type");
            let mut iter = metadata.split(';');

            let pid = iter.next().unwrap().parse::<u128>().unwrap();
            let ptr = iter.next().unwrap().parse::<usize>().unwrap();
            if pid == *PROCESS_ID {
                Box::from_raw(ptr as *const ExtensionSentinel as *mut ExtensionSentinel)
            } else {
                panic!("pid did not mach process id")
            }
        } else {
            panic!("should be extension type")
        }
    }

    /// Calls the heap allocated function in the [`ExtensionSentinel`] that knows
    /// how to convert the [`FixedSizeBinaryArray`] to a `Series` of type [`ObjectChunked<T>`]
    pub(crate) unsafe fn get_series(&self, name: &PlSmallStr) -> Series {
        self.with_sentinel(|sent| {
            (sent.to_series_fn.as_ref().unwrap())(self.array.as_ref().unwrap(), name)
        })
    }

    // heap allocates a function that converts the binary array to a Series of [`ObjectChunked<T>`]
    // the `name` will be the `name` of the output `Series` when this function is called (later).
    pub(crate) unsafe fn set_to_series_fn<T: PolarsObject>(&mut self) {
        let f = Box::new(move |arr: &FixedSizeBinaryArray, name: &PlSmallStr| {
            let iter = arr.iter().map(|opt| {
                opt.map(|bytes| {
                    let t = std::ptr::read_unaligned(bytes.as_ptr() as *const T);

                    let ret = t.clone();
                    std::mem::forget(t);
                    ret
                })
            });

            let ca = ObjectChunked::<T>::from_iter_options(name.clone(), iter);
            ca.into_series()
        });
        self.with_sentinel(move |sent| {
            sent.to_series_fn = Some(f);
        });
    }
}

impl Drop for PolarsExtension {
    fn drop(&mut self) {
        // implicitly drop by taking ownership
        unsafe { self.get_sentinel() };
    }
}
