use std::sync::Arc;
use std::any::TypeId;
use std::collections::HashMap;
use std::cell::{RefCell, UnsafeCell};
use std::hash::{BuildHasherDefault, Hasher};
use join::Join;
use coroutine_impl::Coroutine;
use generator::get_local_data;

// thread local map storage
thread_local!{static LOCALMAP: LocalMap = RefCell::new(HashMap::default());}


/// coroutine local storage
pub struct CoroutineLocal {
    // current coroutine handle
    co: Coroutine,
    // when panic happens, we need to trigger the join here
    join: Arc<UnsafeCell<Join>>,
    // real local data hash map
    local_data: LocalMap,
}

impl CoroutineLocal {
    /// create coroutine local storage
    pub fn new(co: Coroutine, join: Arc<UnsafeCell<Join>>) -> Box<Self> {
        Box::new(CoroutineLocal {
            co: co,
            join: join,
            local_data: RefCell::new(HashMap::default()),
        })
    }

    // get the coroutine handle
    pub fn get_co(&self) -> &Coroutine {
        &self.co
    }

    // get the join handle
    pub fn get_join(&self) -> Arc<UnsafeCell<Join>> {
        self.join.clone()
    }
}

fn with<F: FnOnce(&LocalMap) -> R, R>(f: F) -> R {
    let ptr = get_local_data();
    if ptr.is_null() {
        LOCALMAP.with(|data| f(data))
    } else {
        let local = unsafe { &*(ptr as *mut CoroutineLocal) };
        let data = &local.local_data;
        f(data)
    }
}

/// A macro to create a `static` of type `LocalKey`
///
/// This macro is intentionally similar to the `thread_local!`, and creates a
/// `static` which has a `with` method to access the data on a coroutine.
///
/// The data associated with each coroutine local is per-coroutine,
/// so different coroutines will contain different values.
#[macro_export]
macro_rules! coroutine_local {
    (static $NAME:ident: $t:ty = $e:expr) => (
        static $NAME: $crate::LocalKey<$t> = {
            fn __init() -> $t { $e }
            fn __key() -> ::std::any::TypeId {
                struct __A;
                ::std::any::TypeId::of::<__A>()
            }
            $crate::LocalKey {
                __init: __init,
                __key: __key,
            }
        };
    )
}

pub type LocalMap = RefCell<HashMap<TypeId, Box<Opaque>, BuildHasherDefault<IdHasher>>>;

pub trait Opaque {}
impl<T> Opaque for T {}

/// A key for local data stored in a coroutine.
///
/// This type is generated by the `coroutine_local!` macro and performs very
/// similarly to the `thread_local!` macro and `std::thread::LocalKey` types.
/// Data associated with a `LocalKey<T>` is stored inside of a coroutine,
/// and the data is destroyed when the coroutine is completed.
///
/// coroutine-local data requires the `'static` bound to ensure it lives long
/// enough. When a key is accessed for the first time the coroutine's data is
/// initialized with the provided initialization expression to the macro.
pub struct LocalKey<T> {
    // "private" fields which have to be public to get around macro hygiene, not
    // included in the stability story for this type. Can change at any time.
    #[doc(hidden)]
    pub __key: fn() -> TypeId,
    #[doc(hidden)]
    pub __init: fn() -> T,
}

pub struct IdHasher {
    id: u64,
}

impl Default for IdHasher {
    fn default() -> IdHasher {
        IdHasher { id: 0 }
    }
}

impl Hasher for IdHasher {
    fn write(&mut self, _bytes: &[u8]) {
        // TODO: need to do something sensible
        panic!("can only hash u64");
    }

    fn write_u64(&mut self, u: u64) {
        self.id = u;
    }

    fn finish(&self) -> u64 {
        self.id
    }
}

impl<T: 'static> LocalKey<T> {
    /// Access this coroutine-local key, running the provided closure with a
    /// reference to the value.
    ///
    /// This function will access this coroutine-local key to retrieve the data
    /// associated with the current coroutine and this key. If this is the first
    /// time this key has been accessed on this coroutine, then the key will be
    /// initialized with the initialization expression provided at the time the
    /// `coroutine_local!` macro was called.
    ///
    /// The provided closure will be provided a shared reference to the
    /// underlying data associated with this coroutine-local-key. The data itself
    /// is stored inside of the current coroutine.
    ///
    /// if it's not accessed in a coroutine context, it will use the thread local
    /// storage as a backend, so it's safe to use it in thread context
    ///
    /// # Panics
    ///
    /// This function can possibly panic for a number of reasons:
    ///
    /// * If the initialization expression is run and it panics
    /// * If the closure provided panics
    pub fn with<F, R>(&'static self, f: F) -> R
        where F: FnOnce(&T) -> R
    {
        let key = (self.__key)();
        with(|data| {
            let raw_pointer = {
                let mut data = data.borrow_mut();
                let entry = data.entry(key).or_insert_with(|| Box::new((self.__init)()));
                &**entry as *const Opaque as *const T
            };
            unsafe { f(&*raw_pointer) }
        })
    }
}
