use std::ffi::NulError;
use std::io::Error as IoError;
use std::str::Utf8Error;

use imagefmt::Error as ImageFmtError;
use thiserror::Error;

#[derive(Debug, Error)]
pub enum Error {
    #[error("IO Error: {0}")]
    IoError(IoError),

    #[error("UTF-8 Error: {0}")]
    Utf8Error(Utf8Error),

    #[error("FFI Error: {0}")]
    NulError(NulError),

    #[error("Image Format Error: {0}")]
    ImageFmtError(ImageFmtError),

    #[error("OpenGL Error: {0}")]
    GlError(gl::GLenum),

    #[error("Error while compiling shader object\n{code:}\n{error:}")]
    ShaderCompile { code: String, error: String },

    #[error("Error while linking shader program: {0}")]
    ShaderLink(String),

    #[error("Error while resolving include: {0}")]
    ShaderInclude(String),

    #[error("Unknown Uniform: {0}")]
    ShaderUnknownUniform(String),

    #[error("Vertex Array: Index is already in use: {0}!")]
    VertexArrayIndexAlreadyInUse(gl::GLuint),

    #[error("Vertex Array: Expected pointer!")]
    VertexArrayExpectedPointer,

    #[error("Invalid Parameter!")]
    InvalidParameter,

    #[error("Texture Buffer is to small!")]
    TextureBufferOverflow,

    #[error("Texture Unsupported Format!")]
    TextureUnsupportedFormat,
}

impl Error {
    pub fn err_if<T>(err: &T, value: T) -> Result<T, Error>
    where
        T: PartialEq<T>,
    {
        if value.eq(err) {
            Err(Error::GlError(gl::get_error()))
        } else {
            Ok(value)
        }
    }

    pub fn checked<F, T>(f: F) -> Result<T, Self>
    where
        F: FnOnce() -> T,
    {
        gl::get_error();

        let ret = f();

        match gl::get_error() {
            0 => Ok(ret),
            err => Err(Self::GlError(err)),
        }
    }
}

impl From<IoError> for Error {
    fn from(err: IoError) -> Self {
        Self::IoError(err)
    }
}

impl From<Utf8Error> for Error {
    fn from(err: Utf8Error) -> Self {
        Self::Utf8Error(err)
    }
}

impl From<NulError> for Error {
    fn from(err: NulError) -> Self {
        Self::NulError(err)
    }
}

impl From<ImageFmtError> for Error {
    fn from(err: ImageFmtError) -> Self {
        Self::ImageFmtError(err)
    }
}
