use std::{
    fs,
    ops::Deref,
    path::{Path, PathBuf},
};

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>>, cahe_path: PathBuf) -> R<Self> {
        let swith_value = Self::load_new(call.clone(), cahe_path)?;
        let drop = Self {
            action: true,
            inner: call,
            swith_value: Some(swith_value),
        };
        Ok(drop)
    }

    // load new dll is name
    pub fn dll_name(&self) -> String {
        crate::types::get_file_name(self.inner.path.as_str())
            .unwrap_or_else(|_| "".to_string())
            .to_string()
    }

    // 取消本次加载
    pub fn cancel(&mut self) {
        self.action = false;
        debug!("Cancel load new dll");
    }

    pub(crate) fn load_new(
        _self: UA<HotloadInner<API>>,
        mut f_new: PathBuf,
    ) -> R<(usize, Container<API>)> {
        let inner = _self.borrow_mut();
        let mut load_index = inner.load_index + 1;
        if load_index >= MAX_LOAD_INDEX {
            load_index = 0;
        }

        // 当前exe文件名
        let exe_name = std::env::current_exe().ok();
        let exe_name = exe_name
            .as_ref()
            .and_then(|x| x.file_name())
            .and_then(|x| x.to_str())
            .unwrap_or_else(|| "unknown.exe");
        let ext_index = exe_name.rfind('.').unwrap_or_else(|| exe_name.len());
        let exe_name = &exe_name[..ext_index];

        // 加载新的
        // 获得文件名. 重命名, 在名称中加入后缀 ".下标" 重命名;
        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_{exe_name}_{f_name}", load_index);
        // f_new.set_file_name(new_name);
        f_new.push(new_name);

        // current file is being created, waiting for 100ms;
        let mut copy_r = Ok(0);
        for i in 1..=10 {
            copy_r = fs::copy(f_old, f_new.clone());
            if copy_r.is_ok() {
                break;
            }
            let t = 500 * i;
            warn!("fs::copy({f_old:?}, {f_new:?}): {copy_r:?}; current file is being created, waiting for {t}ms, please wait");
            std::thread::sleep(std::time::Duration::from_millis(t));
        }
        copy_r?;

        let c: Container<API> = unsafe { Container::load(&f_new) }?;
        debug!("load new dll start: {f_name} > {f_new:?}");
        // inner.container[load_index] = Some(c);
        // // 添加索引即生效
        // inner.load_index = load_index;
        // debug!("load new container: {load_index}");

        Ok((load_index, c))
    }

    fn swith(&mut self) {
        if !self.action {
            return;
        }
        let inner = self.inner.borrow_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.borrow_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.borrow_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()
    }
}
