use super::waker::*;
use futures::future::LocalBoxFuture;
use futures::task::{Context, Poll};
use hashbrown::HashMap;
use parking_lot::RwLock;
use std::any::{Any, TypeId};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;

#[derive(Clone, Debug)]
pub(crate) struct DoneSignal(pub Arc<AtomicBool>);
impl DoneSignal {
    #[inline]
    pub fn new() -> Self {
        Self::from_bool(false)
    }

    #[inline]
    pub fn from_bool(value: bool) -> Self {
        Self(Arc::new(AtomicBool::new(value)))
    }

    #[inline]
    pub fn done(&mut self) {
        self.0.store(true, Ordering::SeqCst);
    }

    #[inline]
    pub fn is_done(&self) -> bool {
        self.0.load(Ordering::SeqCst)
    }
}

pub(crate) struct LoadWrapper {
    fut: LocalBoxFuture<'static, Result<Vec<u8>, String>>,
    pub loaded: DoneSignal,
    pub type_id: TypeId,
    id: String,
}

impl LoadWrapper {
    pub fn new(
        id: &str,
        fut: LocalBoxFuture<'static, Result<Vec<u8>, String>>,
        type_id: TypeId,
    ) -> Self {
        Self {
            id: id.to_string(),
            fut,
            loaded: DoneSignal::new(),
            type_id,
        }
    }

    pub fn try_load(&mut self) -> Option<Vec<u8>> {
        let waker = DummyWaker.into_task_waker();
        let mut ctx = Context::from_waker(&waker);
        match self.fut.as_mut().poll(&mut ctx) {
            Poll::Ready(r_buff) => match r_buff {
                Ok(buff) => Some(buff),
                Err(err) => {
                    log::error!("File: {} -> {}", self.id, err);
                    None
                }
            },
            _ => None,
        }
    }

    #[inline(always)]
    pub fn is_loaded(&self) -> bool {
        self.loaded.is_done()
    }
}

#[derive(Clone)]
pub(crate) struct LoadTracker {
    pub loaded: DoneSignal,
    pub asset: Arc<dyn Any + Send + Sync>,
}

impl LoadTracker {
    pub fn is_loaded(&self) -> bool {
        self.loaded.is_done()
    }
}

struct ClaimTracker {
    tracker: LoadTracker,
    claim: bool,
}

impl ClaimTracker {
    fn is_ready(&self) -> bool {
        self.tracker.is_loaded() && self.claim
    }
}

#[derive(Default, Clone)]
pub(crate) struct AssetLoadTracker {
    assets: Arc<RwLock<HashMap<String, ClaimTracker>>>,
}

impl AssetLoadTracker {
    pub fn insert_if_necessary<A>(&self, id: &str, loaded: DoneSignal)
    where
        A: Send + Sync + 'static,
    {
        self.assets
            .write()
            .entry(id.to_string())
            .or_insert_with(|| {
                let asset: Arc<RwLock<Option<A>>> = Arc::new(RwLock::new(None));
                let tracker = LoadTracker { loaded, asset };

                ClaimTracker {
                    tracker,
                    claim: false,
                }
            });
    }

    pub fn get_asset<A>(
        &self,
        id: &str,
        loaded: DoneSignal,
    ) -> Result<Arc<RwLock<Option<A>>>, String>
    where
        A: Send + Sync + 'static,
    {
        self.insert_if_necessary::<A>(id, loaded);
        self.assets
            .read()
            .get(id)
            .unwrap()
            .tracker
            .asset
            .clone()
            .downcast::<RwLock<Option<A>>>()
            .map_err(|_| "Invalid asset type".to_string())
    }

    pub fn claim_asset<A>(
        &self,
        id: &str,
        loaded: DoneSignal,
    ) -> Result<Arc<RwLock<Option<A>>>, String>
    where
        A: Send + Sync + 'static,
    {
        self.insert_if_necessary::<A>(id, loaded);
        let mut assets = self.assets.write();
        let tracker = assets.get_mut(id).unwrap();
        tracker.claim = true;
        tracker
            .tracker
            .asset
            .clone()
            .downcast::<RwLock<Option<A>>>()
            .map_err(|_| "Invalid asset type".to_string())
    }

    #[inline]
    pub fn clean(&mut self) {
        self.assets.write().retain(|_, tracker| !tracker.is_ready());
    }
}
