use std::{borrow::Cow, hint::unreachable_unchecked};

use ahash::{AHashMap, AHashSet};
use futures::future::Either;
use tracing::{info, warn};
use vk_parse::{CommentedChildren, EnumSpec, Enums, EnumsChild, TypeCodeMarkup, TypeMember, TypeSpec};

use crate::{
    source::{
        name::{FunctionName, TypeName},
        structure::Field,
    },
    symbol::{SymbolName, SymbolTable},
};

use super::{
    expr::Expr,
    name::EnumVariant,
    structure::{ExternalType, Struct},
    ty::{native_raw, Ty},
    Constant, ConstantValue, Origin,
};

#[derive(Debug, Clone, PartialEq)]
pub struct Union<'a> {
    /// The name of the union
    pub name: TypeName<'a>,

    /// The fields that compose this struct
    pub fields: SymbolTable<'a, Field<'a>>,

    /// The origin
    pub origin: Origin<'a>,
}

impl<'a> SymbolName<'a> for Union<'a> {
    fn name(&self) -> Cow<'a, str> {
        self.name.original.clone()
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Handle<'a> {
    /// The name of the union
    pub name: TypeName<'a>,

    /// The object type enum name
    pub object_ty: Cow<'a, str>,

    /// The parent of this structure
    pub parent: Option<TypeName<'a>>,

    /// The origin
    pub origin: Origin<'a>,
}

impl<'a> SymbolName<'a> for Handle<'a> {
    fn name(&self) -> Cow<'a, str> {
        self.name.original.clone()
    }
}

#[derive(Debug, Clone, PartialEq)]
pub struct FuncPointer<'a> {
    /// The name of the union
    pub name: FunctionName<'a>,

    /// The arguments of the function
    pub arguments: SymbolTable<'a, (Cow<'a, str>, Ty<'a>)>,

    /// The origin
    pub origin: Origin<'a>,
}

impl<'a> SymbolName<'a> for FuncPointer<'a> {
    fn name(&self) -> Cow<'a, str> {
        self.name.original.clone()
    }
}

impl<'a> SymbolName<'a> for (Cow<'a, str>, Ty<'a>) {
    fn name(&self) -> Cow<'a, str> {
        self.0.clone()
    }
}

#[derive(Debug, Clone, PartialEq)]
pub struct BaseType<'a> {
    /// The name of the base type.
    pub name: TypeName<'a>,

    /// The type of the base type
    pub ty: Ty<'a>,

    /// The origin
    pub origin: Origin<'a>,
}

impl<'a> SymbolName<'a> for BaseType<'a> {
    fn name(&self) -> Cow<'a, str> {
        self.name.original.clone()
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Bitmask<'a> {
    /// The name of the base type.
    pub name: TypeName<'a>,

    /// The bit types (actual flags) of this mask.
    /// If this is none then there are no flags.
    pub bits: Option<TypeName<'a>>,

    /// The origin
    pub origin: Origin<'a>,
}

impl<'a> SymbolName<'a> for Bitmask<'a> {
    fn name(&self) -> Cow<'a, str> {
        self.name.original.clone()
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Alias<'a> {
    /// The name of the alias.
    pub name: TypeName<'a>,

    /// The type name the alias points to.
    pub of: TypeName<'a>,

    /// The origin of the alias.
    pub origin: Origin<'a>,
}

impl<'a> SymbolName<'a> for Alias<'a> {
    fn name(&self) -> Cow<'a, str> {
        self.name.original.clone()
    }
}

#[derive(Debug, Clone, PartialEq)]
pub enum Type<'a> {
    External(ExternalType<'a>),
    Struct(Struct<'a>),
    Alias(Alias<'a>),
    Union(Union<'a>),
    Handle(Handle<'a>),
    FunctionPointer(FuncPointer<'a>),
    BaseType(BaseType<'a>),
    Bitmask(Bitmask<'a>),
    Enum(Enum<'a>),
}

impl<'a> Type<'a> {
    pub fn new<'b>(
        child: &'a CommentedChildren<vk_parse::TypesChild>,
        tags: &'b SymbolTable<'a, Cow<'a, str>>,
    ) -> impl Iterator<Item = Type<'a>> + 'b
    where
        'a: 'b,
    {
        child
            .children
            .iter()
            .filter_map(|ty| match ty {
                vk_parse::TypesChild::Type(ty) => Some(ty),
                _ => None,
            })
            .filter_map(|ty| {
                if let Some(req) = &ty.requires {
                    if req.ends_with(".h") {
                        let name = TypeName::new(ty.name.as_ref().expect("nameless external type"), tags);
                        return Some(
                            Self::External(ExternalType {
                                name,
                                require: Cow::Borrowed(req),
                                origin: Origin::default(),
                            })
                        );
                    }
                }

                match ty.category.as_ref().map(|s| s as &str) {
                    Some("struct") if let Some(name) = &ty.name => {
                        if let Some(alias) = &ty.alias {
                            let type_name = TypeName::new(name, tags);
                            info!("Found alias: `{}` (untagged: `{}`)", type_name.original, type_name.no_tag);

                            Some(Self::Alias(Alias {
                                name: type_name,
                                of: TypeName::new(alias, tags),
                                origin: Origin::default(),
                            }))
                        } else {
                            let type_name = TypeName::new(name, tags);
                            info!("Found struct: `{}` (untagged: `{}`)", type_name.original, type_name.no_tag);

                            let extends = ty.structextends.as_ref()
                                .map_or_else(
                                    AHashSet::new,
                                    |s| s
                                        .split(',')
                                        .map(|s| TypeName::new(s, tags))
                                        .collect()
                                );

                            let fields = match &ty.spec {
                                TypeSpec::Members(members) => {
                                    members.iter().filter_map(|member| match member {
                                        TypeMember::Comment(_) => None,
                                        TypeMember::Definition(def) => Some(def),
                                        _ => unreachable!("unknown member: {:?}", member)
                                    })
                                    .map(|def| Field::new(def, tags)).collect()
                                },
                                _ => unreachable!("the spec of a struct should always be `TypeSpec::Members`"),
                            };

                            let always_returned = ty.returnedonly.as_ref().map_or(false, |s| s.eq_ignore_ascii_case("true"));

                            Some(Self::Struct(Struct {
                                    name: type_name,
                                    extends,
                                    always_returned,
                                    fields,
                                    origin: Origin::default(),
                                })
                            )
                        }
                    },
                    Some("union") if let Some(name) = &ty.name => {
                        let type_name = TypeName::new(name, tags);
                        info!("Found union: `{}` (untagged: `{}`)", type_name.original, type_name.no_tag);

                        let fields = match &ty.spec {
                            TypeSpec::Members(members) => {
                                members.iter().filter_map(|member| match member {
                                    TypeMember::Comment(_) => None,
                                    TypeMember::Definition(def) => Some(def),
                                    _ => unreachable!("unknown member: {:?}", member)
                                })
                                .map(|def| Field::new(def, tags)).collect()
                            },
                            _ => unreachable!("the spec of a struct should always be `TypeSpec::Members`"),
                        };

                        Some(
                            Self::Union(Union {
                                name: type_name,
                                fields,
                                origin: Origin::default(),
                            })
                        )
                    },
                    Some("handle") => if let Some(name) = &ty.name {
                        let type_name = TypeName::new(name, tags);
                        info!("Found handle: `{}` (untagged: `{}`)", type_name.original, type_name.no_tag);

                        if let Some(alias) = &ty.alias {
                            Some(
                                Self::Alias(Alias {
                                    name: type_name,
                                    of: TypeName::new(alias, tags),
                                    origin: Origin::default(),
                                })
                            )
                        } else {
                            Some(
                                Self::Handle(Handle {
                                    name: type_name,
                                    object_ty: ty.objtypeenum.as_ref().map(|s| s as &str).map(Cow::Borrowed).expect("handle without object type"),
                                    parent: ty.parent.as_ref().map(|p| TypeName::new(p, tags)),
                                    origin: Origin::default(),
                            }))
                        }
                    } else {
                        let type_name = if let TypeSpec::Code(code) = &ty.spec {
                            TypeName::new(
                                code.markup.iter()
                                    .filter_map(|s| if let TypeCodeMarkup::Name(name) = s { Some(name) } else { None })
                                    .next()
                                    .expect("no name for handle"), tags)
                        } else {
                            panic!("handle without code: {:?}", ty)
                        };
                        Some(
                            Self::Handle(Handle {
                                name: type_name,
                                object_ty: ty.objtypeenum.as_ref().map(|s| s as &str).map(Cow::Borrowed).expect("handle without object type"),
                                parent: ty.parent.as_ref().map(|p| TypeName::new(p, tags)),
                                origin: Origin::default(),
                        }))
                    },
                    Some("funcpointer")  => {
                        let (code, name) = match &ty.spec {
                            TypeSpec::Code(code) => (code.code.trim(), code.markup.iter().filter_map(|s| match s {
                                TypeCodeMarkup::Name(name) => Some(name),
                                _ => None
                            }).next().expect("function pointer without name")),
                            _ => unreachable!("no code found for function pointer: {:?}", ty)
                        };

                        let name = FunctionName::new(name, tags);

                        let arguments = code.split('(').nth(2).expect("no arguments for function pointer")
                            .split(',')
                            .map(|elements| {
                                Ty::new(elements, "", tags)
                            })
                            .collect();

                        info!("Found funcpointer: `{}` (untagged: `{}`)", name.original, name.no_tag);

                        Some(
                            Type::FunctionPointer(FuncPointer {
                                name,
                                arguments,
                                origin: Origin::default(),
                            }
                        ))
                    },
                    Some("basetype") => {
                        let name = match &ty.spec {
                            TypeSpec::Code(code) => code.markup.iter().filter_map(|s| match s {
                                TypeCodeMarkup::Name(name) => Some(name),
                                _ => None
                            }).next().expect("basetype without name"),
                            _ => unreachable!("no code found for basetype: {:?}", ty)
                        };

                        let ty = match &ty.spec {
                            TypeSpec::Code(code) => code.markup.iter().filter_map(|s| match s {
                                TypeCodeMarkup::Type(ty) => Some(ty as &str),
                                _ => None
                            }).next().unwrap_or("void"),
                            _ => unreachable!("no code found for basetype: {:?}", ty)
                        };

                        let name = TypeName::new(name, tags);
                        info!("Found basetype: `{}` (untagged: `{}`)", name.original, name.no_tag);

                        Some(
                            Self::BaseType(BaseType {
                                name,
                                ty: Ty::new(ty, "", tags).1,
                                origin: Origin::default(),
                            })
                        )
                    },
                    Some("bitmask") => {
                        if let Some(alias) = &ty.alias {
                            let type_name = TypeName::new(ty.name.as_ref().expect("alias without a name"), tags);
                            info!("Found alias: `{}` (untagged: `{}`)", type_name.original, type_name.no_tag);

                            Some(Self::Alias(Alias {
                                name: type_name,
                                of: TypeName::new(alias, tags),
                                origin: Origin::default(),
                            }))
                        } else {
                            let name = match &ty.spec {
                                TypeSpec::Code(code) => code.markup.iter().filter_map(|s| match s {
                                    TypeCodeMarkup::Name(name) => Some(name),
                                    _ => None
                                }).next().expect("bitmask without name"),
                                _ => unreachable!("no code found for bitmask: {:?}", ty)
                            };


                            Some(
                                Self::Bitmask(Bitmask {
                                    name: TypeName::new(name, tags),
                                    bits: ty.bitvalues.as_ref().or(ty.requires.as_ref()).map(|r| TypeName::new(r, tags)),
                                    origin: Origin::default(),
                                }
                            ))
                        }
                    },
                    Some("enum") => if let (Some(name), Some(alias)) = (&ty.name, &ty.alias) {
                        let name = TypeName::new(name, tags);
                        info!("Found enum alias: `{}` (untagged: `{}`)", name.original, name.no_tag);

                        Some(
                            Type::Alias(Alias {
                                name,
                                of: TypeName::new(alias, tags),
                                origin: Origin::default(),
                            }
                        ))
                    } else if let Some(name) = &ty.name {
                        let name = TypeName::new(name, tags);
                        info!("Found enum: `{}` (untagged: `{}`)", name.original, name.no_tag);

                        /*Some(
                            Type::Enum(Enum::new_empty(name))
                        )*/

                        None
                    } else {
                        None
                    },
                    Some(other) => {
                        warn!("unknown category `{}`", other);
                        None
                    },
                    _ => None,
                }
            })
    }
}

#[derive(Debug, Clone, PartialEq)]
pub enum EnumValue<'a> {
    Alias {
        name: EnumVariant<'a>,
        of: EnumVariant<'a>,
        origin: Origin<'a>,
    },
    Offset {
        name: EnumVariant<'a>,
        base: i64,
        negative: bool,
        offset: i64,
        origin: Origin<'a>,
    },
    Bit {
        name: EnumVariant<'a>,
        bit: i64,
        origin: Origin<'a>,
    },
    Value {
        name: EnumVariant<'a>,
        value: Expr<'a>,
        origin: Origin<'a>,
    },
}

impl<'a> EnumValue<'a> {
    pub fn new(
        e: &'a vk_parse::Enum,
        extension_id: Option<i64>,
        origin: Origin<'a>,
        tags: &SymbolTable<'a, Cow<'a, str>>,
        enums: &SymbolTable<'a, Enum<'a>>,
    ) -> Self {
        let name = EnumVariant::new(&e.name, tags, enums);

        match &e.spec {
            EnumSpec::Alias { alias, .. } => Self::Alias {
                name,
                of: EnumVariant::new(alias, tags, enums),
                origin,
            },
            EnumSpec::Offset {
                offset, extnumber, dir, ..
            } => Self::Offset {
                name,
                base: extnumber.or(extension_id).unwrap_or(0),
                negative: *dir,
                offset: *offset,
                origin,
            },
            EnumSpec::Bitpos { bitpos, .. } => Self::Bit {
                name,
                bit: *bitpos,
                origin,
            },
            EnumSpec::Value { value, .. } => Self::Value {
                name,
                value: Expr::new(value),
                origin,
            },
            e => unreachable!("unvalid enum spec: {:?}", e),
        }
    }

    pub fn is_alias(&self) -> bool {
        matches!(self, Self::Alias { .. })
    }

    pub fn name(&self) -> &EnumVariant<'a> {
        match self {
            Self::Alias { name, .. }
            | Self::Bit { name, .. }
            | Self::Value { name, .. }
            | Self::Offset { name, .. } => name,
        }
    }

    pub fn origin(&self) -> &Origin<'a> {
        match self {
            Self::Alias { origin, .. }
            | Self::Bit { origin, .. }
            | Self::Value { origin, .. }
            | Self::Offset { origin, .. } => origin,
        }
    }

    pub fn origin_mut(&mut self) -> &mut Origin<'a> {
        match self {
            Self::Alias { origin, .. }
            | Self::Bit { origin, .. }
            | Self::Value { origin, .. }
            | Self::Offset { origin, .. } => origin,
        }
    }
}

impl<'a> SymbolName<'a> for EnumValue<'a> {
    fn name(&self) -> Cow<'a, str> {
        self.name().original.clone()
    }
}

#[derive(Debug, Clone, PartialEq)]
pub enum Enum<'a> {
    Bitmask {
        name: TypeName<'a>,
        values: SymbolTable<'a, EnumValue<'a>>,
        origin: Origin<'a>,
        bitwidth: u32,
    },
    Enum {
        name: TypeName<'a>,
        values: SymbolTable<'a, EnumValue<'a>>,
        origin: Origin<'a>,
    },
}

impl<'a> Enum<'a> {
    pub fn new_empty(name: TypeName<'a>) -> Self {
        Self::Enum {
            name,
            values: SymbolTable::default(),
            origin: Origin::default(),
        }
    }

    pub fn new<'b>(
        enums: &'a Enums,
        tags: &'b SymbolTable<'a, Cow<'a, str>>,
    ) -> Either<Self, impl Iterator<Item = Constant<'a>> + 'b> {
        let name = enums.name.as_ref().expect("found nameless enum");
        let mut origin = Origin::Unknown;
        if name == "API Constants" {
            origin = Origin::Core;
        }

        let name = TypeName::new(name, tags);

        let values: SymbolTable<'a, EnumValue<'a>> = enums
            .children
            .iter()
            .filter_map(|s| match s {
                EnumsChild::Enum(e) => Some(e),
                _ => None,
            })
            .map(|e| {
                let value = match &e.spec {
                    EnumSpec::Alias { alias, .. } => EnumValue::Alias {
                        name: EnumVariant::new_single(&e.name, tags, &name.original),
                        of: EnumVariant::new_single(alias, tags, &name.original),
                        origin: origin.clone(),
                    },
                    EnumSpec::Bitpos { bitpos, .. } => EnumValue::Bit {
                        name: EnumVariant::new_single(&e.name, tags, &name.original),
                        bit: *bitpos,
                        origin: origin.clone(),
                    },
                    EnumSpec::Value { value, .. } => EnumValue::Value {
                        name: EnumVariant::new_single(&e.name, tags, &name.original),
                        value: Expr::new(value),
                        origin: origin.clone(),
                    },
                    _ => unreachable!("invalid enum spec: {:#?}", e.spec),
                };

                value
            })
            .collect();

        match enums.kind.as_ref().map(|s| s as &str) {
            Some("bitmask") => Either::Left(Self::Bitmask {
                name,
                values,
                origin: origin.clone(),
                bitwidth: enums.bitwidth.unwrap_or(32),
            }),
            Some("enum") => Either::Left(Self::Enum {
                name,
                values,
                origin: origin.clone(),
            }),
            None => {
                let constants = values.into_iter().enumerate().map(move |(index, value)| {
                    let name = value.name().clone();

                    let value = if let EnumsChild::Enum(e) = &enums.children[index] {
                        if let Some(ty) = &e.type_suffix {
                            match value {
                                EnumValue::Bit { bit, .. } => ConstantValue::Bit(native_raw(ty).unwrap().1, bit),
                                EnumValue::Value { value, .. } => {
                                    ConstantValue::Value(native_raw(ty).unwrap().1, value)
                                },
                                EnumValue::Alias { of, .. } => ConstantValue::Alias(of.original),
                                _ => unreachable!("an offset should not be a constant"),
                            }
                        } else {
                            match value {
                                EnumValue::Alias { of, .. } => ConstantValue::Alias(of.original),
                                _ => unreachable!("should be an alias"),
                            }
                        }
                    } else {
                        unsafe {
                            unreachable_unchecked();
                        }
                    };

                    Constant {
                        name,
                        value,
                        origin: origin.clone(),
                    }
                });

                Either::Right(constants)
            },
            Some(ty) => unreachable!("enum with invalid type: {}", ty),
        }
    }

    pub fn name(&self) -> &TypeName<'a> {
        match self {
            Enum::Bitmask { name, .. } | Enum::Enum { name, .. } => name,
        }
    }

    pub fn value(&self, name: &str) -> Option<&EnumValue<'a>> {
        match self {
            Enum::Bitmask { values, .. } | Enum::Enum { values, .. } => values.get_by_name(name),
        }
    }

    pub fn value_mut(&mut self, name: &str) -> Option<&mut EnumValue<'a>> {
        match self {
            Enum::Bitmask { values, .. } | Enum::Enum { values, .. } => values.get_by_name_mut(name),
        }
    }

    pub fn values(&self) -> &SymbolTable<'a, EnumValue<'a>> {
        match self {
            Enum::Bitmask { values, .. } | Enum::Enum { values, .. } => values,
        }
    }

    pub fn values_mut(&mut self) -> &mut SymbolTable<'a, EnumValue<'a>> {
        match self {
            Enum::Bitmask { values, .. } | Enum::Enum { values, .. } => values,
        }
    }

    pub fn insert_value(&mut self, value: EnumValue<'a>) {
        self.values_mut().push(value);
    }
}

impl<'a> SymbolName<'a> for Enum<'a> {
    fn name(&self) -> Cow<'a, str> {
        self.name().original.clone()
    }
}
