use super::backward_references::UnionHasher;
use alloc::{Allocator, SliceWrapper};
use core::marker::PhantomData;
use core::mem;
use enc::threading::{
    BatchSpawnable, BatchSpawnableLite, BrotliEncoderThreadError, CompressMulti,
    CompressionThreadResult, InternalOwned, InternalSendAlloc, Joinable, Owned, OwnedRetriever,
    PoisonedThreadError, SendAlloc,
};
use enc::BrotliAlloc;
use enc::BrotliEncoderParams;

pub struct SingleThreadedJoinable<T: Send + 'static, U: Send + 'static> {
    result: Result<T, U>,
}
impl<T: Send + 'static, U: Send + 'static> Joinable<T, U> for SingleThreadedJoinable<T, U> {
    fn join(self) -> Result<T, U> {
        self.result
    }
}
#[cfg(feature = "std")]
pub struct SingleThreadedOwnedRetriever<U: Send + 'static>(std::sync::RwLock<U>);
#[cfg(feature = "std")]
impl<U: Send + 'static> OwnedRetriever<U> for SingleThreadedOwnedRetriever<U> {
    fn view<T, F: FnOnce(&U) -> T>(&self, f: F) -> Result<T, PoisonedThreadError> {
        Ok(f(&*self.0.read().unwrap()))
    }
    fn unwrap(self) -> Result<U, PoisonedThreadError> {
        Ok(self.0.into_inner().unwrap())
    }
}
#[cfg(feature = "std")]
impl<U: Send + 'static> SingleThreadedOwnedRetriever<U> {
    fn new(u: U) -> Self {
        SingleThreadedOwnedRetriever(std::sync::RwLock::new(u))
    }
}

#[cfg(not(feature = "std"))]
pub struct SingleThreadedOwnedRetriever<U: Send + 'static>(U);
#[cfg(not(feature = "std"))]
impl<U: Send + 'static> SingleThreadedOwnedRetriever<U> {
    fn new(u: U) -> Self {
        SingleThreadedOwnedRetriever(u)
    }
}
#[cfg(not(feature = "std"))]
impl<U: Send + 'static> OwnedRetriever<U> for SingleThreadedOwnedRetriever<U> {
    fn view<T, F: FnOnce(&U) -> T>(&self, f: F) -> Result<T, PoisonedThreadError> {
        Ok(f(&self.0))
    }
    fn unwrap(self) -> Result<U, PoisonedThreadError> {
        Ok(self.0)
    }
}

#[derive(Default)]
pub struct SingleThreadedSpawner {}

impl<
        ReturnValue: Send + 'static,
        ExtraInput: Send + 'static,
        Alloc: BrotliAlloc + Send + 'static,
        U: Send + 'static + Sync,
    > BatchSpawnable<ReturnValue, ExtraInput, Alloc, U> for SingleThreadedSpawner
where
    <Alloc as Allocator<u8>>::AllocatedMemory: Send + 'static,
{
    type JoinHandle = SingleThreadedJoinable<ReturnValue, BrotliEncoderThreadError>;
    type FinalJoinHandle = SingleThreadedOwnedRetriever<U>;
    fn make_spawner(&mut self, input: &mut Owned<U>) -> Self::FinalJoinHandle {
        SingleThreadedOwnedRetriever::<U>::new(
            mem::replace(input, Owned(InternalOwned::Borrowed)).unwrap(),
        )
    }
    fn spawn<F: Fn(ExtraInput, usize, usize, &U, Alloc) -> ReturnValue + Send + 'static + Copy>(
        &mut self,
        handle: &mut Self::FinalJoinHandle,
        work: &mut SendAlloc<ReturnValue, ExtraInput, Alloc, Self::JoinHandle>,
        index: usize,
        num_threads: usize,
        f: F,
    ) {
        let (alloc, extra_input) = work.replace_with_default();
        let ret = handle.view(|sub_view| f(extra_input, index, num_threads, sub_view, alloc));
        *work = SendAlloc(InternalSendAlloc::Join(SingleThreadedJoinable {
            result: Ok(ret.unwrap()),
        }));
    }
}

impl<
        ReturnValue: Send + 'static,
        ExtraInput: Send + 'static,
        Alloc: BrotliAlloc + Send + 'static,
        U: Send + 'static + Sync,
    > BatchSpawnableLite<ReturnValue, ExtraInput, Alloc, U> for SingleThreadedSpawner
where
    <Alloc as Allocator<u8>>::AllocatedMemory: Send + 'static,
{
    type JoinHandle =
        <SingleThreadedSpawner as BatchSpawnable<ReturnValue, ExtraInput, Alloc, U>>::JoinHandle;
    type FinalJoinHandle = <SingleThreadedSpawner as BatchSpawnable<
        ReturnValue,
        ExtraInput,
        Alloc,
        U,
    >>::FinalJoinHandle;

    fn make_spawner(&mut self, input: &mut Owned<U>) -> Self::FinalJoinHandle {
        <Self as BatchSpawnable<ReturnValue, ExtraInput, Alloc, U>>::make_spawner(self, input)
    }
    fn spawn(
        &mut self,
        handle: &mut Self::FinalJoinHandle,
        alloc_per_thread: &mut SendAlloc<ReturnValue, ExtraInput, Alloc, Self::JoinHandle>,
        index: usize,
        num_threads: usize,
        f: fn(ExtraInput, usize, usize, &U, Alloc) -> ReturnValue,
    ) {
        <Self as BatchSpawnable<ReturnValue, ExtraInput, Alloc, U>>::spawn(
            self,
            handle,
            alloc_per_thread,
            index,
            num_threads,
            f,
        )
    }
}

pub fn compress_multi<
    Alloc: BrotliAlloc + Send + 'static,
    SliceW: SliceWrapper<u8> + Send + 'static + Sync,
>(
    params: &BrotliEncoderParams,
    owned_input: &mut Owned<SliceW>,
    output: &mut [u8],
    alloc_per_thread: &mut [SendAlloc<
        CompressionThreadResult<Alloc>,
        UnionHasher<Alloc>,
        Alloc,
        <SingleThreadedSpawner as BatchSpawnable<
            CompressionThreadResult<Alloc>,
            UnionHasher<Alloc>,
            Alloc,
            SliceW,
        >>::JoinHandle,
    >],
) -> Result<usize, BrotliEncoderThreadError>
where
    <Alloc as Allocator<u8>>::AllocatedMemory: Send,
    <Alloc as Allocator<u16>>::AllocatedMemory: Send,
    <Alloc as Allocator<u32>>::AllocatedMemory: Send,
{
    CompressMulti(
        params,
        owned_input,
        output,
        alloc_per_thread,
        &mut SingleThreadedSpawner::default(),
    )
}

pub struct WorkerPool<A, B, C, D> {
    a: PhantomData<A>,
    b: PhantomData<B>,
    c: PhantomData<C>,
    d: PhantomData<D>,
}
pub fn new_work_pool<A, B, C, D>(_num_threads: usize) -> WorkerPool<A, B, C, D> {
    WorkerPool::<A, B, C, D> {
        a: PhantomData,
        b: PhantomData,
        c: PhantomData,
        d: PhantomData,
    }
}

pub fn compress_worker_pool<
    Alloc: BrotliAlloc + Send + 'static,
    SliceW: SliceWrapper<u8> + Send + 'static + Sync,
>(
    params: &BrotliEncoderParams,
    owned_input: &mut Owned<SliceW>,
    output: &mut [u8],
    alloc_per_thread: &mut [SendAlloc<
        CompressionThreadResult<Alloc>,
        UnionHasher<Alloc>,
        Alloc,
        <SingleThreadedSpawner as BatchSpawnable<
            CompressionThreadResult<Alloc>,
            UnionHasher<Alloc>,
            Alloc,
            SliceW,
        >>::JoinHandle,
    >],
    _worker_pool: &mut WorkerPool<
        CompressionThreadResult<Alloc>,
        UnionHasher<Alloc>,
        Alloc,
        (SliceW, BrotliEncoderParams),
    >,
) -> Result<usize, BrotliEncoderThreadError>
where
    <Alloc as Allocator<u8>>::AllocatedMemory: Send,
    <Alloc as Allocator<u16>>::AllocatedMemory: Send,
    <Alloc as Allocator<u32>>::AllocatedMemory: Send,
{
    compress_multi(params, owned_input, output, alloc_per_thread)
}
