// Copyright 2015 Brendan Zabarauskas and the gl-rs developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

extern crate khronos_api;

use std::borrow::Cow;
use std::collections::hash_map::Entry;
use std::collections::BTreeSet;
use std::collections::HashSet;
use std::collections::HashMap;
use std::io;
use xml::attribute::OwnedAttribute;
use xml::EventReader as XmlEventReader;
use xml::reader::XmlEvent;

use {Fallbacks, Api, Profile};
use registry::{Binding, Cmd, Enum, GlxOpcode, Registry};

pub fn from_xml<R: io::Read>(src: R, filter: Filter) -> Registry {
    XmlEventReader::new(src).into_iter()
        .map(Result::unwrap)
        .filter_map(ParseEvent::from_xml)
        .parse(filter)
}

#[derive(Debug, PartialEq, Eq)]
struct Attribute {
    key: String,
    value: String,
}

impl Attribute {
    fn new<Key, Value>(key: Key, value: Value) -> Attribute where
        Key: ToString,
        Value: ToString,
    {
        Attribute {
            key: key.to_string(),
            value: value.to_string(),
        }
    }
}

impl From<OwnedAttribute> for Attribute {
    fn from(attribute: OwnedAttribute) -> Attribute {
        Attribute::new(attribute.name.local_name, attribute.value)
    }
}

#[derive(Debug, PartialEq, Eq)]
enum ParseEvent {
    Start(String, Vec<Attribute>),
    End(String),
    Text(String),
}

impl ParseEvent {
    fn from_xml(event: XmlEvent) -> Option<ParseEvent> {
        match event {
            XmlEvent::StartDocument { .. } => None,
            XmlEvent::EndDocument => None,
            XmlEvent::StartElement { name, attributes, .. } => {
                let attributes = attributes.into_iter()
                    .map(Attribute::from)
                    .collect();
                Some(ParseEvent::Start(name.local_name, attributes))
            },
            XmlEvent::EndElement { name } => Some(ParseEvent::End(name.local_name)),
            XmlEvent::Characters(chars) => Some(ParseEvent::Text(chars)),
            XmlEvent::ProcessingInstruction { .. } => None,
            XmlEvent::CData(_) => None,
            XmlEvent::Comment(_) => None,
            XmlEvent::Whitespace(_) => None,
        }
    }
}

fn api_from_str(src: &str) -> Result<Api, ()> {
    match src {
        "gl" => Ok(Api::Gl),
        "glx" => Ok(Api::Glx),
        "wgl" => Ok(Api::Wgl),
        "egl" => Ok(Api::Egl),
        "glcore" => Ok(Api::GlCore),
        "gles1" => Ok(Api::Gles1),
        "gles2" => Ok(Api::Gles2),
        _     => Err(()),
    }
}

fn profile_from_str(src: &str) -> Result<Profile, ()> {
    match src {
        "core" => Ok(Profile::Core),
        "compatibility" => Ok(Profile::Compatibility),
        _ => Err(()),
    }
}

fn underscore_numeric_prefix(src: &str) -> String {
    match src.chars().next() {
        Some(c) if c.is_numeric() => format!("_{}", src),
        Some(_) | None => src.to_string(),
    }
}

fn underscore_keyword(ident: String) -> String {
    match ident.as_ref() {
        "in" => "in_".to_string(),
        "ref" => "ref_".to_string(),
        "type" => "type_".to_string(),
        _ => ident,
    }
}

fn trim_str<'a>(s: &'a str, trim: &str) -> &'a str {
    if s.starts_with(trim) { &s[trim.len()..] } else { s }
}

fn trim_enum_prefix(ident: &str, api: Api) -> String {
    let ident = match api {
        Api::Gl | Api::GlCore | Api::Gles1 | Api::Gles2 => trim_str(ident, "GL_"),
        Api::Glx => trim_str(ident, "GLX_"),
        Api::Wgl =>  trim_str(ident, "WGL_"),
        Api::Egl =>  trim_str(ident, "EGL_"),
    };
    underscore_numeric_prefix(ident)
}

fn make_enum(ident: String, ty: Option<String>, value: String, alias: Option<String>) -> Enum {
    let (ty, value, cast) = {
        if value.starts_with("((") && value.ends_with(")") {
            // Some enums have a value of the form `'((' type ')' expr ')'`.

            // nothing to see here....
            // just brute forcing some paren matching... (ﾉ ◕ ◡ ◕)ﾉ *:･ﾟ✧
            let working = &value[2 .. value.len() - 1];
            if let Some((i, _)) = working.match_indices(")").next() {
                let ty = working[.. i].to_string();
                let value = working[i + 1 ..].to_string();

                (Cow::Owned(ty), value, true)
            } else {
                panic!("Unexpected value format: {}", value)
            }
        } else {
            let ty = match ty {
                Some(ref ty) if ty == "u" => "GLuint",
                Some(ref ty) if ty == "ull" => "GLuint64",
                Some(ty) => panic!("Unhandled enum type: {}", ty),
                None if value.starts_with("\"") => "&'static str",
                None if ident == "TRUE" || ident == "FALSE" => "GLboolean",
                None => "GLenum",
            };
            (Cow::Borrowed(ty), value, false)
        }
    };

    Enum {
        ident: ident,
        value: value,
        cast: cast,
        alias: alias,
        ty: ty,
    }
}

fn trim_cmd_prefix(ident: &str, api: Api) -> &str {
    match api {
        Api::Gl | Api::GlCore | Api::Gles1 | Api::Gles2 => trim_str(ident, "gl"),
        Api::Glx => trim_str(ident, "glX"),
        Api::Wgl =>  trim_str(ident, "wgl"),
        Api::Egl =>  trim_str(ident, "egl"),
    }
}

fn merge_map(a: &mut HashMap<String, Vec<String>>, b: HashMap<String, Vec<String>>) {
    for (k, v) in b {
        match a.entry(k) {
            Entry::Occupied(mut ent) => { ent.get_mut().extend(v); },
            Entry::Vacant(ent) => { ent.insert(v); }
        }
    }
}

#[derive(Clone)]
struct Feature {
    pub api: Api,
    pub name: String,
    pub number: String,
    pub requires: Vec<Require>,
    pub removes: Vec<Remove>,
}

#[derive(Clone)]
struct Require {
    /// A reference to the earlier types, by name
    pub enums: Vec<String>,
    /// A reference to the earlier types, by name
    pub commands: Vec<String>,
}

#[derive(Clone)]
struct Remove {
    // always Core, for now
    pub profile: Profile,
    /// A reference to the earlier types, by name
    pub enums: Vec<String>,
    /// A reference to the earlier types, by name
    pub commands: Vec<String>,
}

#[derive(Clone)]
struct Extension {
    pub name: String,
    /// which apis this extension is defined for (see Feature.api)
    pub supported: Vec<Api>,
    pub requires: Vec<Require>,
}

pub struct Filter {
    pub api: Api,
    pub fallbacks: Fallbacks,
    pub extensions: BTreeSet<String>,
    pub profile: Profile,
    pub version: String,
}

trait Parse: Sized + Iterator<Item = ParseEvent> {
    fn parse(mut self, filter: Filter) -> Registry {
        self.consume_start_element("registry");

        let mut enums = Vec::new();
        let mut cmds = Vec::new();
        let mut features = Vec::new();
        let mut extensions = Vec::new();
        let mut aliases = HashMap::new();

        while let Some(event) = self.next() {
            match event {
                // ignores
                ParseEvent::Text(_) => (),
                ParseEvent::Start(ref name, _) if name == "comment" => self.skip_to_end("comment"),
                ParseEvent::Start(ref name, _) if name == "types" => self.skip_to_end("types"),
                ParseEvent::Start(ref name, _) if name == "groups" => self.skip_to_end("groups"),

                // add enum namespace
                ParseEvent::Start(ref name, _) if name == "enums" => {
                    enums.extend(self.consume_enums(filter.api));
                }

                // add command namespace
                ParseEvent::Start(ref name, _) if name == "commands" => {
                    let (new_cmds, new_aliases) = self.consume_cmds(filter.api);
                    cmds.extend(new_cmds);
                    merge_map(&mut aliases, new_aliases);
                }

                ParseEvent::Start(ref name, ref attributes) if name == "feature" => {
                    debug!("Parsing feature: {:?}", attributes);
                    features.push(Feature::convert(&mut self, &attributes));
                }

                ParseEvent::Start(ref name, _) if name == "extensions" => {
                    loop {
                        match self.next().unwrap() {
                            ParseEvent::Start(ref name, ref attributes) if name == "extension" => {
                                extensions.push(Extension::convert(&mut self, &attributes));
                            }
                            ParseEvent::End(ref name) if name == "extensions" => break,
                            event => panic!("Unexpected message {:?}", event),
                        }
                    }
                }

                // finished building the registry
                ParseEvent::End(ref name) if name == "registry" => break,

                // error handling
                event => panic!("Expected </registry>, found: {:?}", event),
            }
        }

        let mut desired_enums = HashSet::new();
        let mut desired_cmds = HashSet::new();

        // find the features we want
        let mut found_feature = false;
        for feature in &features {
            // XXX: verify that the string comparison with <= actually works as desired
            if feature.api == filter.api && feature.number <= filter.version {
                for require in &feature.requires {
                    desired_enums.extend(require.enums.iter().map(|x| x.clone()));
                    desired_cmds.extend(require.commands.iter().map(|x| x.clone()));
                }

                for remove in &feature.removes {
                    if remove.profile == filter.profile {
                        for enm in &remove.enums {
                            debug!("Removing {}", enm);
                            desired_enums.remove(enm);
                        }
                        for cmd in &remove.commands {
                            debug!("Removing {}", cmd);
                            desired_cmds.remove(cmd);
                        }
                    }
                }
            }
            if feature.number == filter.version {
                found_feature = true;
            }
        }

        if !found_feature {
            panic!("Did not find version {} in the registry", filter.version);
        }

        for extension in &extensions {
            if filter.extensions.contains(&extension.name) {
                if !extension.supported.contains(&filter.api) {
                    panic!("Requested {}, which doesn't support the {} API", extension.name, filter.api);
                }
                for require in &extension.requires {
                    desired_enums.extend(require.enums.iter().map(|x| x.clone()));
                    desired_cmds.extend(require.commands.iter().map(|x| x.clone()));
                }
            }
        }

        let is_desired_enum = |e: &Enum| {
            desired_enums.contains(&("GL_".to_string() + &e.ident)) ||
            desired_enums.contains(&("WGL_".to_string() + &e.ident)) ||
            desired_enums.contains(&("GLX_".to_string() + &e.ident)) ||
            desired_enums.contains(&("EGL_".to_string() + &e.ident))
        };

        let is_desired_cmd = |c: &Cmd| {
            desired_cmds.contains(&("gl".to_string() + &c.proto.ident)) ||
            desired_cmds.contains(&("wgl".to_string() + &c.proto.ident)) ||
            desired_cmds.contains(&("glX".to_string() + &c.proto.ident)) ||
            desired_cmds.contains(&("egl".to_string() + &c.proto.ident))
        };

        Registry {
            api: filter.api,
            enums: enums.into_iter().filter(is_desired_enum).collect(),
            cmds: cmds.into_iter().filter(is_desired_cmd).collect(),
            aliases: if filter.fallbacks == Fallbacks::None { HashMap::new() } else { aliases },
        }
    }

    fn consume_characters(&mut self) -> String {
        match self.next().unwrap() {
            ParseEvent::Text(ch) => ch,
            event => panic!("Expected characters, found: {:?}", event),
        }
    }

    fn consume_start_element(&mut self, expected_name: &str) -> Vec<Attribute> {
        match self.next().unwrap() {
            ParseEvent::Start(name, attributes) => {
                if expected_name == name { attributes } else {
                    panic!("Expected <{}>, found: <{}>", expected_name, name)
                }
            }
            event => panic!("Expected <{}>, found: {:?}", expected_name, event),
        }
    }

    fn consume_end_element(&mut self, expected_name: &str) {
        match self.next().unwrap() {
            ParseEvent::End(ref name) if expected_name == name => (),
            event => panic!("Expected </{}>, found: {:?}", expected_name, event),
        }
    }

    fn skip_to_end(&mut self, expected_name: &str) {
        loop {
            match self.next().unwrap() {
                ParseEvent::End(ref name) if expected_name == name => break,
                _ => {},
            }
        }
    }

    fn consume_two<'a, T: FromXml, U: FromXml>(&mut self, one: &'a str, two: &'a str, end: &'a str) -> (Vec<T>, Vec<U>) {
        debug!("consume_two: looking for {} and {} until {}", one, two, end);

        let mut ones = Vec::new();
        let mut twos = Vec::new();

        loop {
            match self.next().unwrap() {
                ParseEvent::Start(ref name, ref attributes) => {
                    debug!("Found start element <{:?} {:?}>", name, attributes);
                    debug!("one and two are {} and {}", one, two);

                    let n = name.clone();

                    if one == n {
                        ones.push(FromXml::convert(self, &attributes));
                    } else if "type" == n {
                        // XXX: GL1.1 contains types, which we never care about anyway.
                        // Make sure consume_two doesn't get used for things which *do*
                        // care about type.
                        warn!("Ignoring type!");
                        continue;
                    } else if two == n {
                        twos.push(FromXml::convert(self, &attributes));
                    } else {
                        panic!("Unexpected element: <{:?} {:?}>", n, &attributes);
                    }
                },
                ParseEvent::End(ref name) => {
                    debug!("Found end element </{:?}>", name);

                    if one == name || two == name {
                        continue;
                    } else if "type" == name {
                        // XXX: GL1.1 contains types, which we never care about anyway.
                        // Make sure consume_two doesn't get used for things which *do*
                        // care about type.
                        warn!("Ignoring type!");
                        continue;
                    } else if end == name {
                        return (ones, twos);
                    } else {
                        panic!("Unexpected end element {:?}", name);
                    }
                },
                event => panic!("Unexpected message {:?}", event) }
        }
    }

    fn consume_enums(&mut self, api: Api) -> Vec<Enum> {
        let mut enums = Vec::new();
        loop {
            match self.next().unwrap() {
                // ignores
                ParseEvent::Text(_) => {},
                ParseEvent::Start(ref name, _) if name == "unused" => self.skip_to_end("unused"),

                // add enum definition
                ParseEvent::Start(ref name, ref attributes) if name == "enum" => {
                    enums.push(self.consume_enum(api, attributes));
                },

                // finished building the namespace
                ParseEvent::End(ref name) if name == "enums" => break,
                // error handling
                event => panic!("Expected </enums>, found: {:?}", event),
            }
        }
        enums
    }

    fn consume_enum(&mut self, api: Api, attributes: &[Attribute]) -> Enum {
        let ident = trim_enum_prefix(&get_attribute(&attributes, "name").unwrap(), api).to_string();
        let value = get_attribute(&attributes, "value").unwrap();
        let alias = get_attribute(&attributes, "alias");
        let ty = get_attribute(&attributes, "type");
        self.consume_end_element("enum");

        make_enum(ident, ty, value, alias)
    }

    fn consume_cmds(&mut self, api: Api) -> (Vec<Cmd>, HashMap<String, Vec<String>>) {
        let mut cmds = Vec::new();
        let mut aliases: HashMap<String, Vec<String>> = HashMap::new();
        loop {
            match self.next().unwrap() {
                // add command definition
                ParseEvent::Start(ref name, _) if name == "command" => {
                    let new = self.consume_cmd(api);
                    if let Some(ref v) = new.alias {
                        match aliases.entry(v.clone()) {
                            Entry::Occupied(mut ent) => { ent.get_mut().push(new.proto.ident.clone()); },
                            Entry::Vacant(ent) => { ent.insert(vec![new.proto.ident.clone()]); }
                        }
                    }
                    cmds.push(new);
                }
                // finished building the namespace
                ParseEvent::End(ref name) if name == "commands" => break,
                // error handling
                event => panic!("Expected </commands>, found: {:?}", event),
            }
        }
        (cmds, aliases)
    }

    fn consume_cmd(&mut self, api: Api) -> Cmd {
        // consume command prototype
        self.consume_start_element("proto");
        let mut proto = self.consume_binding("proto");
        proto.ident = trim_cmd_prefix(&proto.ident, api).to_string();

        let mut params = Vec::new();
        let mut alias = None;
        let mut vecequiv = None;
        let mut glx = None;
        loop {
            match self.next().unwrap() {
                ParseEvent::Start(ref name, _) if name == "param" => {
                    params.push(self.consume_binding("param"));
                }
                ParseEvent::Start(ref name, ref attributes) if name == "alias" => {
                    alias = get_attribute(&attributes, "name");
                    alias = alias.map(|t| trim_cmd_prefix(&t, api).to_string());
                    self.consume_end_element("alias");
                }
                ParseEvent::Start(ref name, ref attributes) if name == "vecequiv" => {
                    vecequiv = get_attribute(&attributes, "vecequiv");
                    self.consume_end_element("vecequiv");
                }
                ParseEvent::Start(ref name, ref attributes) if name == "glx" => {
                    glx = Some(GlxOpcode {
                        opcode: get_attribute(&attributes, "opcode").unwrap(),
                        name: get_attribute(&attributes, "name"),
                    });
                    self.consume_end_element("glx");
                }
                ParseEvent::End(ref name) if name == "command" => break,
                event => panic!("Expected </command>, found: {:?}", event),
            }
        }

        Cmd {
            proto: proto,
            params: params,
            alias: alias,
            vecequiv: vecequiv,
            glx: glx,
        }
    }

    fn consume_binding(&mut self, outside_tag: &str) -> Binding {
        // consume type
        let mut ty = String::new();
        loop {
            match self.next().unwrap() {
                ParseEvent::Text(text) => ty.push_str(&text),
                ParseEvent::Start(ref name, _) if name == "ptype" => (),
                ParseEvent::End(ref name) if name == "ptype" => (),
                ParseEvent::Start(ref name, _) if name == "name" => break,
                event => panic!("Expected binding, found: {:?}", event),
            }
        }

        // consume identifier
        let ident = underscore_keyword(self.consume_characters());
        self.consume_end_element("name");

        // consume the type suffix
        loop {
            match self.next().unwrap() {
                ParseEvent::Text(text) => ty.push_str(&text),
                ParseEvent::End(ref name) if name == outside_tag => break,
                event => panic!("Expected binding, found: {:?}", event),
            }
        }

        Binding {
            ident: ident,
            ty: to_rust_ty(ty),
        }
    }
}

impl<T> Parse for T where
    T: Sized + Iterator<Item = ParseEvent>,
{}

fn get_attribute(attribs: &[Attribute], key: &str) -> Option<String> {
    attribs.iter()
        .find(|attrib| attrib.key == key)
        .map(|attrib| attrib.value.clone())
}

trait FromXml {
    fn convert<P: Parse>(parser: &mut P, a: &[Attribute]) -> Self;
}

impl FromXml for Require {
    fn convert<P: Parse>(parser: &mut P, _: &[Attribute]) -> Require {
        debug!("Doing a FromXml on Require");
        let (enums, commands) = parser.consume_two("enum", "command", "require");
        Require {
            enums: enums,
            commands: commands
        }
    }
}

impl FromXml for Remove {
    fn convert<P: Parse>(parser: &mut P, a: &[Attribute]) -> Remove {
        debug!("Doing a FromXml on Remove");
        let profile = get_attribute(a, "profile").unwrap();
        let profile = profile_from_str(&profile).unwrap();
        let (enums, commands) = parser.consume_two("enum", "command", "remove");

        Remove {
            profile: profile,
            enums: enums,
            commands: commands
        }
    }
}

impl FromXml for Feature {
    fn convert<P: Parse>(parser: &mut P, a: &[Attribute]) -> Feature {
        debug!("Doing a FromXml on Feature");
        let api = get_attribute(a, "api").unwrap();
        let api = api_from_str(&api).unwrap();
        let name = get_attribute(a, "name").unwrap();
        let number = get_attribute(a, "number").unwrap();

        debug!("Found api = {}, name = {}, number = {}", api, name, number);

        let (require, remove) = parser.consume_two("require", "remove", "feature");

        Feature {
            api: api,
            name: name,
            number: number,
            requires: require,
            removes: remove
        }
    }
}

impl FromXml for Extension {
    fn convert<P: Parse>(parser: &mut P, a: &[Attribute]) -> Extension {
        debug!("Doing a FromXml on Extension");
        let name = get_attribute(a, "name").unwrap();
        let supported = get_attribute(a, "supported").unwrap()
            .split('|')
            .map(api_from_str)
            .map(Result::unwrap)
            .collect::<Vec<_>>();
        let mut require = Vec::new();
        loop {
            match parser.next().unwrap() {
                ParseEvent::Start(ref name, ref attributes) if name == "require" => {
                    require.push(FromXml::convert(parser, &attributes));
                }
                ParseEvent::End(ref name) if name == "extension" => break,
                event => panic!("Unexpected message {:?}", event)
            }
        }

        Extension {
            name: name,
            supported: supported,
            requires: require
        }
    }
}

impl FromXml for String {
    fn convert<P: Parse>(_: &mut P, a: &[Attribute]) -> String {
        get_attribute(a, "name").unwrap()
    }
}

/// Converts a C style type definition to the Rust equivalent
pub fn to_rust_ty<T: AsRef<str>>(ty: T) -> Cow<'static, str> {
    let ty = match ty.as_ref() {
        // gl.xml types
        "GLDEBUGPROC"               => "types::GLDEBUGPROC",
        "GLDEBUGPROCAMD"            => "types::GLDEBUGPROCAMD",
        "GLDEBUGPROCARB"            => "types::GLDEBUGPROCARB",
        "GLDEBUGPROCKHR"            => "types::GLDEBUGPROCKHR",
        "GLbitfield"                => "types::GLbitfield",
        "GLboolean"                 => "types::GLboolean",
        "GLbyte"                    => "types::GLbyte",
        "GLclampd"                  => "types::GLclampd",
        "GLclampf"                  => "types::GLclampf",
        "GLclampx"                  => "types::GLclampx",
        "GLdouble"                  => "types::GLdouble",
        "GLeglImageOES"             => "types::GLeglImageOES",
        "GLenum"                    => "types::GLenum",
        "GLfixed"                   => "types::GLfixed",
        "GLfloat"                   => "types::GLfloat",
        "GLhalfNV"                  => "types::GLhalfNV",
        "GLhandleARB"               => "types::GLhandleARB",
        "GLint"                     => "types::GLint",
        "GLint64"                   => "types::GLint64",
        "GLint64EXT"                => "types::GLint64EXT",
        "GLintptr"                  => "types::GLintptr",
        "GLintptrARB"               => "types::GLintptrARB",
        "GLshort"                   => "types::GLshort",
        "GLsizei"                   => "types::GLsizei",
        "GLsizeiptr"                => "types::GLsizeiptr",
        "GLsizeiptrARB"             => "types::GLsizeiptrARB",
        "GLsync"                    => "types::GLsync",
        "GLubyte"                   => "types::GLubyte",
        "GLuint"                    => "types::GLuint",
        "GLuint64"                  => "types::GLuint64",
        "GLuint64EXT"               => "types::GLuint64EXT",
        "GLushort"                  => "types::GLushort",
        "GLvdpauSurfaceNV"          => "types::GLvdpauSurfaceNV",
        "void "                     => "()",
        "GLboolean *"               => "*mut types::GLboolean",
        "GLchar *"                  => "*mut types::GLchar",
        "GLcharARB *"               => "*mut types::GLcharARB",
        "GLdouble *"                => "*mut types::GLdouble",
        "GLenum *"                  => "*mut types::GLenum",
        "GLfixed *"                 => "*mut types::GLfixed",
        "GLfloat *"                 => "*mut types::GLfloat",
        "GLhandleARB *"             => "*mut types::GLhandleARB",
        "GLint *"                   => "*mut types::GLint",
        "GLint64 *"                 => "*mut types::GLint64",
        "GLint64EXT *"              => "*mut types::GLint64EXT",
        "GLsizei *"                 => "*mut types::GLsizei",
        "GLubyte *"                 => "*mut types::GLubyte",
        "GLuint *"                  => "*mut types::GLuint",
        "GLuint [2]"                => "*mut [types::GLuint; 2]",
        "GLuint64 *"                => "*mut types::GLuint64",
        "GLuint64EXT *"             => "*mut types::GLuint64EXT",
        "GLushort *"                => "*mut types::GLushort",
        "GLvoid *"                  => "*mut types::GLvoid",
        "GLvoid **"                 => "*const *mut types::GLvoid",
        "void *"                    => "*mut __gl_imports::raw::c_void",
        "void **"                   => "*const *mut __gl_imports::raw::c_void",
        "const GLboolean *"         => "*const types::GLboolean",
        "const GLbyte *"            => "*const types::GLbyte",
        "const GLchar *"            => "*const types::GLchar",
        "const GLcharARB *"         => "*const types::GLcharARB",
        "const GLclampf *"          => "*const types::GLclampf",
        "const GLdouble *"          => "*const types::GLdouble",
        "const GLenum *"            => "*const types::GLenum",
        "const GLfixed *"           => "*const types::GLfixed",
        "const GLfloat"             => "types::GLfloat",
        "const GLfloat *"           => "*const types::GLfloat",
        "const GLhalfNV *"          => "*const types::GLhalfNV",
        "const GLint *"             => "*const types::GLint",
        "const GLint64 *"           => "*const types::GLint64",
        "const GLint64EXT *"        => "*const types::GLint64EXT",
        "const GLintptr *"          => "*const types::GLintptr",
        "const GLshort *"           => "*const types::GLshort",
        "const GLsizei *"           => "*const types::GLsizei",
        "const GLsizeiptr *"        => "*const types::GLsizeiptr",
        "const GLubyte *"           => "*const types::GLubyte",
        "const GLuint *"            => "*const types::GLuint",
        "const GLuint64 *"          => "*const types::GLuint64",
        "const GLuint64EXT *"       => "*const types::GLuint64EXT",
        "const GLushort *"          => "*const types::GLushort",
        "const GLvdpauSurfaceNV *"  => "*const types::GLvdpauSurfaceNV",
        "const GLvoid *"            => "*const types::GLvoid",
        "const void *"              => "*const __gl_imports::raw::c_void",
        "const void **"             => "*const *const __gl_imports::raw::c_void",
        "const void *const*"        => "*const *const __gl_imports::raw::c_void",
        "const GLboolean **"        => "*const *const types::GLboolean",
        "const GLchar **"           => "*const *const types::GLchar",
        "const GLcharARB **"        => "*const *const types::GLcharARB",
        "const GLvoid **"           => "*const *const types::GLvoid",
        "const GLchar *const*"      => "*const *const types::GLchar",
        "const GLvoid *const*"      => "*const *const types::GLvoid",
        "struct _cl_context *"      => "*const types::_cl_context",
        "struct _cl_event *"        => "*const types::_cl_event",
        "GLuint[2]"                 => "[Gluint; 2]",

        // glx.xml types
        "Bool"                      => "types::Bool",
        "Colormap"                  => "types::Colormap",
        "DMbuffer"                  => "types::DMbuffer",
        "Font"                      => "types::Font",
        "GLXContext"                => "types::GLXContext",
        "GLXContextID"              => "types::GLXContextID",
        "GLXDrawable"               => "types::GLXDrawable",
        "GLXFBConfig"               => "types::GLXFBConfig",
        "GLXFBConfigSGIX"           => "types::GLXFBConfigSGIX",
        "GLXPbuffer"                => "types::GLXPbuffer",
        "GLXPbufferSGIX"            => "types::GLXPbufferSGIX",
        "GLXPixmap"                 => "types::GLXPixmap",
        "GLXVideoCaptureDeviceNV"   => "types::GLXVideoCaptureDeviceNV",
        "GLXVideoDeviceNV"          => "types::GLXVideoDeviceNV",
        "GLXVideoSourceSGIX"        => "types::GLXVideoSourceSGIX",
        "GLXWindow"                 => "types::GLXWindow",
        // "GLboolean"                 => "types::GLboolean",
        // "GLenum"                    => "types::GLenum",
        // "GLint"                     => "types::GLint",
        // "GLsizei"                   => "types::GLsizei",
        // "GLuint"                    => "types::GLuint",
        "Pixmap"                    => "types::Pixmap",
        "Status"                    => "types::Status",
        "VLNode"                    => "types::VLNode",
        "VLPath"                    => "types::VLPath",
        "VLServer"                  => "types::VLServer",
        "Window"                    => "types::Window",
        "__GLXextFuncPtr"           => "types::__GLXextFuncPtr",
        "const GLXContext"          => "const types::GLXContext",
        "float "                    => "__gl_imports::raw::c_float",
        "int "                      => "__gl_imports::raw::c_int",
        "int64_t"                   => "i64",
        "unsigned int "             => "__gl_imports::raw::c_uint",
        "unsigned long "            => "__gl_imports::raw::c_ulong",
        // "void "                     => "()",
        "DMparams *"                => "*mut types::DMparams",
        "Display *"                 => "*mut types::Display",
        "GLXFBConfig *"             => "*mut types::GLXFBConfig",
        "GLXFBConfigSGIX *"         => "*mut types::GLXFBConfigSGIX",
        "GLXHyperpipeConfigSGIX *"  => "*mut types::GLXHyperpipeConfigSGIX",
        "GLXHyperpipeNetworkSGIX *" => "*mut types::GLXHyperpipeNetworkSGIX",
        "GLXVideoCaptureDeviceNV *" => "*mut types::GLXVideoCaptureDeviceNV",
        "GLXVideoDeviceNV *"        => "*mut types::GLXVideoDeviceNV",
        // "GLuint *"                  => "*mut types::GLuint",
        "XVisualInfo *"             => "*mut types::XVisualInfo",
        // "const GLubyte *"           => "*GLubyte",
        "const char *"              => "*const __gl_imports::raw::c_char",
        "const int *"               => "*const __gl_imports::raw::c_int",
        // "const void *"              => "*const __gl_imports::raw::c_void",
        "int *"                     => "*mut __gl_imports::raw::c_int",
        "int32_t *"                 => "*mut i32",
        "int64_t *"                 => "*mut i64",
        "long *"                    => "*mut __gl_imports::raw::c_long",
        "unsigned int *"            => "*mut __gl_imports::raw::c_uint",
        "unsigned long *"           => "*mut __gl_imports::raw::c_ulong",
        // "void *"                    => "*mut __gl_imports::raw::c_void",

        // wgl.xml types
        "BOOL"                      => "types::BOOL",
        "DWORD"                     => "types::DWORD",
        "FLOAT"                     => "types::FLOAT",
        // "GLbitfield"                => "types::GLbitfield",
        // "GLboolean"                 => "types::GLboolean",
        // "GLenum"                    => "types::GLenum",
        // "GLfloat"                   => "types::GLfloat",
        // "GLint"                     => "types::GLint",
        // "GLsizei"                   => "types::GLsizei",
        // "GLuint"                    => "types::GLuint",
        // "GLushort"                  => "types::GLushort",
        "HANDLE"                    => "types::HANDLE",
        "HDC"                       => "types::HDC",
        "HENHMETAFILE"              => "types::HENHMETAFILE",
        "HGLRC"                     => "types::HGLRC",
        "HGPUNV"                    => "types::HGPUNV",
        "HPBUFFERARB"               => "types::HPBUFFERARB",
        "HPBUFFEREXT"               => "types::HPBUFFEREXT",
        "HPVIDEODEV"                => "types::HPVIDEODEV",
        "HVIDEOINPUTDEVICENV"       => "types::HVIDEOINPUTDEVICENV",
        "HVIDEOOUTPUTDEVICENV"      => "types::HVIDEOOUTPUTDEVICENV",
        "INT"                       => "types::INT",
        "INT64"                     => "types::INT64",
        "LPCSTR"                    => "types::LPCSTR",
        "LPGLYPHMETRICSFLOAT"       => "types::LPGLYPHMETRICSFLOAT",
        "LPVOID"                    => "types::LPVOID",
        "PGPU_DEVICE"               => "types::PGPU_DEVICE",
        "PROC"                      => "types::PROC",
        "UINT"                      => "types::UINT",
        "VOID"                      => "types::VOID",
        // "int "                      => "__gl_imports::raw::c_int",
        // "unsigned int "             => "__gl_imports::raw::c_uint",
        // "void "                     => "()",
        "BOOL *"                    => "*mut types::BOOL",
        "DWORD *"                   => "*mut types::DWORD",
        "FLOAT *"                   => "*mut types::FLOAT",
        // "GLuint *"                  => "*mut types::GLuint",
        "HANDLE *"                  => "*mut types::HANDLE",
        "HGPUNV *"                  => "*mut types::HGPUNV",
        "HPVIDEODEV *"              => "*mut types::HPVIDEODEV",
        "HVIDEOINPUTDEVICENV *"     => "*mut types::HVIDEOINPUTDEVICENV",
        "HVIDEOOUTPUTDEVICENV *"    => "*mut types::HVIDEOOUTPUTDEVICENV",
        "INT32 *"                   => "*mut types::INT32",
        "INT64 *"                   => "*mut types::INT64",
        "UINT *"                    => "*mut types::UINT",
        "USHORT *"                  => "*mut types::USHORT",
        "const COLORREF *"          => "*const types::COLORREF",
        "const DWORD *"             => "*const types::DWORD",
        "const FLOAT *"             => "*const types::FLOAT",
        // "const GLushort *"          => "*const types::GLushort",
        "const HANDLE *"            => "*const types::HANDLE",
        "const HGPUNV *"            => "*const types::HGPUNV",
        "const LAYERPLANEDESCRIPTOR *"  => "*const types::LAYERPLANEDESCRIPTOR",
        "const LPVOID *"            => "*const types::LPVOID",
        "const PIXELFORMATDESCRIPTOR *" => "*const types::IXELFORMATDESCRIPTOR",
        "const USHORT *"            => "*const types::USHORT",
        // "const char *"              => "*const __gl_imports::raw::c_char",
        // "const int *"               => "*const __gl_imports::raw::c_int",
        "float *"                   => "*mut __gl_imports::raw::c_float",
        // "int *"                     => "*mut __gl_imports::raw::c_int",
        // "unsigned long *"           => "*mut __gl_imports::raw::c_ulong",
        // "void *"                    => "*mut __gl_imports::raw::c_void",

        // elx.xml types
        "khronos_utime_nanoseconds_t"   => "types::khronos_utime_nanoseconds_t",
        "khronos_uint64_t"          => "types::khronos_uint64_t",
        "khronos_ssize_t"           => "types::khronos_ssize_t",
        "EGLNativeDisplayType"      => "types::EGLNativeDisplayType",
        "EGLNativePixmapType"       => "types::EGLNativePixmapType",
        "EGLNativeWindowType"       => "types::EGLNativeWindowType",
        "EGLint"                    => "types::EGLint",
        "EGLint *"                  => "*mut types::EGLint",
        "const EGLint *"            => "*const types::EGLint",
        "NativeDisplayType"         => "types::NativeDisplayType",
        "NativePixmapType"          => "types::NativePixmapType",
        "NativeWindowType"          => "types::NativeWindowType",
        //"Bool"                      => "types::Bool",
        "EGLBoolean"                => "types::EGLBoolean",
        "EGLenum"                   => "types::EGLenum",
        "EGLAttribKHR"              => "types::EGLAttribKHR",
        "EGLAttrib"                 => "types::EGLAttrib",
        "EGLAttrib *"               => "*mut types::EGLAttrib",
        "const EGLAttrib *"         => "*const types::EGLAttrib",
        "EGLConfig"                 => "types::EGLConfig",
        "EGLConfig *"               => "*mut types::EGLConfig",
        "EGLContext"                => "types::EGLContext",
        "EGLDeviceEXT"              => "types::EGLDeviceEXT",
        "EGLDisplay"                => "types::EGLDisplay",
        "EGLSurface"                => "types::EGLSurface",
        "EGLClientBuffer"           => "types::EGLClientBuffer",
        "__eglMustCastToProperFunctionPointerType" => "types::__eglMustCastToProperFunctionPointerType",
        "EGLImageKHR"               => "types::EGLImageKHR",
        "EGLImage"                  => "types::EGLImage",
        "EGLOutputLayerEXT"         => "types::EGLOutputLayerEXT",
        "EGLOutputPortEXT"          => "types::EGLOutputPortEXT",
        "EGLSyncKHR"                => "types::EGLSyncKHR",
        "EGLSync"                   => "types::EGLSync",
        "EGLTimeKHR"                => "types::EGLTimeKHR",
        "EGLTime"                   => "types::EGLTime",
        "EGLSyncNV"                 => "types::EGLSyncNV",
        "EGLTimeNV"                 => "types::EGLTimeNV",
        "EGLuint64NV"               => "types::EGLuint64NV",
        "EGLStreamKHR"              => "types::EGLStreamKHR",
        "EGLuint64KHR"              => "types::EGLuint64KHR",
        "EGLNativeFileDescriptorKHR" => "types::EGLNativeFileDescriptorKHR",
        "EGLsizeiANDROID"           => "types::EGLsizeiANDROID",
        "EGLSetBlobFuncANDROID"     => "types::EGLSetBlobFuncANDROID",
        "EGLGetBlobFuncANDROID"     => "types::EGLGetBlobFuncANDROID",
        "EGLClientPixmapHI"         => "types::EGLClientPixmapHI",
        "struct EGLClientPixmapHI *" => "*const types::EGLClientPixmapHI",
        "const EGLAttribKHR *"      => "*const types::EGLAttribKHR",
        "const EGLuint64KHR *"      => "*const types::EGLuint64KHR",
        "EGLAttribKHR *"            => "*mut types::EGLAttribKHR",
        "EGLDeviceEXT *"            => "*mut types::EGLDeviceEXT",
        "EGLNativeDisplayType *"    => "*mut types::EGLNativeDisplayType",
        "EGLNativePixmapType *"     => "*mut types::EGLNativePixmapType",
        "EGLNativeWindowType *"     => "*mut types::EGLNativeWindowType",
        "EGLOutputLayerEXT *"       => "*mut types::EGLOutputLayerEXT",
        "EGLTimeKHR *"              => "*mut types::EGLTimeKHR",
        "EGLOutputPortEXT *"        => "*mut types::EGLOutputPortEXT",
        "EGLuint64KHR *"            => "*mut types::EGLuint64KHR",

        // failure
        _ => panic!("Type conversion not implemented for `{}`", ty.as_ref()),
    };

    Cow::Borrowed(ty)
}

#[cfg(test)]
mod tests {
    mod underscore_numeric_prefix {
        use registry::parse;

        #[test]
        fn test_numeric_prefix() {
            assert_eq!(parse::underscore_numeric_prefix("3"), "_3");
            assert_eq!(parse::underscore_numeric_prefix("123_FOO"), "_123_FOO");
        }

        #[test]
        fn test_non_numeric_prefix() {
            assert_eq!(parse::underscore_numeric_prefix(""), "");
            assert_eq!(parse::underscore_numeric_prefix("A"), "A");
            assert_eq!(parse::underscore_numeric_prefix("FOO"), "FOO");
        }
    }

    mod underscore_keyword {
        use registry::parse;

        #[test]
        fn test_keyword() {
            assert_eq!(parse::underscore_keyword("in".to_string()), "in_");
            assert_eq!(parse::underscore_keyword("ref".to_string()), "ref_");
            assert_eq!(parse::underscore_keyword("type".to_string()), "type_");
        }

        #[test]
        fn test_non_keyword() {
            assert_eq!(parse::underscore_keyword("foo".to_string()), "foo");
            assert_eq!(parse::underscore_keyword("bar".to_string()), "bar");
        }
    }
    mod make_enum {
        use registry::parse;

        #[test]
        fn test_cast_0() {
            let e = parse::make_enum("FOO".to_string(), None, "((EGLint)-1)".to_string(), Some("BAR".to_string()));
            assert_eq!(e.ident, "FOO");
            assert_eq!((&*e.ty, &*e.value), ("EGLint", "-1"));
            assert_eq!(e.alias, Some("BAR".to_string()));
        }

        #[test]
        fn test_cast_1() {
            let e = parse::make_enum("FOO".to_string(), None, "((EGLint)(-1))".to_string(), Some("BAR".to_string()));
            assert_eq!(e.ident, "FOO");
            assert_eq!((&*e.ty, &*e.value), ("EGLint", "(-1)"));
            assert_eq!(e.alias, Some("BAR".to_string()));
        }

        #[test]
        fn test_no_type() {
            let e = parse::make_enum("FOO".to_string(), None, "value".to_string(), Some("BAR".to_string()));
            assert_eq!(e.ident, "FOO");
            assert_eq!(e.value, "value");
            assert_eq!(e.alias, Some("BAR".to_string()));
            assert_eq!(e.ty, "GLenum");
            assert_eq!(e.cast, false);
        }

        #[test]
        fn test_u() {
            let e = parse::make_enum("FOO".to_string(), Some("u".to_string()), String::new(), None);
            assert_eq!(e.ty, "GLuint");
        }

        #[test]
        fn test_ull() {
            let e = parse::make_enum("FOO".to_string(), Some("ull".to_string()), String::new(), None);
            assert_eq!(e.ty, "GLuint64");
        }

        #[test]
        #[should_panic]
        fn test_unknown_type() {
            parse::make_enum("FOO".to_string(), Some("blargh".to_string()), String::new(), None);
        }

        #[test]
        fn test_value_str() {
            let e = parse::make_enum("FOO".to_string(), None, "\"hi\"".to_string(), None);
            assert_eq!(e.ty, "&'static str");
        }

        #[test]
        fn test_ident_true() {
            let e = parse::make_enum("TRUE".to_string(), None, String::new(), None);
            assert_eq!(e.ty, "GLboolean");
        }

        #[test]
        fn test_ident_false() {
            let e = parse::make_enum("FALSE".to_string(), None, String::new(), None);
            assert_eq!(e.ty, "GLboolean");
        }
    }

    mod parse_event {
        mod from_xml {
            use xml::attribute::OwnedAttribute;
            use xml::common::XmlVersion;
            use xml::name::OwnedName;
            use xml::namespace::Namespace;
            use xml::reader::XmlEvent;

            use registry::parse::{Attribute, ParseEvent};

            #[test]
            fn test_start_event() {
                let given = XmlEvent::StartElement {
                    name: OwnedName::local("element"),
                    attributes: vec![
                        OwnedAttribute::new(OwnedName::local("attr1"), "val1"),
                        OwnedAttribute::new(OwnedName::local("attr2"), "val2"),
                    ],
                    namespace: Namespace::empty(),
                };
                let expected = ParseEvent::Start(
                    "element".to_string(),
                    vec![
                        Attribute::new("attr1", "val1"),
                        Attribute::new("attr2", "val2"),
                    ],
                );
                assert_eq!(ParseEvent::from_xml(given), Some(expected));
            }

            #[test]
            fn test_end_element() {
                let given = XmlEvent::EndElement {
                    name: OwnedName::local("element"),
                };
                let expected = ParseEvent::End("element".to_string());
                assert_eq!(ParseEvent::from_xml(given), Some(expected));
            }

            #[test]
            fn test_characters() {
                let given = XmlEvent::Characters("text".to_string());
                let expected = ParseEvent::Text("text".to_string());
                assert_eq!(ParseEvent::from_xml(given), Some(expected));
            }

            #[test]
            fn test_start_document() {
                let given = XmlEvent::StartDocument {
                    version: XmlVersion::Version10,
                    encoding: "".to_string(),
                    standalone: None,
                };
                assert_eq!(ParseEvent::from_xml(given), None);
            }

            #[test]
            fn test_end_document() {
                let given = XmlEvent::EndDocument;
                assert_eq!(ParseEvent::from_xml(given), None);
            }

            #[test]
            fn test_processing_instruction() {
                let given = XmlEvent::ProcessingInstruction {
                    name: "".to_string(),
                    data: None,
                };
                assert_eq!(ParseEvent::from_xml(given), None);
            }

            #[test]
            fn test_cdata() {
                let given = XmlEvent::CData("CData".to_string());
                assert_eq!(ParseEvent::from_xml(given), None);
            }

            #[test]
            fn test_comment() {
                let given = XmlEvent::Comment("Comment".to_string());
                assert_eq!(ParseEvent::from_xml(given), None);
            }

            #[test]
            fn test_whitespace() {
                let given = XmlEvent::Whitespace("Whitespace".to_string());
                assert_eq!(ParseEvent::from_xml(given), None);
            }
        }
    }
}
