use super::*;
use super::errors::*;

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum SyncStatus {
    AlreadySignaled,
    TimeoutExpired,
    ConditionSatisfied,
    WaitFailed,
}

impl From<SyncStatus> for u32 {
    fn from(sync_status: SyncStatus) -> Self {
        match sync_status {
            SyncStatus::AlreadySignaled => gl::ALREADY_SIGNALED,
            SyncStatus::TimeoutExpired => gl::TIMEOUT_EXPIRED,
            SyncStatus::ConditionSatisfied => gl::CONDITION_SATISFIED,
            SyncStatus::WaitFailed => gl::WAIT_FAILED,
        }
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum SyncFlushCommands {
    True,
    False,
}

impl From<SyncFlushCommands> for u32 {
    fn from(sync_status: SyncFlushCommands) -> Self {
        match sync_status {
            SyncFlushCommands::True => gl::SYNC_FLUSH_COMMANDS_BIT,
            SyncFlushCommands::False => 0,
        }
    }
}

bitflags! {
    pub struct MemoryBarriers: u32 {
        const VERTEX_ATTRIB_ARRAY_BARRIER = gl::VERTEX_ATTRIB_ARRAY_BARRIER_BIT;
        const GL_ELEMENT_ARRAY_BARRIER = gl::VERTEX_ATTRIB_ARRAY_BARRIER_BIT;
        const GL_UNIFORM_BARRIER = gl::VERTEX_ATTRIB_ARRAY_BARRIER_BIT;
        const GL_TEXTURE_FETCH_BARRIER = gl::VERTEX_ATTRIB_ARRAY_BARRIER_BIT;
        const GL_SHADER_IMAGE_ACCESS_BARRIER = gl::VERTEX_ATTRIB_ARRAY_BARRIER_BIT;
        const GL_COMMAND_BARRIER = gl::VERTEX_ATTRIB_ARRAY_BARRIER_BIT;
        const GL_PIXEL_BUFFER_BARRIER = gl::VERTEX_ATTRIB_ARRAY_BARRIER_BIT;
        const GL_TEXTURE_UPDATE_BARRIER = gl::VERTEX_ATTRIB_ARRAY_BARRIER_BIT;
        const GL_BUFFER_UPDATE_BARRIER = gl::VERTEX_ATTRIB_ARRAY_BARRIER_BIT;
        const GL_FRAMEBUFFER_BARRIER = gl::VERTEX_ATTRIB_ARRAY_BARRIER_BIT;
        const GL_TRANSFORM_FEEDBACK_BARRIER = gl::VERTEX_ATTRIB_ARRAY_BARRIER_BIT;
        const GL_ATOMIC_COUNTER_BARRIER = gl::VERTEX_ATTRIB_ARRAY_BARRIER_BIT;
        const GL_SHADER_STORAGE_BARRIER = gl::VERTEX_ATTRIB_ARRAY_BARRIER_BIT;
        const GL_QUERY_BUFFER_BARRIER = gl::VERTEX_ATTRIB_ARRAY_BARRIER_BIT;
        const GL_ALL_BARRIER_BITS = gl::VERTEX_ATTRIB_ARRAY_BARRIER_BIT;
    }
}

pub unsafe fn fence_sync() -> Result<gl::types::GLsync> {
    let result = gl::FenceSync(gl::SYNC_GPU_COMMANDS_COMPLETE, 0);

    if result.is_null() {
        bail!("Creation of sync object failed");
    }

    Ok(result)
}

pub unsafe fn delete_sync(sync_ptr: gl::types::GLsync) {
    gl::DeleteSync(sync_ptr);
}

pub unsafe fn wait_sync(sync_ptr: gl::types::GLsync) {
    gl::WaitSync(sync_ptr, 0, gl::TIMEOUT_IGNORED)
}

pub unsafe fn client_wait_sync(
    sync_ptr: gl::types::GLsync,
    flush_commands: SyncFlushCommands,
    timeout: u64,
) -> Result<SyncStatus> {
    let result: gl::types::GLenum = gl::ClientWaitSync(sync_ptr, flush_commands.into(), timeout);

    get_error(match result {
        gl::ALREADY_SIGNALED => SyncStatus::AlreadySignaled,
        gl::TIMEOUT_EXPIRED => SyncStatus::TimeoutExpired,
        gl::CONDITION_SATISFIED => SyncStatus::ConditionSatisfied,
        gl::WAIT_FAILED => SyncStatus::WaitFailed,
        _ => bail!("Unknown sync status"),
    })
}

pub unsafe fn memory_barrier(barriers: MemoryBarriers) {
    gl::MemoryBarrier(barriers.bits());
}
