use quickcheck::{Arbitrary, Gen};
use std::fmt;
use std::fmt::Write as _;

/// `BaseTypeC` is used in generation of C headers to represent the C language's
/// primitive types as well as `void*`.
#[derive(Debug, Clone)]
pub struct BaseTypeC {
    /// String representation of C type.
    pub def: String,
}

/// `TypeQualifierC` is used in generation of C headers to represent qualifiers
/// such as `const`.
#[derive(Debug, Clone)]
pub struct TypeQualifierC {
    /// String representation of C type qualifier.
    pub def: String,
}

/// `PointerLevelC` is used in generation of C headers to represent number of
/// `*` for pointer types.
#[derive(Debug, Clone)]
pub struct PointerLevelC {
    /// String representation of C declaration's pointer level.
    pub def: String,
}

/// `ArrayDimensionC` is used in generation of C headers to represent number of
/// `[]` used to define array types.
#[derive(Debug, Clone)]
pub struct ArrayDimensionC {
    /// String representation of C declaration's array dimension.
    pub def: String,
}

/// `BasicTypeDeclarationC` is used in generation of C headers to represent
/// declarations outside of function pointers that take the form
/// `BaseTypeC` + `TypeQualifierC` + `PointerLevelC` + `ident_id`.
#[derive(Debug, Clone)]
pub struct BasicTypeDeclarationC {
    /// The declaration's base type, i.e. `int`.
    pub type_name: BaseTypeC,
    /// The declaration's type qualifier, i.e. `const`.
    pub type_qualifier: TypeQualifierC,
    /// The declaration's pointer level, i.e. `***`.
    pub pointer_level: PointerLevelC,
    /// The declaration's array dimension, i.e. [][][].
    pub array_dimension: ArrayDimensionC,
    /// The declaration's identifier, i.e. `ident_N`.
    pub ident_id: String,
}

/// `StructDeclarationC` is used in generation of C headers to represent the
/// definition of a struct type.
#[derive(Debug, Clone)]
pub struct StructDeclarationC {
    /// The declaration's fields.
    pub fields: DeclarationListC,
    /// The declaration's array dimension, i.e. [][][].
    pub array_dimension: ArrayDimensionC,
    /// The declaration's identifier, i.e. `struct_N`.
    pub ident_id: String,
}

/// `UnionDeclarationC` is used in generation of C headers to represent the
/// definition of a union type.
#[derive(Debug, Clone)]
pub struct UnionDeclarationC {
    /// The declaration's fields.
    pub fields: DeclarationListC,
    /// The declaration's array dimension, i.e. [][][].
    pub array_dimension: ArrayDimensionC,
    /// The declaration's identifier, i.e. `union_N`.
    pub ident_id: String,
}

/// `FunctionPointerDeclarationC` is used in generation of C headers to represent
/// the definition of a function pointer type.
#[derive(Debug, Clone)]
pub struct FunctionPointerDeclarationC {
    /// The function's type qualifier, i.e. `const`.
    pub type_qualifier: TypeQualifierC,
    /// The function's return type, i.e. `int`.
    pub type_name: BaseTypeC,
    /// The function's pointer level, i.e. `***`.
    pub pointer_level: PointerLevelC,
    /// The function's parameters.
    pub params: ParameterListC,
    /// The declaration's identifier, i.e. `func_ptr_N`.
    pub ident_id: String,
}

/// `FunctionPrototypeC` is used in generation of C headers to represent the
/// definition of a function prototype.
#[derive(Debug, Clone)]
pub struct FunctionPrototypeC {
    /// The function's type qualifier, i.e. `const`.
    pub type_qualifier: TypeQualifierC,
    /// The function's return type, i.e. `int`.
    pub type_name: BaseTypeC,
    /// The function's pointer level, i.e. `***`.
    pub pointer_level: PointerLevelC,
    /// The function's parameters.
    pub params: ParameterListC,
    /// The prototype's identifier, i.e. `func_N`.
    pub ident_id: String,
}

/// `ParameterC` is used in generation of C headers to represent the
/// definition function parameters.
#[derive(Debug, Clone)]
pub struct ParameterC {
    /// The parameter's type qualifier, i.e. `const`.
    pub type_qualifier: TypeQualifierC,
    /// The parameter's base type, i.e. `int`.
    pub type_name: BaseTypeC,
    /// The parameter's pointer level, i.e. `***`.
    pub pointer_level: PointerLevelC,
}

/// `ParameterListC` is used in generation of C headers to represent a list of
/// definitions of function parameters.
#[derive(Debug, Clone)]
pub struct ParameterListC {
    /// Parameters that define a C function signature.
    pub params: Vec<ParameterC>,
}

/// `DeclarationC` is used in generation of C headers to represent all supported
/// C type declarations allowed in the generated header.
#[derive(Debug, Clone)]
pub enum DeclarationC {
    /// Function prototype declaration kind.
    FunctionDecl(FunctionPrototypeC),
    /// Function pointer declaration kind.
    FunctionPtrDecl(FunctionPointerDeclarationC),
    /// Struct declaration kind.
    StructDecl(StructDeclarationC),
    /// Union declaration kind.
    UnionDecl(UnionDeclarationC),
    /// Basic type declaration kind.
    VariableDecl(BasicTypeDeclarationC),
}

/// `DeclarationListC` is used in generation of C headers to represent a list of
/// declarations.
#[derive(Debug, Clone)]
pub struct DeclarationListC {
    /// Grouping of C declarations.
    pub decls: Vec<DeclarationC>,
}

/// `HeaderC` is used in generation of C headers to represent a collection of
/// declarations.
#[derive(Clone)]
pub struct HeaderC {
    /// The header's declarations.
    pub def: DeclarationListC,
}

/// `MakeUnique` is used in generation of C headers to make declaration
/// identifiers unique by incorporating the `stamp` parameter into it's name.
trait MakeUnique {
    fn make_unique(&mut self, stamp: usize);
}

/// `MakeUnique` is used in generation of C headers to make `DeclarationC`
/// identifiers unique.
impl MakeUnique for DeclarationC {
    fn make_unique(&mut self, stamp: usize) {
        match *self {
            DeclarationC::FunctionDecl(ref mut d) => d.make_unique(stamp),
            DeclarationC::FunctionPtrDecl(ref mut d) => d.make_unique(stamp),
            DeclarationC::StructDecl(ref mut d) => d.make_unique(stamp),
            DeclarationC::UnionDecl(ref mut d) => d.make_unique(stamp),
            DeclarationC::VariableDecl(ref mut d) => d.make_unique(stamp),
        }
    }
}

/// A qucickcheck trait for describing how `DeclarationC` types can be
/// randomly generated and shrunk.
impl Arbitrary for DeclarationC {
    fn arbitrary(g: &mut Gen) -> DeclarationC {
        match gen_range(g, 0, 5) {
            0 => DeclarationC::FunctionDecl(FunctionPrototypeC::arbitrary(g)),
            1 => DeclarationC::FunctionPtrDecl(
                FunctionPointerDeclarationC::arbitrary(g),
            ),
            2 => DeclarationC::StructDecl(StructDeclarationC::arbitrary(g)),
            3 => DeclarationC::UnionDecl(UnionDeclarationC::arbitrary(g)),
            4 => {
                DeclarationC::VariableDecl(BasicTypeDeclarationC::arbitrary(g))
            }
            _ => unreachable!(),
        }
    }
}

/// Enables to string and format for `DeclarationC` types.
impl fmt::Display for DeclarationC {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            DeclarationC::FunctionPtrDecl(ref d) => write!(f, "{d}"),
            DeclarationC::StructDecl(ref d) => write!(f, "{d}"),
            DeclarationC::UnionDecl(ref d) => write!(f, "{d}"),
            DeclarationC::VariableDecl(ref d) => write!(f, "{d}"),
            DeclarationC::FunctionDecl(ref d) => write!(f, "{d}"),
        }
    }
}

/// A qucickcheck trait for describing how `DeclarationListC` types can be
/// randomly generated and shrunk.
impl Arbitrary for DeclarationListC {
    fn arbitrary(g: &mut Gen) -> DeclarationListC {
        DeclarationListC {
            decls: Arbitrary::arbitrary(g),
        }
    }
}

/// Enables to string and format for `DeclarationListC` types.
impl fmt::Display for DeclarationListC {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        for decl in &self.decls {
            write!(f, "{decl}")?;
        }
        Ok(())
    }
}

/// A quickcheck trait for describing how `BaseTypeC` types can be
/// randomly generated and shrunk.
impl Arbitrary for BaseTypeC {
    fn arbitrary(g: &mut Gen) -> BaseTypeC {
        // Special case `long double` until issue #550 is resolved.
        let base_type = vec![
            "char",
            "signed char",
            "unsigned char",
            "short",
            "short int",
            "signed short",
            "signed short int",
            "unsigned short",
            "unsigned short int",
            "int",
            "signed",
            "signed int",
            "unsigned",
            "unsigned int",
            "long",
            "long int",
            "signed long",
            "signed long int",
            "unsigned long",
            "unsigned long int",
            "long long",
            "long long int",
            "signed long long",
            "signed long long int",
            "unsigned long long",
            "unsigned long long int",
            "float",
            "double",
            #[cfg(feature = "long-doubles")]
            "long double",
            "void*",
        ];
        BaseTypeC {
            def: String::from(*g.choose(&base_type).unwrap()),
        }
    }
}

/// Enables to string and format for `BaseTypeC` types,
impl fmt::Display for BaseTypeC {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.def)
    }
}

/// A qucickcheck trait for describing how `TypeQualifierC` types can be
/// randomly generated and shrunk.
impl Arbitrary for TypeQualifierC {
    fn arbitrary(g: &mut Gen) -> TypeQualifierC {
        let qualifier = vec!["const", ""];
        TypeQualifierC {
            def: String::from(*g.choose(&qualifier).unwrap()),
        }
    }
}

/// Enables to string and format for `TypeQualifierC` types.
impl fmt::Display for TypeQualifierC {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.def)
    }
}

/// A qucickcheck trait for describing how `PointerLevelC` types can be
/// randomly generated and shrunk.
impl Arbitrary for PointerLevelC {
    fn arbitrary(g: &mut Gen) -> PointerLevelC {
        PointerLevelC {
            // 16 is an arbitrary "not too big" number for capping pointer level.
            def: (0..gen_range(g, 0, 16)).map(|_| "*").collect::<String>(),
        }
    }
}

/// Enables to string and format for `PointerLevelC` types.
impl fmt::Display for PointerLevelC {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.def)
    }
}

/// A qucickcheck trait for describing how `ArrayDimensionC` types can be
/// randomly generated and shrunk.
impl Arbitrary for ArrayDimensionC {
    fn arbitrary(g: &mut Gen) -> ArrayDimensionC {
        // Keep these small, clang complains when they get too big.
        let dimensions = gen_range(g, 0, 5);
        let mut def = String::new();

        let lower_bound = u64::from(cfg!(feature = "zero-sized-arrays"));

        for _ in 1..dimensions {
            // 16 is an arbitrary "not too big" number for capping array size.
            let _ = write!(def, "[{}]", gen_range(g, lower_bound, 16));
        }
        ArrayDimensionC { def }
    }
}

/// Enables to string and format for `ArrayDimensionC` types.
impl fmt::Display for ArrayDimensionC {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.def)
    }
}

/// `MakeUnique` is used in generation of C headers to make `BasicTypeDeclarationC`
/// identifiers unique.
impl MakeUnique for BasicTypeDeclarationC {
    fn make_unique(&mut self, stamp: usize) {
        let _ = write!(self.ident_id, "_{stamp}");
    }
}

/// A qucickcheck trait for describing how `BasicTypeDeclarationC` types can be
/// randomly generated and shrunk.
impl Arbitrary for BasicTypeDeclarationC {
    fn arbitrary(g: &mut Gen) -> BasicTypeDeclarationC {
        BasicTypeDeclarationC {
            type_qualifier: Arbitrary::arbitrary(g),
            type_name: Arbitrary::arbitrary(g),
            pointer_level: Arbitrary::arbitrary(g),
            array_dimension: Arbitrary::arbitrary(g),
            ident_id: format!("{}", usize::arbitrary(g)),
        }
    }
}

/// Enables to string and format for `BasicTypeDeclarationC` types.
impl fmt::Display for BasicTypeDeclarationC {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{} {} {} ident_{}{};",
            self.type_qualifier,
            self.type_name,
            self.pointer_level,
            self.ident_id,
            self.array_dimension
        )
    }
}

/// `MakeUnique` is used in generation of C headers to make `StructDeclarationC`
/// identifiers unique.
impl MakeUnique for StructDeclarationC {
    fn make_unique(&mut self, stamp: usize) {
        let _ = write!(self.ident_id, "_{stamp}");
    }
}

/// A qucickcheck trait for describing how `StructDeclarationC` types can be
/// randomly generated and shrunk.
impl Arbitrary for StructDeclarationC {
    fn arbitrary(g: &mut Gen) -> StructDeclarationC {
        // Reduce generator size as a method of putting a bound on recursion.
        // When size < 1 the empty list is generated.
        let reduced_size: usize = (g.size() / 2) + 1;
        let mut decl_list: DeclarationListC =
            Arbitrary::arbitrary(&mut Gen::new(reduced_size));
        let mut fields: DeclarationListC = DeclarationListC { decls: vec![] };

        for (i, decl) in decl_list.decls.iter_mut().enumerate() {
            match *decl {
                DeclarationC::FunctionDecl(_) => {}
                ref mut decl => {
                    decl.make_unique(i);
                    fields.decls.push(decl.clone());
                }
            }
        }

        StructDeclarationC {
            fields,
            ident_id: format!("{}", usize::arbitrary(g)),
            array_dimension: Arbitrary::arbitrary(g),
        }
    }
}

/// Enables to string and format for `StructDeclarationC` types.
impl fmt::Display for StructDeclarationC {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "struct {{ {} }} struct_{}{};",
            self.fields, self.ident_id, self.array_dimension
        )
    }
}

/// `MakeUnique` is used in generation of C headers to make `UnionDeclarationC`
/// identifiers unique.
impl MakeUnique for UnionDeclarationC {
    fn make_unique(&mut self, stamp: usize) {
        let _ = write!(self.ident_id, "_{stamp}");
    }
}

/// A qucickcheck trait for describing how `UnionDeclarationC` types can be
/// randomly generated and shrunk.
impl Arbitrary for UnionDeclarationC {
    fn arbitrary(g: &mut Gen) -> UnionDeclarationC {
        // Reduce generator size as a method of putting a bound on recursion.
        // When size < 1 the empty list is generated.
        let reduced_size: usize = (g.size() / 2) + 1;
        let mut decl_list: DeclarationListC =
            Arbitrary::arbitrary(&mut Gen::new(reduced_size));
        let mut fields: DeclarationListC = DeclarationListC { decls: vec![] };

        for (i, decl) in decl_list.decls.iter_mut().enumerate() {
            match *decl {
                DeclarationC::FunctionDecl(_) => {}
                ref mut decl => {
                    decl.make_unique(i);
                    fields.decls.push(decl.clone());
                }
            }
        }

        UnionDeclarationC {
            fields,
            ident_id: format!("{}", usize::arbitrary(g)),
            array_dimension: Arbitrary::arbitrary(g),
        }
    }
}

/// Enables to string and format for `UnionDeclarationC` types.
impl fmt::Display for UnionDeclarationC {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "union {{ {} }} union_{}{};",
            self.fields, self.ident_id, self.array_dimension
        )
    }
}

/// `MakeUnique` is used in generation of C headers to make
/// `FunctionPointerDeclarationC` identifiers unique.
impl MakeUnique for FunctionPointerDeclarationC {
    fn make_unique(&mut self, stamp: usize) {
        let _ = write!(self.ident_id, "_{stamp}");
    }
}

/// A qucickcheck trait for describing how `FunctionPointerDeclarationC` types can
/// be randomly generated and shrunk.
impl Arbitrary for FunctionPointerDeclarationC {
    fn arbitrary(g: &mut Gen) -> FunctionPointerDeclarationC {
        FunctionPointerDeclarationC {
            type_qualifier: Arbitrary::arbitrary(g),
            type_name: Arbitrary::arbitrary(g),
            pointer_level: Arbitrary::arbitrary(g),
            params: Arbitrary::arbitrary(g),
            ident_id: format!("{}", usize::arbitrary(g)),
        }
    }
}

/// Enables to string and format for `FunctionPointerDeclarationC` types.
impl fmt::Display for FunctionPointerDeclarationC {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{} {} {} (*func_ptr_{})({});",
            self.type_qualifier,
            self.type_name,
            self.pointer_level,
            self.ident_id,
            self.params
        )
    }
}

/// `MakeUnique` is used in generation of C headers to make `FunctionPrototypeC`
/// identifiers unique.
impl MakeUnique for FunctionPrototypeC {
    fn make_unique(&mut self, stamp: usize) {
        let _ = write!(self.ident_id, "_{stamp}");
    }
}

/// A qucickcheck trait for describing how `FunctionPrototypeC` types can be
/// randomly generated and shrunk.
impl Arbitrary for FunctionPrototypeC {
    fn arbitrary(g: &mut Gen) -> FunctionPrototypeC {
        FunctionPrototypeC {
            type_qualifier: Arbitrary::arbitrary(g),
            type_name: Arbitrary::arbitrary(g),
            pointer_level: Arbitrary::arbitrary(g),
            params: Arbitrary::arbitrary(g),
            ident_id: format!("{}", usize::arbitrary(g)),
        }
    }
}

/// Enables to string and format for `FunctionPrototypeC` types.
impl fmt::Display for FunctionPrototypeC {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{} {} {} func_{}({});",
            self.type_qualifier,
            self.type_name,
            self.pointer_level,
            self.ident_id,
            self.params
        )
    }
}

/// A qucickcheck trait for describing how `ParameterC` types can be
/// randomly generated and shrunk.
impl Arbitrary for ParameterC {
    fn arbitrary(g: &mut Gen) -> ParameterC {
        ParameterC {
            type_qualifier: Arbitrary::arbitrary(g),
            type_name: Arbitrary::arbitrary(g),
            pointer_level: Arbitrary::arbitrary(g),
        }
    }
}

/// Enables to string and format for `ParameterC` types.
impl fmt::Display for ParameterC {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{} {} {}",
            self.type_qualifier, self.type_name, self.pointer_level
        )
    }
}

/// A qucickcheck trait for describing how `ParameterListC` types can be
/// randomly generated and shrunk.
impl Arbitrary for ParameterListC {
    fn arbitrary(g: &mut Gen) -> ParameterListC {
        ParameterListC {
            params: Arbitrary::arbitrary(g),
        }
    }
}

/// Enables to string and format for `ParameterListC` types.
impl fmt::Display for ParameterListC {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        for (i, p) in self.params.iter().enumerate() {
            match i {
                0 => write!(f, "{p}")?,
                _ => write!(f, ",{p}")?,
            }
        }
        Ok(())
    }
}

/// A qucickcheck trait for describing how `HeaderC` types can be
/// randomly generated and shrunk.
impl Arbitrary for HeaderC {
    fn arbitrary(g: &mut Gen) -> HeaderC {
        let mut decl_list: DeclarationListC = Arbitrary::arbitrary(g);
        for (i, decl) in decl_list.decls.iter_mut().enumerate() {
            decl.make_unique(i);
        }
        HeaderC { def: decl_list }
    }
}

/// Enables to string and format for `HeaderC` types.
impl fmt::Display for HeaderC {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        for decl in &self.def.decls {
            write!(f, "{decl}")?;
        }
        Ok(())
    }
}

/// Use Display trait for Debug so that any failing property tests report
/// generated C code rather than the data structures that contain it.
impl fmt::Debug for HeaderC {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{self}")
    }
}

/// FIXME: is this actually uniform?
fn gen_range(gen: &mut Gen, lo: u64, hi: u64) -> u64 {
    let len = hi - lo;
    (u64::arbitrary(gen) % len) + lo
}
