use crate::{Bool, Driver, Object, OnscreenTemplate, Output, RendererConstraint, WinsysID};
use ffi;
use glib;
use glib::translate::*;
use std::{fmt, ptr};

glib_wrapper! {
    pub struct Renderer(Object<ffi::CoglRenderer, RendererClass>) @extends Object;

    match fn {
        get_type => || ffi::cogl_renderer_get_gtype(),
    }
}

impl Renderer {
    /// Instantiates a new (unconnected) `Renderer` object. A
    /// `Renderer` represents a means to render. It encapsulates the
    /// selection of an underlying driver, such as OpenGL or OpenGL-ES and
    /// a selection of a window system binding API such as GLX, or EGL or
    /// WGL.
    ///
    /// While the renderer is unconnected it can be configured so that
    /// applications may specify backend constraints, such as "must use
    /// x11" for example via `Renderer::add_constraint`.
    ///
    /// There are also some platform specific configuration apis such
    /// as `cogl_xlib_renderer_set_foreign_display` that may also be
    /// used while the renderer is unconnected.
    ///
    /// Once the renderer has been configured, then it may (optionally) be
    /// explicitly connected using `Renderer::connect` which allows
    /// errors to be handled gracefully and potentially fallback
    /// configurations can be tried out if there are initial failures.
    ///
    /// If a renderer is not explicitly connected then `Display::new`
    /// will automatically connect the renderer for you. If you don't
    /// have any code to deal with error/fallback situations then its fine
    /// to just let Cogl do the connection for you.
    ///
    /// Once you have setup your renderer then the next step is to create a
    /// `Display` using `Display::new`.
    ///
    /// `<note>`Many applications don't need to explicitly use
    /// `Renderer::new` or `Display::new` and can just jump
    /// straight to `Context::new` and pass a `None` display argument
    /// so Cogl will automatically connect and setup a renderer and
    /// display.`</note>`
    ///
    /// # Returns
    ///
    /// A newly created `Renderer`.
    pub fn new() -> Renderer {
        assert_initialized_main_thread!();
        unsafe { from_glib_full(ffi::cogl_renderer_new()) }
    }

    /// This adds a renderer selection `constraint`.
    ///
    /// Applications should ideally minimize how many of these constraints they
    /// depend on to ensure maximum portability.
    /// ## `constraint`
    /// A `RendererConstraint` to add
    pub fn add_constraint(&self, constraint: RendererConstraint) {
        unsafe {
            ffi::cogl_renderer_add_constraint(self.to_glib_none().0, constraint.to_glib());
        }
    }

    /// Tests if a given `onscreen_template` can be supported with the given
    /// `self`.
    /// ## `onscreen_template`
    /// A `OnscreenTemplate`
    ///
    /// # Returns
    ///
    /// `true` if the `onscreen_template` can be supported,
    ///  else `false`.
    pub fn check_onscreen_template(
        &self,
        onscreen_template: &OnscreenTemplate,
    ) -> Result<Bool, glib::Error> {
        unsafe {
            let mut error = ptr::null_mut();
            let ret = ffi::cogl_renderer_check_onscreen_template(
                self.to_glib_none().0,
                onscreen_template.to_glib_none().0,
                &mut error,
            );
            if error.is_null() {
                Ok(ret)
            } else {
                Err(from_glib_full(error))
            }
        }
    }

    /// Connects the configured `self`. Renderer connection isn't a
    /// very active process, it basically just means validating that
    /// any given constraint criteria can be satisfied and that a
    /// usable driver and window system backend can be found.
    ///
    /// # Returns
    ///
    /// `true` if there was no error while connecting the
    ///  given `self`. `false` if there was an error.
    pub fn connect(&self) -> Result<Bool, glib::Error> {
        unsafe {
            let mut error = ptr::null_mut();
            let ret = ffi::cogl_renderer_connect(self.to_glib_none().0, &mut error);
            if error.is_null() {
                Ok(ret)
            } else {
                Err(from_glib_full(error))
            }
        }
    }

    /// Iterates all known display outputs for the given `self` and
    /// passes a corresponding `Output` pointer to the given `callback`
    /// for each one, along with the given `user_data`.
    /// ## `callback`
    /// A `CoglOutputCallback` to be called for
    ///  each display output
    /// ## `user_data`
    /// A user pointer to be passed to `callback`
    pub fn foreach_output<P: FnMut(&Output)>(&self, callback: P) {
        let callback_data: P = callback;
        unsafe extern "C" fn callback_func<P: FnMut(&Output)>(
            output: *mut ffi::CoglOutput,
            user_data: glib_sys::gpointer,
        ) {
            let output = from_glib_borrow(output);
            let callback: *mut P = user_data as *const _ as usize as *mut P;
            (*callback)(&output);
        }
        let callback = Some(callback_func::<P> as _);
        let super_callback0: &P = &callback_data;
        unsafe {
            ffi::cogl_renderer_foreach_output(
                self.to_glib_none().0,
                callback,
                super_callback0 as *const _ as usize as *mut _,
            );
        }
    }

    /// Queries what underlying driver is being used by Cogl.
    ///
    /// This may only be called on a connected `Renderer`.
    pub fn get_driver(&self) -> Driver {
        unsafe { from_glib(ffi::cogl_renderer_get_driver(self.to_glib_none().0)) }
    }

    /// Queries how many texture units can be used from fragment programs
    ///
    /// # Returns
    ///
    /// the number of texture image units.
    pub fn get_n_fragment_texture_units(&self) -> i32 {
        unsafe { ffi::cogl_renderer_get_n_fragment_texture_units(self.to_glib_none().0) }
    }

    /// Queries which window system backend Cogl has chosen to use.
    ///
    /// This may only be called on a connected `Renderer`.
    ///
    /// # Returns
    ///
    /// The `WinsysID` corresponding to the chosen window
    ///  system backend.
    pub fn get_winsys_id(&self) -> WinsysID {
        unsafe { from_glib(ffi::cogl_renderer_get_winsys_id(self.to_glib_none().0)) }
    }

    /// This removes a renderer selection `constraint`.
    ///
    /// Applications should ideally minimize how many of these constraints they
    /// depend on to ensure maximum portability.
    /// ## `constraint`
    /// A `RendererConstraint` to remove
    pub fn remove_constraint(&self, constraint: RendererConstraint) {
        unsafe {
            ffi::cogl_renderer_remove_constraint(self.to_glib_none().0, constraint.to_glib());
        }
    }

    /// Requests that Cogl should try to use a specific underlying driver
    /// for rendering.
    ///
    /// If you select an unsupported driver then `Renderer::connect`
    /// will fail and report an error. Most applications should not
    /// explicitly select a driver and should rely on Cogl automatically
    /// choosing the driver.
    ///
    /// This may only be called on an un-connected `Renderer`.
    pub fn set_driver(&self, driver: Driver) {
        unsafe {
            ffi::cogl_renderer_set_driver(self.to_glib_none().0, driver.to_glib());
        }
    }

    /// This allows you to explicitly select a winsys backend to use instead
    /// of letting Cogl automatically select a backend.
    ///
    /// if you select an unsupported backend then `Renderer::connect`
    /// will fail and report an error.
    ///
    /// This may only be called on an un-connected `Renderer`.
    /// ## `winsys_id`
    /// An ID of the winsys you explicitly want to use.
    pub fn set_winsys_id(&self, winsys_id: WinsysID) {
        unsafe {
            ffi::cogl_renderer_set_winsys_id(self.to_glib_none().0, winsys_id.to_glib());
        }
    }
}

impl Default for Renderer {
    fn default() -> Self {
        Self::new()
    }
}

impl fmt::Display for Renderer {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Renderer")
    }
}
