// 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::btree_map::Entry;
use std::collections::{BTreeMap, BTreeSet};
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 BTreeMap<String, Vec<String>>, b: BTreeMap<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 = BTreeMap::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 = BTreeSet::new();
        let mut desired_cmds = BTreeSet::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 {
                BTreeMap::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>, BTreeMap<String, Vec<String>>) {
        let mut cmds = Vec::new();
        let mut aliases: BTreeMap<String, Vec<String>> = BTreeMap::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);
            }
        }
    }
}
