//! RTL emitter for the MAPE-K autonomic feedback loop.
//!
//! Generates six synthesis-clean SystemVerilog modules:
//!
//! | Module              | Role                                       |
//! |---------------------|--------------------------------------------|
//! | `mirr_monitor`      | Shadow registers, trace buffer, thresholds |
//! | `mirr_analyze`      | Per-property LTL checkers, priority encode |
//! | `mirr_plan`         | Action lookup, priority selection          |
//! | `mirr_execute`      | Action dispatch, emergency latch           |
//! | `mirr_knowledge`    | FIFO ring buffer for adaptation records    |
//! | `mirr_mape_k_top`   | Top-level wiring of all five blocks        |
//!
//! All generated RTL uses `always_ff` / `always_comb` (no `$display`),
//! so the output is synthesis-clean for FPGA / ASIC flows.

#![forbid(unsafe_code)]

mod lower;
mod upper;

use crate::mape_k::bridge::bridge_from_pipeline;
use crate::pipeline::PipelineResult;

// ---------------------------------------------------------------------------
// Constants — bounded resource limits (NASA P10)
// ---------------------------------------------------------------------------

/// Maximum sensor signals in the generated RTL.
pub const MAX_RTL_SIGNALS: usize = 64;

/// Maximum temporal properties in the generated RTL.
pub const MAX_RTL_PROPERTIES: usize = 32;

/// Maximum action-table entries in the generated RTL.
pub const MAX_RTL_ACTIONS: usize = 32;

/// Maximum knowledge FIFO depth in the generated RTL.
pub const MAX_RTL_KNOWLEDGE_DEPTH: usize = 256;

// ---------------------------------------------------------------------------
// Public entry point
// ---------------------------------------------------------------------------

/// Emit the complete MAPE-K RTL (six SystemVerilog modules) from a
/// `PipelineResult`.
///
/// 1. Bridges the pipeline AST into a `SimConfig`.
/// 2. Validates that the config fits within RTL resource limits.
/// 3. Concatenates all six module outputs into a single SV string.
pub fn emit_mape_k_rtl(result: &PipelineResult) -> Result<String, String> {
    let config = bridge_from_pipeline(result).map_err(|errs| {
        let mut msg = String::from("MAPE-K bridge errors:\n");
        for (i, e) in errs.iter().enumerate().take(MAX_RTL_PROPERTIES) {
            msg.push_str(&format!("  [{i}] {e}\n"));
        }
        msg
    })?;

    if config.sensors.len() > MAX_RTL_SIGNALS {
        return Err(format!(
            "too many signals for RTL: {} > {MAX_RTL_SIGNALS}",
            config.sensors.len()
        ));
    }
    if config.properties.len() > MAX_RTL_PROPERTIES {
        return Err(format!(
            "too many properties for RTL: {} > {MAX_RTL_PROPERTIES}",
            config.properties.len()
        ));
    }
    if config.action_table.len() > MAX_RTL_ACTIONS {
        return Err(format!(
            "too many actions for RTL: {} > {MAX_RTL_ACTIONS}",
            config.action_table.len()
        ));
    }

    let mut out = String::with_capacity(8192);
    out.push_str("// =============================================================\n");
    out.push_str("// MAPE-K RTL — auto-generated by MIRR compiler\n");
    out.push_str("// Do not edit — regenerate from .mirr source.\n");
    out.push_str("// =============================================================\n\n");

    out.push_str(&lower::emit_monitor_block(&config));
    out.push_str(&lower::emit_analyze_block(&config));
    out.push_str(&upper::emit_plan_block(&config));
    out.push_str(&upper::emit_execute_block(&config));
    out.push_str(&upper::emit_knowledge_block(&config));
    out.push_str(&upper::emit_mape_k_top(&config));

    Ok(out)
}

// ---------------------------------------------------------------------------
// Unit tests
// ---------------------------------------------------------------------------

#[cfg(test)]
mod tests {
    use super::lower::{bit_width, emit_analyze_block, emit_monitor_block, predicate_to_sv};
    use super::upper::{
        emit_execute_block, emit_knowledge_block, emit_mape_k_top, emit_plan_block,
    };
    use crate::mape_k::ltl::{SignalPredicate, TemporalProperty};
    use crate::mape_k::planner::{ActionEntry, AdaptationAction, TriggerCondition};
    use crate::mape_k::sensor::SensorConfig;
    use crate::mape_k::SimConfig;

    fn stub_config() -> SimConfig {
        SimConfig {
            sensors: vec![
                SensorConfig {
                    name: "pressure".to_string(),
                    base_value: 50,
                    noise_amplitude: 2,
                    fault_at_tick: None,
                    fault_value: 0,
                    fault_end_tick: None,
                    seed: 1,
                    is_observable: true,
                },
                SensorConfig {
                    name: "temp".to_string(),
                    base_value: 37,
                    noise_amplitude: 1,
                    fault_at_tick: None,
                    fault_value: 0,
                    fault_end_tick: None,
                    seed: 2,
                    is_observable: true,
                },
            ],
            properties: vec![
                TemporalProperty::Always(SignalPredicate::LessThan("pressure".to_string(), 100)),
                TemporalProperty::EventuallyWithin(SignalPredicate::IsTrue("temp".to_string()), 8),
            ],
            action_table: vec![
                ActionEntry {
                    trigger_property_idx: 0,
                    action: AdaptationAction::EmergencyStop,
                    priority: 255,
                    trigger_on: TriggerCondition::OnViolation,
                },
                ActionEntry {
                    trigger_property_idx: 1,
                    action: AdaptationAction::SetSignal { name: "temp".to_string(), value: 37 },
                    priority: 100,
                    trigger_on: TriggerCondition::OnViolation,
                },
            ],
            window_size: 64,
            knowledge_capacity: 128,
        }
    }

    #[test]
    fn bit_width_values() {
        assert_eq!(bit_width(0), 1);
        assert_eq!(bit_width(1), 1);
        assert_eq!(bit_width(2), 1);
        assert_eq!(bit_width(3), 2);
        assert_eq!(bit_width(4), 2);
        assert_eq!(bit_width(5), 3);
        assert_eq!(bit_width(64), 6);
        assert_eq!(bit_width(256), 8);
    }

    #[test]
    fn predicate_to_sv_is_true() {
        let p = SignalPredicate::IsTrue("x".to_string());
        assert_eq!(predicate_to_sv(&p, "s[0]"), "(s[0] != 32'd0)");
    }

    #[test]
    fn predicate_to_sv_less_than() {
        let p = SignalPredicate::LessThan("x".to_string(), 42);
        assert_eq!(predicate_to_sv(&p, "sig"), "(sig < 32'd42)");
    }

    #[test]
    fn predicate_to_sv_greater_than() {
        let p = SignalPredicate::GreaterThan("x".to_string(), 10);
        assert_eq!(predicate_to_sv(&p, "sig"), "(sig > 32'd10)");
    }

    #[test]
    fn predicate_to_sv_in_range() {
        let p = SignalPredicate::InRange("x".to_string(), 5, 15);
        let sv = predicate_to_sv(&p, "d");
        assert!(sv.contains("d >= 32'd5"));
        assert!(sv.contains("d <= 32'd15"));
    }

    #[test]
    fn monitor_block_contains_shadow_regs() {
        let config = stub_config();
        let sv = emit_monitor_block(&config);
        assert!(sv.contains("module mirr_monitor"));
        assert!(sv.contains("shadow"));
        assert!(sv.contains("trace_buf"));
        assert!(sv.contains("always_ff"));
        assert!(sv.contains("rst_n"));
    }

    #[test]
    fn analyze_block_contains_property_checkers() {
        let config = stub_config();
        let sv = emit_analyze_block(&config);
        assert!(sv.contains("module mirr_analyze"));
        assert!(sv.contains("violation_vec"));
        assert!(sv.contains("Property 0: Always"));
        assert!(sv.contains("Property 1: EventuallyWithin"));
        assert!(sv.contains("ev_cnt_1"));
    }

    #[test]
    fn plan_block_contains_action_selection() {
        let config = stub_config();
        let sv = emit_plan_block(&config);
        assert!(sv.contains("module mirr_plan"));
        assert!(sv.contains("best_priority"));
        assert!(sv.contains("selected_action_idx"));
        assert!(sv.contains("action_valid"));
    }

    #[test]
    fn execute_block_contains_emergency_latch() {
        let config = stub_config();
        let sv = emit_execute_block(&config);
        assert!(sv.contains("module mirr_execute"));
        assert!(sv.contains("emergency_active"));
        assert!(sv.contains("override_en"));
    }

    #[test]
    fn knowledge_block_contains_fifo() {
        let config = stub_config();
        let sv = emit_knowledge_block(&config);
        assert!(sv.contains("module mirr_knowledge"));
        assert!(sv.contains("fifo"));
        assert!(sv.contains("wr_ptr"));
        assert!(sv.contains("full"));
    }

    #[test]
    fn top_block_wires_all_submodules() {
        let config = stub_config();
        let sv = emit_mape_k_top(&config);
        assert!(sv.contains("module mirr_mape_k_top"));
        assert!(sv.contains("u_monitor"));
        assert!(sv.contains("u_analyze"));
        assert!(sv.contains("u_plan"));
        assert!(sv.contains("u_execute"));
        assert!(sv.contains("u_knowledge"));
        assert!(sv.contains("tick_counter"));
    }

    #[test]
    fn no_display_in_output() {
        let config = stub_config();
        let mon = emit_monitor_block(&config);
        let ana = emit_analyze_block(&config);
        let pln = emit_plan_block(&config);
        let exe = emit_execute_block(&config);
        let knw = emit_knowledge_block(&config);
        let top = emit_mape_k_top(&config);
        for block in [&mon, &ana, &pln, &exe, &knw, &top] {
            assert!(!block.contains("$display"), "synthesis-clean: no $display");
        }
    }
}
