use std::{fs, ops::Deref, path::Path};
use dlopen2::wrapper::{Container, WrapperApi};
use crate::{
    types::{HotloadInner, MAX_LOAD_INDEX, UA},
    R,
};
pub struct EventNewDll<API: WrapperApi> {
    action: bool,
    inner: UA<HotloadInner<API>>,
    swith_value: Option<(usize, Container<API>)>,
}
impl<API: WrapperApi + 'static> Deref for EventNewDll<API> {
    type Target = Container<API>;
    fn deref(&self) -> &Self::Target {
        self.swith_value
            .as_ref()
            .map(|x| &x.1)
            .expect("New dll not loaded")
    }
}
impl<API: WrapperApi> EventNewDll<API> {
    pub(crate) fn new(call: UA<HotloadInner<API>>) -> R<Self> {
        let swith_value = Self::load_new(call.clone())?;
        let drop = Self {
            action: true,
            inner: call,
            swith_value: Some(swith_value),
        };
        Ok(drop)
    }
    pub fn cancel(&mut self) {
        self.action = false;
        debug!("Cancel load new dll");
    }
    pub(crate) fn load_new(_self: UA<HotloadInner<API>>) -> R<(usize, Container<API>)> {
        let inner = _self.get_mut();
        let mut load_index = inner.load_index + 1;
        if load_index >= MAX_LOAD_INDEX {
            load_index = 0;
        }
        let f_name = crate::types::get_file_name(&inner.path)?;
        let f_old: &Path = std::path::Path::new(&inner.path);
        let mut f_new = f_old.to_path_buf();
        let new_name = format!(".{}_hotload_{f_name}", load_index);
        f_new.set_file_name(new_name);
        fs::copy(f_old, f_new.clone())?;
        let c: Container<API> = unsafe { Container::load(&f_new) }?;
        debug!("load new dll start: {load_index}");
        Ok((load_index, c))
    }
    fn swith(&mut self) {
        if !self.action {
            return;
        }
        let inner = self.inner.get_mut();
        if let Some((load_index, c)) = self.swith_value.take() {
            inner.container[load_index] = Some(c);
            inner.load_index = load_index;
            debug!("load new dll: {load_index} complete!");
        }
    }
}
impl<API: WrapperApi> Drop for EventNewDll<API> {
    fn drop(&mut self) {
        self.swith();
    }
}
pub struct EventOldDll<API: WrapperApi> {
    action: bool,
    inner: UA<HotloadInner<API>>,
    cur_index: usize,
}
impl<API: WrapperApi + 'static> Deref for EventOldDll<API> {
    type Target = Container<API>;
    fn deref(&self) -> &Self::Target {
        let inner = &*self.inner;
        let inner = &inner.container[self.cur_index];
        inner.as_ref().expect("Old dll not loaded")
    }
}
impl<API: WrapperApi> EventOldDll<API> {
    pub fn new(call: UA<HotloadInner<API>>) -> Self {
        let cur_index = call.get_mut().load_index;
        let drop = Self {
            action: true,
            inner: call,
            cur_index,
        };
        drop
    }
    pub fn cancel(&mut self) {
        self.action = false;
        debug!("Cancel release old dll");
    }
    fn release_old(&self) {
        if !self.action {
            return;
        }
        let inner = self.inner.get_mut();
        let load_index = self.cur_index;
        let take_v = inner.container[load_index].take();
        if let Some(api) = take_v {
            debug!("release old dll {load_index} start, please wait");
            drop(api);
            debug!("release old dll {load_index} complete!");
        }
    }
}
impl<API: WrapperApi> Drop for EventOldDll<API> {
    fn drop(&mut self) {
        self.release_old()
    }
}