use {
    crate::open,
    std::{fmt::Write as FmtWrite, io::Write},
};

fn write_egl_procs<W: Write>(f: &mut W) -> anyhow::Result<()> {
    let map = [
        (
            "eglGetPlatformDisplayEXT",
            "EGLDisplay",
            &[
                ("platform", "EGLenum"),
                ("native_display", "*mut u8"),
                ("attrib_list", "*const EGLint"),
            ][..],
        ),
        (
            "eglCreateImageKHR",
            "EGLImageKHR",
            &[
                ("dpy", "EGLDisplay"),
                ("ctx", "EGLContext"),
                ("target", "EGLenum"),
                ("buffer", "EGLClientBuffer"),
                ("attrib_list", "*const EGLint"),
            ][..],
        ),
        (
            "eglDestroyImageKHR",
            "EGLBoolean",
            &[("dpy", "EGLDisplay"), ("image", "EGLImageKHR")][..],
        ),
        (
            "eglQueryDmaBufFormatsEXT",
            "EGLBoolean",
            &[
                ("dpy", "EGLDisplay"),
                ("max_formats", "EGLint"),
                ("formats", "*mut EGLint"),
                ("num_formats", "*mut EGLint"),
            ][..],
        ),
        (
            "eglQueryDmaBufModifiersEXT",
            "EGLBoolean",
            &[
                ("dpy", "EGLDisplay"),
                ("format", "EGLint"),
                ("max_modifiers", "EGLint"),
                ("modifiers", "*mut EGLuint64KHR"),
                ("external_only", "*mut EGLBoolean"),
                ("num_modifiers", "*mut EGLint"),
            ][..],
        ),
        (
            "eglDebugMessageControlKHR",
            "EGLint",
            &[
                ("callback", "EGLDEBUGPROCKHR"),
                ("attrib_list", "*const EGLAttrib"),
            ][..],
        ),
        (
            "eglQueryDisplayAttribEXT",
            "EGLBoolean",
            &[
                ("dpy", "EGLDisplay"),
                ("attribute", "EGLint"),
                ("value", "*mut EGLAttrib"),
            ][..],
        ),
        (
            "glEGLImageTargetRenderbufferStorageOES",
            "()",
            &[("target", "GLenum"), ("image", "GLeglImageOES")][..],
        ),
        (
            "glEGLImageTargetTexture2DOES",
            "()",
            &[("target", "GLenum"), ("image", "GLeglImageOES")][..],
        ),
        ("glGetGraphicsResetStatusKHR", "GLenum", &[][..]),
        (
            "eglCreateSyncKHR",
            "EGLSyncKHR",
            &[
                ("dpy", "EGLDisplay"),
                ("ty", "EGLenum"),
                ("attrib_list", "*const EGLint"),
            ][..],
        ),
        (
            "eglDestroySyncKHR",
            "EGLBoolean",
            &[("dpy", "EGLDisplay"), ("sync", "EGLSyncKHR")][..],
        ),
        (
            "eglDupNativeFenceFDANDROID",
            "EGLint",
            &[("dpy", "EGLDisplay"), ("sync", "EGLSyncKHR")][..],
        ),
        (
            "eglWaitSyncKHR",
            "EGLint",
            &[
                ("dpy", "EGLDisplay"),
                ("sync", "EGLSyncKHR"),
                ("flags", "EGLint"),
            ][..],
        ),
        (
            "eglQueryDeviceStringEXT",
            "*const c::c_char",
            &[("device", "EGLDeviceEXT"), ("name", "EGLint")][..],
        ),
    ];

    writeln!(f, "use std::ptr;")?;
    writeln!(f, "use super::gl::sys::*;")?;
    writeln!(f, "use super::egl::sys::*;")?;
    writeln!(f)?;
    writeln!(f, "#[derive(Copy, Clone, Debug)]")?;
    writeln!(f, "pub struct ExtProc {{")?;
    for (name, _, _) in map.iter() {
        writeln!(f, "    {}: *mut u8,", name)?;
    }
    writeln!(f, "}}")?;
    writeln!(f)?;
    writeln!(f, "unsafe impl Sync for ExtProc {{ }}")?;
    writeln!(f, "unsafe impl Send for ExtProc {{ }}")?;
    writeln!(f)?;
    writeln!(f, "impl ExtProc {{")?;
    writeln!(f, "    pub fn load() -> Option<Self> {{")?;
    writeln!(f, "        let egl = EGL.as_ref()?;")?;
    writeln!(f, "        Some(Self {{")?;
    for (name, _, _) in map.iter().copied() {
        writeln!(
            f,
            "            {}: unsafe {{ (egl.eglGetProcAddress)(c\"{}\".as_ptr() as _) }},",
            name, name
        )?;
    }
    writeln!(f, "        }})")?;
    writeln!(f, "    }}")?;
    for (name, ret, args) in map.iter().copied() {
        let mut args_names = String::new();
        let mut args_full = String::new();
        let mut args_tys = String::new();
        for (name, ty) in args.iter().copied() {
            write!(args_full, "{}: {}, ", name, ty)?;
            write!(args_names, "{}, ", name)?;
            write!(args_tys, "{}, ", ty)?;
        }
        writeln!(f)?;
        writeln!(
            f,
            "    pub(super) unsafe fn {}(&self, {}) -> {} {{",
            name, args_full, ret
        )?;
        writeln!(f, "       if self.{}.is_null() {{", name)?;
        writeln!(f, "           panic!(\"Could not load `{}`\");", name)?;
        writeln!(f, "       }}")?;
        writeln!(f, "       unsafe {{")?;
        writeln!(
            f,
            r#"           ptr::read(&self.{} as *const *mut u8 as *const unsafe extern "C" fn({}) -> {})({})"#,
            name, args_tys, ret, args_names
        )?;
        writeln!(f, "       }}")?;
        writeln!(f, "    }}")?;
    }
    writeln!(f, "}}")?;
    Ok(())
}

pub fn main() -> anyhow::Result<()> {
    let mut f = open("egl_procs.rs")?;
    write_egl_procs(&mut f)?;

    Ok(())
}
