use mavinspect::parser::errors::XmlInspectionError;
use mavinspect::parser::XMLInspector;

fn default_dialect_paths() -> Vec<&'static str> {
    vec![
        "./message_definitions/standard",
        "./message_definitions/extra",
    ]
}

fn extra_dialect_paths() -> Vec<&'static str> {
    vec!["./message_definitions/extra"]
}

#[test]
fn naming_collisions_are_avoided() {
    use mavinspect::errors::Error;

    let inspector = XMLInspector::builder()
        .set_sources(&["./message_definitions/colliding"])
        .build();

    assert!(
        inspector.is_err(),
        "XMLInspector should recognize naming collisions"
    );
    assert!(matches!(
        inspector,
        Err(Error::Inspection(
            XmlInspectionError::NamingCollision { .. }
        ))
    ))
}

#[test]
fn wrong_paths_do_not_cause_panic() {
    use mavinspect::errors::Error;

    let paths = vec![
        "./message_definitions/invalid", // Non-existing
        "./message_definitions/standard",
    ];
    let inspector = XMLInspector::builder().set_sources(&paths).build();
    assert!(
        inspector.is_err(),
        "XMLInspector should return error for non-existing paths"
    );
    assert!(matches!(inspector, Err(Error::Io(_))))
}

#[test]
fn empty_paths_do_not_cause_errors() {
    let inspector = XMLInspector::builder()
        .set_sources(&[
            "./tests", // Clearly don't have any definitions
            "./message_definitions/standard",
        ])
        .build();

    assert!(
        inspector.is_ok(),
        "XMLInspector should non return error for paths without definitions"
    );
}

#[test]
fn xml_definitions_are_loaded() {
    let inspector = XMLInspector::builder()
        .set_sources(&default_dialect_paths())
        .build();
    assert!(inspector.is_ok(), "failed to instantiate XMLInspector");

    let inspector = inspector.unwrap();
    assert!(!inspector.definitions().is_empty(), "no definitions loaded");
}

#[test]
fn default_message_definitions_are_parsed() {
    let inspector = XMLInspector::builder()
        .set_sources(&default_dialect_paths())
        .build()
        .unwrap();
    let protocol = inspector.parse();

    assert!(
        protocol.is_ok(),
        "failed to instantiate XMLInspector: {:?}",
        protocol
    );
    let protocol = protocol.unwrap();

    // Check that some dialects are parsed
    assert!(!protocol.dialects().is_empty(), "no dialects parsed");
    // Check that minimal dialect is parsed
    assert!(
        protocol.dialects().get("minimal").is_some(),
        "`minimal` dialect should be parsed"
    );
}

#[test]
fn default_minimal_dialect_is_parsed_correctly() {
    let inspector = XMLInspector::builder()
        .set_sources(&default_dialect_paths())
        .build()
        .unwrap();
    let protocol = inspector.parse().unwrap();

    let minimal = protocol.dialects().get("minimal").unwrap();

    assert_eq!(minimal.name(), "minimal", "incorrect dialect name");

    assert!(
        minimal.enums().get("MAV_AUTOPILOT").is_some(),
        "`minimal` dialect should contain `MAV_AUTOPILOT` enum"
    );

    assert!(
        minimal.messages().get(&0u32).is_some(),
        "`minimal` dialect should contain `HEARTBEAT` message"
    );
    assert_eq!(
        minimal.messages().get(&0u32).unwrap().name(),
        "HEARTBEAT",
        "`minimal` dialect should contain `HEARTBEAT` message"
    );

    assert!(
        minimal.messages().get(&300u32).is_some(),
        "`minimal` dialect should contain `PROTOCOL_VERSION` message"
    );
    assert_eq!(
        minimal.messages().get(&300u32).unwrap().name(),
        "PROTOCOL_VERSION",
        "`minimal` dialect should contain `PROTOCOL_VERSION` message"
    );
}

#[test]
fn custom_dialect() {
    let inspector = XMLInspector::builder()
        .set_sources(&extra_dialect_paths())
        .set_include(&["CrazyFlight"])
        .build()
        .unwrap();
    let protocol = inspector.parse().unwrap();
    let dialect = protocol.dialects().get("crazy_flight").unwrap();

    assert!(dialect.version().is_some());
    assert!(matches!(dialect.version(), Some(3)));

    assert!(dialect.enums().contains_key("CRAZYFLIGHT_INSANITY_LEVEL"));
    assert!(dialect.enums().contains_key("CRAZYFLIGHT_DEED_FLAGS"));

    let mav_enum = dialect.enums().get("CRAZYFLIGHT_INSANITY_LEVEL").unwrap();
    assert_eq!(mav_enum.entries().len(), 3);

    let mav_bitmask = dialect.enums().get("CRAZYFLIGHT_DEED_FLAGS").unwrap();
    assert_eq!(mav_bitmask.entries().len(), 3);
    assert!(mav_bitmask.bitmask());
}

#[test]
fn enum_entry_prefix_stripping() {
    let inspector = XMLInspector::builder()
        .set_sources(&extra_dialect_paths())
        .set_include(&["CrazyFlight"])
        .build()
        .unwrap();
    let protocol = inspector.parse().unwrap();
    let dialect = protocol.dialects().get("crazy_flight").unwrap();

    let mav_enum = dialect.enums().get("CRAZYFLIGHT_INSANITY_LEVEL").unwrap();
    for entry in mav_enum.entries().values() {
        assert_eq!(
            format!("{}_{}", mav_enum.name(), entry.name_stripped()),
            entry.name()
        );
    }
}
