use crate::builder::compute_layout;
use crate::layout::{
    Annotation, Array, BuiltinType, Record, RecordField, RecordKind, Type, TypeLayout, TypeVariant,
};
use crate::result::ErrorType;
use crate::target::Target;

#[test]
fn annotated_builtin() {
    let ty = Type::<()> {
        layout: (),
        annotations: vec![Annotation::AttrPacked],
        variant: TypeVariant::Builtin(BuiltinType::Int),
    };
    let err = compute_layout(Target::X86_64UnknownLinuxGnu, &ty).unwrap_err();
    assert!(matches!(err.kind(), ErrorType::AnnotatedBuiltinType));
}

#[test]
fn annotated_opaque() {
    let ty = Type::<()> {
        layout: (),
        annotations: vec![Annotation::AttrPacked],
        variant: TypeVariant::Opaque(TypeLayout {
            size_bits: 0,
            field_alignment_bits: 8,
            pointer_alignment_bits: 8,
            required_alignment_bits: 8,
        }),
    };
    let err = compute_layout(Target::X86_64UnknownLinuxGnu, &ty).unwrap_err();
    assert!(matches!(err.kind(), ErrorType::AnnotatedOpaqueType));
}

#[test]
fn annotated_array() {
    let ty = Type::<()> {
        layout: (),
        annotations: vec![Annotation::AttrPacked],
        variant: TypeVariant::Array(Array {
            element_type: Box::new(Type {
                layout: (),
                annotations: vec![],
                variant: TypeVariant::Builtin(BuiltinType::Int),
            }),
            num_elements: None,
        }),
    };
    let err = compute_layout(Target::X86_64UnknownLinuxGnu, &ty).unwrap_err();
    assert!(matches!(err.kind(), ErrorType::AnnotatedArray));
}

#[test]
fn size_overflow() {
    let ty = Type::<()> {
        layout: (),
        annotations: vec![],
        variant: TypeVariant::Array(Array {
            element_type: Box::new(Type {
                layout: (),
                annotations: vec![],
                variant: TypeVariant::Builtin(BuiltinType::Int),
            }),
            num_elements: Some(u64::MAX / 8 / 4 + 1),
        }),
    };
    let err = compute_layout(Target::X86_64UnknownLinuxGnu, &ty).unwrap_err();
    assert!(matches!(err.kind(), ErrorType::SizeOverflow));
}

#[test]
fn power_of_two_alignment_1() {
    let ty = Type::<()> {
        layout: (),
        annotations: vec![Annotation::Align(Some(24))],
        variant: TypeVariant::Record(Record {
            kind: RecordKind::Struct,
            fields: vec![],
        }),
    };
    let err = compute_layout(Target::X86_64UnknownLinuxGnu, &ty).unwrap_err();
    assert!(matches!(err.kind(), ErrorType::PowerOfTwoAlignment));
}

#[test]
fn power_of_two_alignment_2() {
    let ty = Type::<()> {
        layout: (),
        annotations: vec![],
        variant: TypeVariant::Opaque(TypeLayout {
            size_bits: 0,
            field_alignment_bits: 8,
            pointer_alignment_bits: 24,
            required_alignment_bits: 8,
        }),
    };
    let err = compute_layout(Target::X86_64UnknownLinuxGnu, &ty).unwrap_err();
    assert!(matches!(err.kind(), ErrorType::PowerOfTwoAlignment));
}

#[test]
fn power_of_two_alignment_3() {
    let ty = Type::<()> {
        layout: (),
        annotations: vec![],
        variant: TypeVariant::Opaque(TypeLayout {
            size_bits: 0,
            field_alignment_bits: 24,
            pointer_alignment_bits: 8,
            required_alignment_bits: 8,
        }),
    };
    let err = compute_layout(Target::X86_64UnknownLinuxGnu, &ty).unwrap_err();
    assert!(matches!(err.kind(), ErrorType::PowerOfTwoAlignment));
}

#[test]
fn power_of_two_alignment_4() {
    let ty = Type::<()> {
        layout: (),
        annotations: vec![],
        variant: TypeVariant::Opaque(TypeLayout {
            size_bits: 0,
            field_alignment_bits: 8,
            pointer_alignment_bits: 8,
            required_alignment_bits: 24,
        }),
    };
    let err = compute_layout(Target::X86_64UnknownLinuxGnu, &ty).unwrap_err();
    assert!(matches!(err.kind(), ErrorType::PowerOfTwoAlignment));
}

#[test]
fn sub_byte_alignment_1() {
    let ty = Type::<()> {
        layout: (),
        annotations: vec![Annotation::Align(Some(4))],
        variant: TypeVariant::Record(Record {
            kind: RecordKind::Struct,
            fields: vec![],
        }),
    };
    let err = compute_layout(Target::X86_64UnknownLinuxGnu, &ty).unwrap_err();
    assert!(matches!(err.kind(), ErrorType::SubByteAlignment));
}

#[test]
fn sub_byte_alignment_2() {
    let ty = Type::<()> {
        layout: (),
        annotations: vec![],
        variant: TypeVariant::Opaque(TypeLayout {
            size_bits: 0,
            field_alignment_bits: 8,
            pointer_alignment_bits: 4,
            required_alignment_bits: 8,
        }),
    };
    let err = compute_layout(Target::X86_64UnknownLinuxGnu, &ty).unwrap_err();
    assert!(matches!(err.kind(), ErrorType::SubByteAlignment));
}

#[test]
fn sub_byte_alignment_3() {
    let ty = Type::<()> {
        layout: (),
        annotations: vec![],
        variant: TypeVariant::Opaque(TypeLayout {
            size_bits: 0,
            field_alignment_bits: 4,
            pointer_alignment_bits: 8,
            required_alignment_bits: 8,
        }),
    };
    let err = compute_layout(Target::X86_64UnknownLinuxGnu, &ty).unwrap_err();
    assert!(matches!(err.kind(), ErrorType::SubByteAlignment));
}

#[test]
fn sub_byte_alignment_4() {
    let ty = Type::<()> {
        layout: (),
        annotations: vec![],
        variant: TypeVariant::Opaque(TypeLayout {
            size_bits: 0,
            field_alignment_bits: 8,
            pointer_alignment_bits: 8,
            required_alignment_bits: 4,
        }),
    };
    let err = compute_layout(Target::X86_64UnknownLinuxGnu, &ty).unwrap_err();
    assert!(matches!(err.kind(), ErrorType::SubByteAlignment));
}

#[test]
fn sub_byte_size() {
    let ty = Type::<()> {
        layout: (),
        annotations: vec![],
        variant: TypeVariant::Opaque(TypeLayout {
            size_bits: 4,
            field_alignment_bits: 8,
            pointer_alignment_bits: 8,
            required_alignment_bits: 8,
        }),
    };
    let err = compute_layout(Target::X86_64UnknownLinuxGnu, &ty).unwrap_err();
    assert!(matches!(err.kind(), ErrorType::SubByteSize));
}

#[test]
fn multiple_pragma_pack() {
    let ty = Type::<()> {
        layout: (),
        annotations: vec![Annotation::PragmaPack(8), Annotation::PragmaPack(8)],
        variant: TypeVariant::Record(Record {
            kind: RecordKind::Struct,
            fields: vec![],
        }),
    };
    let err = compute_layout(Target::X86_64UnknownLinuxGnu, &ty).unwrap_err();
    assert!(matches!(
        err.kind(),
        ErrorType::MultiplePragmaPackedAnnotations
    ));
}

#[test]
fn named_zero_sized_bit_field() {
    let ty = Type::<()> {
        layout: (),
        annotations: vec![],
        variant: TypeVariant::Record(Record {
            kind: RecordKind::Struct,
            fields: vec![RecordField {
                layout: None,
                annotations: vec![],
                named: true,
                bit_width: Some(0),
                ty: Type {
                    layout: (),
                    annotations: vec![],
                    variant: TypeVariant::Builtin(BuiltinType::Int),
                },
            }],
        }),
    };
    let err = compute_layout(Target::X86_64UnknownLinuxGnu, &ty).unwrap_err();
    assert!(matches!(err.kind(), ErrorType::NamedZeroSizeBitField));
}

#[test]
fn unnamed_regular_field() {
    let ty = Type::<()> {
        layout: (),
        annotations: vec![],
        variant: TypeVariant::Record(Record {
            kind: RecordKind::Struct,
            fields: vec![RecordField {
                layout: None,
                annotations: vec![],
                named: false,
                bit_width: None,
                ty: Type {
                    layout: (),
                    annotations: vec![],
                    variant: TypeVariant::Builtin(BuiltinType::Int),
                },
            }],
        }),
    };
    let err = compute_layout(Target::X86_64UnknownLinuxGnu, &ty).unwrap_err();
    assert!(matches!(err.kind(), ErrorType::UnnamedRegularField));
}

#[test]
fn oversized_bitfield() {
    let ty = Type::<()> {
        layout: (),
        annotations: vec![],
        variant: TypeVariant::Record(Record {
            kind: RecordKind::Struct,
            fields: vec![RecordField {
                layout: None,
                annotations: vec![],
                named: true,
                bit_width: Some(64),
                ty: Type {
                    layout: (),
                    annotations: vec![],
                    variant: TypeVariant::Builtin(BuiltinType::Int),
                },
            }],
        }),
    };
    let err = compute_layout(Target::X86_64UnknownLinuxGnu, &ty).unwrap_err();
    assert!(matches!(err.kind(), ErrorType::OversizedBitfield));
}

#[test]
fn pragma_packed_field() {
    let ty = Type::<()> {
        layout: (),
        annotations: vec![],
        variant: TypeVariant::Record(Record {
            kind: RecordKind::Struct,
            fields: vec![RecordField {
                layout: None,
                annotations: vec![Annotation::PragmaPack(8)],
                named: true,
                bit_width: None,
                ty: Type {
                    layout: (),
                    annotations: vec![],
                    variant: TypeVariant::Builtin(BuiltinType::Int),
                },
            }],
        }),
    };
    let err = compute_layout(Target::X86_64UnknownLinuxGnu, &ty).unwrap_err();
    assert!(matches!(err.kind(), ErrorType::PragmaPackedField));
}
