mod bindings;
mod probe;
mod recovered_tables;
mod scan;

pub mod types;

pub use scan::{inspect_building_types_dir, inspect_building_types_dir_with_bindings};
pub use types::*;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn probes_bca_selector_bytes_from_fixed_offsets() {
        let mut bytes = vec![0u8; 0xbc + 1];
        bytes[0xb8] = 0x12;
        bytes[0xb9] = 0x34;
        bytes[0xba] = 0x56;
        bytes[0xbb] = 0x78;
        let probe = probe::probe_bca_selector_bytes(&bytes);
        assert_eq!(probe.byte_0xb8, 0x12);
        assert_eq!(probe.byte_0xb9, 0x34);
        assert_eq!(probe.byte_0xba, 0x56);
        assert_eq!(probe.byte_0xbb, 0x78);
        assert_eq!(probe.byte_0xb8_hex, "0x12");
        assert_eq!(probe.byte_0xbb_hex, "0x78");
    }

    #[test]
    fn probes_bty_header_from_fixed_offsets() {
        let mut bytes = vec![0u8; 0xc0];
        bytes[0x00..0x04].copy_from_slice(&0x03ebu32.to_le_bytes());
        bytes[0x04..0x04 + 5].copy_from_slice(b"Port\0");
        bytes[0x22..0x22 + 7].copy_from_slice(b"Cargo\0\0");
        bytes[0x40..0x40 + 6].copy_from_slice(b"Dock\0\0");
        bytes[0x5e..0x5e + 5].copy_from_slice(b"Sea\0\0");
        bytes[0x7c..0x7c + 6].copy_from_slice(b"Coast\0");
        bytes[0x9a..0x9a + 5].copy_from_slice(b"Port\0");
        bytes[0xb8] = 0x12;
        bytes[0xb9] = 0x34;
        bytes[0xba] = 0x56;
        bytes[0xbb..0xbf].copy_from_slice(&0x89abcdefu32.to_le_bytes());

        let probe = probe::probe_bty_header(&bytes);
        assert_eq!(probe.type_id, 0x03eb);
        assert_eq!(probe.type_id_hex, "0x000003eb");
        assert_eq!(probe.name_0x04, "Port");
        assert_eq!(probe.name_0x22, "Cargo");
        assert_eq!(probe.name_0x40, "Dock");
        assert_eq!(probe.name_0x5e, "Sea");
        assert_eq!(probe.name_0x7c, "Coast");
        assert_eq!(probe.name_0x9a, "Port");
        assert_eq!(probe.byte_0xb8_hex, "0x12");
        assert_eq!(probe.byte_0xb9_hex, "0x34");
        assert_eq!(probe.byte_0xba_hex, "0x56");
        assert_eq!(probe.dword_0xbb_hex, "0x89abcdef");
    }

    #[test]
    fn summarizes_recovered_table_families_from_entries_and_files() {
        let entries = vec![
            BuildingTypeSourceEntry {
                canonical_stem: scan::canonicalize_building_stem("VictorianStationSml"),
                raw_stems: vec!["VictorianStationSml".to_string()],
                source_kinds: vec![BuildingTypeSourceKind::Bty],
                file_names: vec!["VictorianStationSml.bty".to_string()],
            },
            BuildingTypeSourceEntry {
                canonical_stem: scan::canonicalize_building_stem("ClpBrdStationLrg"),
                raw_stems: vec!["ClpbrdStationLrg".to_string()],
                source_kinds: vec![BuildingTypeSourceKind::Bty],
                file_names: vec!["ClpbrdStationLrg.bty".to_string()],
            },
            BuildingTypeSourceEntry {
                canonical_stem: scan::canonicalize_building_stem("Maintenance"),
                raw_stems: vec!["Maintenance".to_string()],
                source_kinds: vec![BuildingTypeSourceKind::Bty],
                file_names: vec!["Maintenance.bty".to_string()],
            },
            BuildingTypeSourceEntry {
                canonical_stem: scan::canonicalize_building_stem("ServiceTower"),
                raw_stems: vec!["ServiceTower".to_string()],
                source_kinds: vec![BuildingTypeSourceKind::Bty],
                file_names: vec!["ServiceTower.bty".to_string()],
            },
            BuildingTypeSourceEntry {
                canonical_stem: scan::canonicalize_building_stem("Port"),
                raw_stems: vec!["Port".to_string()],
                source_kinds: vec![BuildingTypeSourceKind::Bca, BuildingTypeSourceKind::Bty],
                file_names: vec!["Port.bca".to_string(), "Port.bty".to_string()],
            },
        ];
        let files = vec![
            BuildingTypeSourceFile {
                file_name: "VictorianStationSml.bty".to_string(),
                raw_stem: "VictorianStationSml".to_string(),
                canonical_stem: scan::canonicalize_building_stem("VictorianStationSml"),
                source_kind: BuildingTypeSourceKind::Bty,
                byte_len: None,
                bca_selector_probe: None,
                bty_header_probe: Some(BuildingTypeBtyHeaderProbe {
                    type_id: 0x03ec,
                    type_id_hex: "0x000003ec".to_string(),
                    name_0x04: "VictorianStationSml".to_string(),
                    name_0x22: "VictorianStationSml".to_string(),
                    name_0x40: "VictorianStations".to_string(),
                    name_0x5e: "SmallTudorHouse".to_string(),
                    name_0x7c: "VictorianStations".to_string(),
                    name_0x9a: "VictorianStationSml".to_string(),
                    byte_0xb8: 0x06,
                    byte_0xb8_hex: "0x06".to_string(),
                    byte_0xb9: 0x06,
                    byte_0xb9_hex: "0x06".to_string(),
                    byte_0xba: 0x30,
                    byte_0xba_hex: "0x30".to_string(),
                    dword_0xbb: 0,
                    dword_0xbb_hex: "0x00000000".to_string(),
                }),
            },
            BuildingTypeSourceFile {
                file_name: "ClpbrdStationLrg.bty".to_string(),
                raw_stem: "ClpbrdStationLrg".to_string(),
                canonical_stem: scan::canonicalize_building_stem("ClpbrdStationLrg"),
                source_kind: BuildingTypeSourceKind::Bty,
                byte_len: None,
                bca_selector_probe: None,
                bty_header_probe: Some(BuildingTypeBtyHeaderProbe {
                    type_id: 0x03ec,
                    type_id_hex: "0x000003ec".to_string(),
                    name_0x04: "ClpbrdStationLrg".to_string(),
                    name_0x22: "ClpbrdStationLrg".to_string(),
                    name_0x40: "ClpBrdStations".to_string(),
                    name_0x5e: "SmallTudorHouse".to_string(),
                    name_0x7c: "ClpBrdStations".to_string(),
                    name_0x9a: "ClpbrdStationLrg".to_string(),
                    byte_0xb8: 0x06,
                    byte_0xb8_hex: "0x06".to_string(),
                    byte_0xb9: 0x06,
                    byte_0xb9_hex: "0x06".to_string(),
                    byte_0xba: 0x30,
                    byte_0xba_hex: "0x30".to_string(),
                    dword_0xbb: 0,
                    dword_0xbb_hex: "0x00000000".to_string(),
                }),
            },
            BuildingTypeSourceFile {
                file_name: "Maintenance.bty".to_string(),
                raw_stem: "Maintenance".to_string(),
                canonical_stem: scan::canonicalize_building_stem("Maintenance"),
                source_kind: BuildingTypeSourceKind::Bty,
                byte_len: None,
                bca_selector_probe: None,
                bty_header_probe: Some(BuildingTypeBtyHeaderProbe {
                    type_id: 0x03ec,
                    type_id_hex: "0x000003ec".to_string(),
                    name_0x04: "Maintenance".to_string(),
                    name_0x22: "Maintenance".to_string(),
                    name_0x40: "Maintenance Facility".to_string(),
                    name_0x5e: "200FtRulerCross".to_string(),
                    name_0x7c: "Maintenance Facility".to_string(),
                    name_0x9a: "Maintenance".to_string(),
                    byte_0xb8: 0x06,
                    byte_0xb8_hex: "0x06".to_string(),
                    byte_0xb9: 0x06,
                    byte_0xb9_hex: "0x06".to_string(),
                    byte_0xba: 0x30,
                    byte_0xba_hex: "0x30".to_string(),
                    dword_0xbb: 0,
                    dword_0xbb_hex: "0x00000000".to_string(),
                }),
            },
            BuildingTypeSourceFile {
                file_name: "ServiceTower.bty".to_string(),
                raw_stem: "ServiceTower".to_string(),
                canonical_stem: scan::canonicalize_building_stem("ServiceTower"),
                source_kind: BuildingTypeSourceKind::Bty,
                byte_len: None,
                bca_selector_probe: None,
                bty_header_probe: Some(BuildingTypeBtyHeaderProbe {
                    type_id: 0x03ec,
                    type_id_hex: "0x000003ec".to_string(),
                    name_0x04: "ServiceTower".to_string(),
                    name_0x22: "ServiceTower".to_string(),
                    name_0x40: "Service Tower".to_string(),
                    name_0x5e: "200FtRulerCross".to_string(),
                    name_0x7c: "Service Tower".to_string(),
                    name_0x9a: "ServiceTower".to_string(),
                    byte_0xb8: 0x06,
                    byte_0xb8_hex: "0x06".to_string(),
                    byte_0xb9: 0x06,
                    byte_0xb9_hex: "0x06".to_string(),
                    byte_0xba: 0x30,
                    byte_0xba_hex: "0x30".to_string(),
                    dword_0xbb: 0,
                    dword_0xbb_hex: "0x00000000".to_string(),
                }),
            },
            BuildingTypeSourceFile {
                file_name: "Port.bty".to_string(),
                raw_stem: "Port".to_string(),
                canonical_stem: scan::canonicalize_building_stem("Port"),
                source_kind: BuildingTypeSourceKind::Bty,
                byte_len: None,
                bca_selector_probe: None,
                bty_header_probe: Some(BuildingTypeBtyHeaderProbe {
                    type_id: 0x03ec,
                    type_id_hex: "0x000003ec".to_string(),
                    name_0x04: "Port".to_string(),
                    name_0x22: "Port".to_string(),
                    name_0x40: "Port".to_string(),
                    name_0x5e: "TextileMill".to_string(),
                    name_0x7c: "Port".to_string(),
                    name_0x9a: "Port".to_string(),
                    byte_0xb8: 0x06,
                    byte_0xb8_hex: "0x06".to_string(),
                    byte_0xb9: 0x06,
                    byte_0xb9_hex: "0x06".to_string(),
                    byte_0xba: 0x30,
                    byte_0xba_hex: "0x30".to_string(),
                    dword_0xbb: 0x01f4,
                    dword_0xbb_hex: "0x000001f4".to_string(),
                }),
            },
            BuildingTypeSourceFile {
                file_name: "Port.bca".to_string(),
                raw_stem: "Port".to_string(),
                canonical_stem: scan::canonicalize_building_stem("Port"),
                source_kind: BuildingTypeSourceKind::Bca,
                byte_len: None,
                bca_selector_probe: Some(BuildingTypeBcaSelectorProbe {
                    byte_0xb8: 0x00,
                    byte_0xb8_hex: "0x00".to_string(),
                    byte_0xb9: 0x00,
                    byte_0xb9_hex: "0x00".to_string(),
                    byte_0xba: 0x00,
                    byte_0xba_hex: "0x00".to_string(),
                    byte_0xbb: 0x00,
                    byte_0xbb_hex: "0x00".to_string(),
                }),
                bty_header_probe: None,
            },
        ];

        let summary = recovered_tables::summarize_recovered_table_families(&entries, &files);
        assert!(
            summary
                .present_style_station_entries
                .contains(&"VictorianStationSml".to_string())
        );
        assert!(
            summary
                .present_style_station_entries
                .contains(&"ClpbrdStationLrg".to_string())
        );
        assert_eq!(
            summary.present_standalone_entries,
            vec!["Maintenance".to_string(), "ServiceTower".to_string()]
        );
        assert_eq!(summary.recovered_source_family_summaries.len(), 4);
        assert!(summary.recovered_source_family_summaries.iter().any(|row| {
            row.canonical_stem == scan::canonicalize_building_stem("Maintenance")
                && row.name_0x40 == "Maintenance Facility"
                && row.dword_0xbb_hex == "0x00000000"
                && row.byte_0xba_hex.is_none()
        }));
        assert!(summary.recovered_source_family_summaries.iter().any(|row| {
            row.canonical_stem == scan::canonicalize_building_stem("VictorianStationSml")
                && row.name_0x40 == "VictorianStations"
                && row.dword_0xbb_hex == "0x00000000"
        }));
        assert_eq!(
            summary.bare_port_warehouse_files,
            vec!["Port.bca".to_string(), "Port.bty".to_string()]
        );
        assert_eq!(summary.nonzero_bty_header_dword_summaries.len(), 1);
        assert_eq!(
            summary.nonzero_bty_header_dword_summaries[0].dword_0xbb_hex,
            "0x000001f4"
        );
        assert_eq!(
            summary.nonzero_bty_header_dword_summaries[0].sample_file_names,
            vec!["Port.bty".to_string()]
        );
        assert_eq!(
            summary.nonzero_bty_header_name_0x40_summaries,
            vec![BuildingTypeBtyHeaderNameSummary {
                header_offset_hex: "0x40".to_string(),
                header_value: "Port".to_string(),
                file_count: 1,
                sample_file_names: vec!["Port.bty".to_string()],
            }]
        );
        assert_eq!(
            summary.nonzero_bty_header_name_0x5e_summaries,
            vec![BuildingTypeBtyHeaderNameSummary {
                header_offset_hex: "0x5e".to_string(),
                header_value: "TextileMill".to_string(),
                file_count: 1,
                sample_file_names: vec!["Port.bty".to_string()],
            }]
        );
        assert_eq!(
            summary.nonzero_bty_header_name_0x7c_summaries,
            vec![BuildingTypeBtyHeaderNameSummary {
                header_offset_hex: "0x7c".to_string(),
                header_value: "Port".to_string(),
                file_count: 1,
                sample_file_names: vec!["Port.bty".to_string()],
            }]
        );
        assert!(
            summary
                .bty_header_name_0x5e_dword_summaries
                .iter()
                .any(|row| {
                    row.header_offset_hex == "0x5e"
                        && row.header_value == "TextileMill"
                        && row.dword_0xbb == 0x01f4
                        && row.file_count == 1
                        && row.sample_file_names == vec!["Port.bty".to_string()]
                })
        );
        assert_eq!(
            summary.nonzero_bty_header_alias_selector_summaries,
            vec![BuildingTypeBtyHeaderAliasSelectorSummary {
                name_0x40: "Port".to_string(),
                name_0x5e: "TextileMill".to_string(),
                name_0x7c: "Port".to_string(),
                dword_0xbb: 0x01f4,
                dword_0xbb_hex: "0x000001f4".to_string(),
                byte_0xb8_hex: "0x00".to_string(),
                byte_0xb9_hex: "0x00".to_string(),
                byte_0xba_hex: "0x00".to_string(),
                byte_0xbb_hex: "0x00".to_string(),
                file_count: 1,
                sample_file_names: vec!["Port.bty".to_string()],
            }]
        );
        assert_eq!(
            summary.bty_name_0x5e_bca_selector_summaries,
            vec![BuildingTypeBtyNameBcaSelectorSummary {
                header_offset_hex: "0x5e".to_string(),
                header_value: "TextileMill".to_string(),
                dword_0xbb: 0x01f4,
                dword_0xbb_hex: "0x000001f4".to_string(),
                byte_0xba_hex: "0x00".to_string(),
                byte_0xbb_hex: "0x00".to_string(),
                file_count: 1,
                sample_file_names: vec!["Port.bty".to_string()],
            }]
        );
    }
}
