use std::fmt::{self, Display};
use std::mem;
use std::os::raw::c_void;
use std::ptr;
use crate::array::{Array, VarLenArray};
use crate::string::{FixedAscii, FixedUnicode, VarLenAscii, VarLenUnicode};
#[allow(non_camel_case_types)]
#[repr(C)]
struct hvl_t {
    len: usize,
    p: *mut c_void,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum IntSize {
    U1 = 1,
    U2 = 2,
    U4 = 4,
    U8 = 8,
}
impl IntSize {
    pub fn from_int(size: usize) -> Option<IntSize> {
        if size == 1 {
            Some(IntSize::U1)
        } else if size == 2 {
            Some(IntSize::U2)
        } else if size == 4 {
            Some(IntSize::U4)
        } else if size == 8 {
            Some(IntSize::U8)
        } else {
            None
        }
    }
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum FloatSize {
    U4 = 4,
    U8 = 8,
}
impl FloatSize {
    pub fn from_int(size: usize) -> Option<FloatSize> {
        if size == 4 {
            Some(FloatSize::U4)
        } else if size == 8 {
            Some(FloatSize::U8)
        } else {
            None
        }
    }
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EnumMember {
    pub name: String,
    pub value: u64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EnumType {
    pub size: IntSize,
    pub signed: bool,
    pub members: Vec<EnumMember>,
}
impl EnumType {
    #[inline]
    pub fn base_type(&self) -> TypeDescriptor {
        if self.signed {
            TypeDescriptor::Integer(self.size)
        } else {
            TypeDescriptor::Unsigned(self.size)
        }
    }
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CompoundField {
    pub name: String,
    pub ty: TypeDescriptor,
    pub offset: usize,
    pub index: usize,
}
impl CompoundField {
    pub fn new(name: &str, ty: TypeDescriptor, offset: usize, index: usize) -> Self {
        Self { name: name.to_owned(), ty, offset, index }
    }
    pub fn typed<T: H5Type>(name: &str, offset: usize, index: usize) -> Self {
        Self::new(name, T::type_descriptor(), offset, index)
    }
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CompoundType {
    pub fields: Vec<CompoundField>,
    pub size: usize,
}
impl CompoundType {
    pub fn to_c_repr(&self) -> CompoundType {
        let mut layout = self.clone();
        layout.fields.sort_by_key(|f| f.index);
        let mut offset = 0;
        let mut max_align = 1;
        for f in layout.fields.iter_mut() {
            f.ty = f.ty.to_c_repr();
            let align = f.ty.c_alignment();
            while offset % align != 0 {
                offset += 1;
            }
            f.offset = offset;
            max_align = max_align.max(align);
            offset += f.ty.size();
            layout.size = offset;
            while layout.size % max_align != 0 {
                layout.size += 1;
            }
        }
        layout
    }
    pub fn to_packed_repr(&self) -> CompoundType {
        let mut layout = self.clone();
        layout.fields.sort_by_key(|f| f.index);
        layout.size = 0;
        for f in layout.fields.iter_mut() {
            f.ty = f.ty.to_packed_repr();
            f.offset = layout.size;
            layout.size += f.ty.size();
        }
        layout
    }
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TypeDescriptor {
    Integer(IntSize),
    Unsigned(IntSize),
    Float(FloatSize),
    Boolean,
    Enum(EnumType),
    Compound(CompoundType),
    FixedArray(Box<TypeDescriptor>, usize),
    FixedAscii(usize),
    FixedUnicode(usize),
    VarLenArray(Box<TypeDescriptor>),
    VarLenAscii,
    VarLenUnicode,
}
impl Display for TypeDescriptor {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            TypeDescriptor::Integer(IntSize::U1) => write!(f, "int8"),
            TypeDescriptor::Integer(IntSize::U2) => write!(f, "int16"),
            TypeDescriptor::Integer(IntSize::U4) => write!(f, "int32"),
            TypeDescriptor::Integer(IntSize::U8) => write!(f, "int64"),
            TypeDescriptor::Unsigned(IntSize::U1) => write!(f, "uint8"),
            TypeDescriptor::Unsigned(IntSize::U2) => write!(f, "uint16"),
            TypeDescriptor::Unsigned(IntSize::U4) => write!(f, "uint32"),
            TypeDescriptor::Unsigned(IntSize::U8) => write!(f, "uint64"),
            TypeDescriptor::Float(FloatSize::U4) => write!(f, "float32"),
            TypeDescriptor::Float(FloatSize::U8) => write!(f, "float64"),
            TypeDescriptor::Boolean => write!(f, "bool"),
            TypeDescriptor::Enum(ref tp) => write!(f, "enum ({})", tp.base_type()),
            TypeDescriptor::Compound(ref tp) => write!(f, "compound ({} fields)", tp.fields.len()),
            TypeDescriptor::FixedArray(ref tp, n) => write!(f, "[{}; {}]", tp, n),
            TypeDescriptor::FixedAscii(n) => write!(f, "string (len {})", n),
            TypeDescriptor::FixedUnicode(n) => write!(f, "unicode (len {})", n),
            TypeDescriptor::VarLenArray(ref tp) => write!(f, "[{}] (var len)", tp),
            TypeDescriptor::VarLenAscii => write!(f, "string (var len)"),
            TypeDescriptor::VarLenUnicode => write!(f, "unicode (var len)"),
        }
    }
}
impl TypeDescriptor {
    pub fn size(&self) -> usize {
        use self::TypeDescriptor::*;
        match *self {
            Integer(size) | Unsigned(size) => size as _,
            Float(size) => size as _,
            Boolean => 1,
            Enum(ref enum_type) => enum_type.size as _,
            Compound(ref compound) => compound.size,
            FixedArray(ref ty, len) => ty.size() * len,
            FixedAscii(len) | FixedUnicode(len) => len,
            VarLenArray(_) => mem::size_of::<hvl_t>(),
            VarLenAscii | VarLenUnicode => mem::size_of::<*const u8>(),
        }
    }
    fn c_alignment(&self) -> usize {
        use self::TypeDescriptor::*;
        match *self {
            Compound(ref compound) => {
                compound.fields.iter().map(|f| f.ty.c_alignment()).max().unwrap_or(1)
            }
            FixedArray(ref ty, _) => ty.c_alignment(),
            FixedAscii(_) | FixedUnicode(_) => 1,
            VarLenArray(_) => mem::size_of::<usize>(),
            _ => self.size(),
        }
    }
    pub fn to_c_repr(&self) -> Self {
        use self::TypeDescriptor::*;
        match *self {
            Compound(ref compound) => Compound(compound.to_c_repr()),
            FixedArray(ref ty, size) => FixedArray(Box::new(ty.to_c_repr()), size),
            VarLenArray(ref ty) => VarLenArray(Box::new(ty.to_c_repr())),
            _ => self.clone(),
        }
    }
    pub fn to_packed_repr(&self) -> Self {
        use self::TypeDescriptor::*;
        match *self {
            Compound(ref compound) => Compound(compound.to_packed_repr()),
            FixedArray(ref ty, size) => FixedArray(Box::new(ty.to_packed_repr()), size),
            VarLenArray(ref ty) => VarLenArray(Box::new(ty.to_packed_repr())),
            _ => self.clone(),
        }
    }
}
pub unsafe trait H5Type: 'static {
    fn type_descriptor() -> TypeDescriptor;
}
macro_rules! impl_h5type {
    ($ty:ty, $variant:ident, $size:expr) => {
        unsafe impl H5Type for $ty {
            #[inline]
            fn type_descriptor() -> TypeDescriptor {
                $crate::h5type::TypeDescriptor::$variant($size)
            }
        }
    };
}
impl_h5type!(i8, Integer, IntSize::U1);
impl_h5type!(i16, Integer, IntSize::U2);
impl_h5type!(i32, Integer, IntSize::U4);
impl_h5type!(i64, Integer, IntSize::U8);
impl_h5type!(u8, Unsigned, IntSize::U1);
impl_h5type!(u16, Unsigned, IntSize::U2);
impl_h5type!(u32, Unsigned, IntSize::U4);
impl_h5type!(u64, Unsigned, IntSize::U8);
impl_h5type!(f32, Float, FloatSize::U4);
impl_h5type!(f64, Float, FloatSize::U8);
#[cfg(target_pointer_width = "32")]
impl_h5type!(isize, Integer, IntSize::U4);
#[cfg(target_pointer_width = "32")]
impl_h5type!(usize, Unsigned, IntSize::U4);
#[cfg(target_pointer_width = "64")]
impl_h5type!(isize, Integer, IntSize::U8);
#[cfg(target_pointer_width = "64")]
impl_h5type!(usize, Unsigned, IntSize::U8);
unsafe impl H5Type for bool {
    #[inline]
    fn type_descriptor() -> TypeDescriptor {
        TypeDescriptor::Boolean
    }
}
macro_rules! impl_tuple {
    (@second $a:tt $b:tt) => ($b);
    (@parse_fields [$($s:ident)*] $origin:ident $fields:ident | $t:ty $(,$tt:ty)*) => (
        let &$($s)*(.., ref f, $(impl_tuple!(@second $tt _),)*) = unsafe { &*$origin };
        let index = $fields.len();
        $fields.push(CompoundField {
            name: format!("{}", index),
            ty: <$t as H5Type>::type_descriptor(),
            offset: f as *const _ as _,
            index,
        });
        impl_tuple!(@parse_fields [$($s)*] $origin $fields | $($tt),*);
    );
    (@parse_fields [$($s:ident)*] $origin:ident $fields:ident |) => ();
    ($t:ident) => (
        unsafe impl<$t> H5Type for ($t,) where $t: H5Type {
            #[inline]
            fn type_descriptor() -> TypeDescriptor {
                let size = mem::size_of::<($t,)>();
                assert_eq!(size, mem::size_of::<$t>());
                TypeDescriptor::Compound(CompoundType {
                    fields: vec![CompoundField::typed::<$t>("0", 0, 0)],
                    size,
                })
            }
        }
    );
    ($t:ident, $($tt:ident),*) => (
        #[allow(dead_code, unused_variables)]
        unsafe impl<$t, $($tt),*> H5Type for ($t, $($tt),*)
            where $t: H5Type, $($tt: H5Type),*
        {
            fn type_descriptor() -> TypeDescriptor {
                let origin: *const Self = ptr::null();
                let mut fields = Vec::new();
                impl_tuple!(@parse_fields [] origin fields | $t, $($tt),*);
                let size = mem::size_of::<Self>();
                fields.sort_by_key(|f| f.offset);
                TypeDescriptor::Compound(CompoundType { fields, size })
            }
        }
        impl_tuple!($($tt),*);
    );
}
impl_tuple! { A, B, C, D, E, F, G, H, I, J, K, L }
unsafe impl<T: Array<Item = I>, I: H5Type> H5Type for T {
    #[inline]
    fn type_descriptor() -> TypeDescriptor {
        TypeDescriptor::FixedArray(
            Box::new(<I as H5Type>::type_descriptor()),
            <T as Array>::capacity(),
        )
    }
}
unsafe impl<T: Copy + H5Type> H5Type for VarLenArray<T> {
    #[inline]
    fn type_descriptor() -> TypeDescriptor {
        TypeDescriptor::VarLenArray(Box::new(<T as H5Type>::type_descriptor()))
    }
}
unsafe impl<A: Array<Item = u8>> H5Type for FixedAscii<A> {
    #[inline]
    fn type_descriptor() -> TypeDescriptor {
        TypeDescriptor::FixedAscii(A::capacity())
    }
}
unsafe impl<A: Array<Item = u8>> H5Type for FixedUnicode<A> {
    #[inline]
    fn type_descriptor() -> TypeDescriptor {
        TypeDescriptor::FixedUnicode(A::capacity())
    }
}
unsafe impl H5Type for VarLenAscii {
    #[inline]
    fn type_descriptor() -> TypeDescriptor {
        TypeDescriptor::VarLenAscii
    }
}
unsafe impl H5Type for VarLenUnicode {
    #[inline]
    fn type_descriptor() -> TypeDescriptor {
        TypeDescriptor::VarLenUnicode
    }
}
#[cfg(test)]
pub mod tests {
    use super::TypeDescriptor as TD;
    use super::{hvl_t, CompoundField, CompoundType, FloatSize, H5Type, IntSize};
    use crate::array::VarLenArray;
    use crate::string::{FixedAscii, FixedUnicode, VarLenAscii, VarLenUnicode};
    use std::mem;
    #[test]
    pub fn test_scalar_types() {
        assert_eq!(bool::type_descriptor(), TD::Boolean);
        assert_eq!(i8::type_descriptor(), TD::Integer(IntSize::U1));
        assert_eq!(i16::type_descriptor(), TD::Integer(IntSize::U2));
        assert_eq!(i32::type_descriptor(), TD::Integer(IntSize::U4));
        assert_eq!(i64::type_descriptor(), TD::Integer(IntSize::U8));
        assert_eq!(u8::type_descriptor(), TD::Unsigned(IntSize::U1));
        assert_eq!(u16::type_descriptor(), TD::Unsigned(IntSize::U2));
        assert_eq!(u32::type_descriptor(), TD::Unsigned(IntSize::U4));
        assert_eq!(u64::type_descriptor(), TD::Unsigned(IntSize::U8));
        assert_eq!(f32::type_descriptor(), TD::Float(FloatSize::U4));
        assert_eq!(f64::type_descriptor(), TD::Float(FloatSize::U8));
        assert_eq!(bool::type_descriptor().size(), 1);
        assert_eq!(i16::type_descriptor().size(), 2);
        assert_eq!(u32::type_descriptor().size(), 4);
        assert_eq!(f64::type_descriptor().size(), 8);
    }
    #[test]
    #[cfg(target_pointer_width = "32")]
    pub fn test_ptr_sized_ints() {
        assert_eq!(isize::type_descriptor(), TD::Integer(IntSize::U4));
        assert_eq!(usize::type_descriptor(), TD::Unsigned(IntSize::U4));
        assert_eq!(usize::type_descriptor().size(), 4);
    }
    #[test]
    #[cfg(target_pointer_width = "64")]
    pub fn test_ptr_sized_ints() {
        assert_eq!(isize::type_descriptor(), TD::Integer(IntSize::U8));
        assert_eq!(usize::type_descriptor(), TD::Unsigned(IntSize::U8));
        assert_eq!(usize::type_descriptor().size(), 8);
    }
    #[test]
    pub fn test_fixed_array() {
        type S = [T; 4];
        type T = [u32; 256];
        assert_eq!(T::type_descriptor(), TD::FixedArray(Box::new(TD::Unsigned(IntSize::U4)), 256));
        assert_eq!(S::type_descriptor(), TD::FixedArray(Box::new(T::type_descriptor()), 4));
    }
    #[test]
    pub fn test_varlen_array() {
        type S = VarLenArray<u16>;
        assert_eq!(S::type_descriptor(), TD::VarLenArray(Box::new(u16::type_descriptor())));
        assert_eq!(mem::size_of::<VarLenArray<u8>>(), mem::size_of::<hvl_t>());
    }
    #[test]
    pub fn test_string_types() {
        type FA = FixedAscii<[u8; 16]>;
        type FU = FixedUnicode<[u8; 32]>;
        assert_eq!(FA::type_descriptor(), TD::FixedAscii(16));
        assert_eq!(FU::type_descriptor(), TD::FixedUnicode(32));
        assert_eq!(VarLenAscii::type_descriptor(), TD::VarLenAscii);
        assert_eq!(VarLenUnicode::type_descriptor(), TD::VarLenUnicode);
    }
    #[test]
    pub fn test_tuples() {
        type T1 = (u16,);
        let td = T1::type_descriptor();
        assert_eq!(
            td,
            TD::Compound(CompoundType {
                fields: vec![CompoundField::typed::<u16>("0", 0, 0),],
                size: 2,
            })
        );
        assert_eq!(td.size(), 2);
        assert_eq!(mem::size_of::<T1>(), 2);
        type T2 = (i32, f32, (u64,));
        let td = T2::type_descriptor();
        assert_eq!(
            td,
            TD::Compound(CompoundType {
                fields: vec![
                    CompoundField::typed::<i32>("0", 0, 0),
                    CompoundField::typed::<f32>("1", 4, 1),
                    CompoundField::new(
                        "2",
                        TD::Compound(CompoundType {
                            fields: vec![CompoundField::typed::<u64>("0", 0, 0),],
                            size: 8,
                        }),
                        8,
                        2
                    ),
                ],
                size: 16,
            })
        );
        assert_eq!(td.size(), 16);
        assert_eq!(mem::size_of::<T2>(), 16);
    }
    #[test]
    pub fn test_tuple_various_reprs() {
        type T = (i8, u64, f32, bool);
        assert_eq!(mem::size_of::<T>(), 16);
        let td = T::type_descriptor();
        assert_eq!(
            td,
            TD::Compound(CompoundType {
                fields: vec![
                    CompoundField::typed::<u64>("1", 0, 1),
                    CompoundField::typed::<f32>("2", 8, 2),
                    CompoundField::typed::<i8>("0", 12, 0),
                    CompoundField::typed::<bool>("3", 13, 3),
                ],
                size: 16,
            })
        );
        assert_eq!(td.size(), 16);
        let td = T::type_descriptor().to_c_repr();
        assert_eq!(
            td,
            TD::Compound(CompoundType {
                fields: vec![
                    CompoundField::typed::<i8>("0", 0, 0),
                    CompoundField::typed::<u64>("1", 8, 1),
                    CompoundField::typed::<f32>("2", 16, 2),
                    CompoundField::typed::<bool>("3", 20, 3),
                ],
                size: 24,
            })
        );
        assert_eq!(td.size(), 24);
        let td = T::type_descriptor().to_packed_repr();
        assert_eq!(
            td,
            TD::Compound(CompoundType {
                fields: vec![
                    CompoundField::typed::<i8>("0", 0, 0),
                    CompoundField::typed::<u64>("1", 1, 1),
                    CompoundField::typed::<f32>("2", 9, 2),
                    CompoundField::typed::<bool>("3", 13, 3),
                ],
                size: 14,
            })
        );
        assert_eq!(td.size(), 14);
    }
}