use containers::collections::*;
use core::any::TypeId;
use core::{ptr, slice};
use core::ops::*;
use core::ptr::Unique;
use loca::*;

type Mask = usize;

pub struct ECS<A: Alloc> {
    alloc: A,
    masks: RawVec<Mask, A>,
    component_ptrs: HashTable<TypeId, *mut u8, ::sip::SipHasher, A>,
    droppers: Unique<fn(*mut u8, usize, &mut A)>
}

impl<A: Alloc + Clone> ECS<A> {
    #[inline]
    pub fn with_capacity_in(mut a: A, cap: usize) -> Option<Self> {
        let mut masks = RawVec::with_capacity_in(a.clone(), cap)?;
        for k in 0..cap { unsafe { masks.storage_mut()[k] = 0; } }
        let cps = HashTable::new_in(a.clone(), (0: Mask).trailing_zeros(), Default::default())?;
        let ds = a.alloc_array(cap).ok()?;
        Some(ECS { alloc: a, masks: masks, component_ptrs: cps, droppers: ds })
    }
}

impl<A: Alloc> ECS<A> {
    #[inline]
    pub fn reg<C: 'static>(&mut self) -> Option<()> {
        let alloc = &mut self.alloc;
        let cap = self.masks.capacity();
        let ptr: Unique<C> = alloc.alloc_array(cap).ok()?;
        match self.component_ptrs.insert_with(TypeId::of::<C>(), |p_opt| match p_opt {
            None => ptr.as_ptr() as _,
            Some(p) => unsafe { let _ = alloc.dealloc_array(ptr, cap); p },
        }).map_err(|(_, ptr)| ptr(None)) {
            Ok((k, _, _)) => unsafe {
                ptr::write(self.droppers.as_ptr().offset(k as _), drop_components::<C, A>);
                Some(())
            },
            Err(ptr) => unsafe {
                let _ = alloc.dealloc_array(Unique::new_unchecked(ptr), cap);
                None
            },
        }
    }

    #[inline]
    fn component<C: 'static>(&self) -> Option<(usize, *mut C)> {
        self.component_ptrs.find_with_ix(&TypeId::of::<C>()).map(|(k, _, &ptr)| (k, ptr as _))
    }

    #[inline]
    pub fn get<C: 'static>(&self, k: usize) -> Option<&C> {
        let (ck, ptr) = self.component()?;
        unsafe { if 0 != self.masks.storage()[k] & 1 << ck { ptr.offset(k as _).as_ref() }
                 else { None } }
    }

    #[inline]
    pub fn modify<C: 'static, F: FnOnce(Option<C>) -> Option<C>>(&mut self, k: usize, f: F) { unsafe {
        let (ck, ptr) = match self.component() { None => return, Some(x) => x };
        let ptr = ptr.offset(k as _);
        match f(if 0 == self.masks.storage()[k] & 1 << ck { None } else {
            self.masks.storage_mut()[k] &= !(1 << ck);
            Some(ptr::read(ptr))
        }) {
            None => (),
            Some(x) => {
                self.masks.storage_mut()[k] |= 1 << ck;
                ptr::write(ptr, x);
            },
        }
    } }
}

impl<A: Alloc> Drop for ECS<A> {
    fn drop(&mut self) {
        for (k, _, &ptr) in self.component_ptrs.iter_with_ix() {
            if let Some(f) = unsafe { self.droppers.as_ptr().offset(k as _).as_ref() } {
                f(ptr, self.masks.capacity(), &mut self.alloc);
            }
        }
    }
}

fn drop_components<C, A: Alloc>(ptr: *mut u8, n: usize, a: &mut A) { unsafe {
    let ptr = ptr as *mut C;
    for c in slice::from_raw_parts_mut(ptr, n) { ptr::drop_in_place(c); }
    let _ = a.dealloc_array(Unique::new_unchecked(ptr), n);
} }