use std::collections::HashMap;

use crate::signal::SignalValue;
use crate::trace::{SignalId, Trace};

type ComputeFn = Box<dyn Fn(usize) -> SignalValue + Send + Sync>;

/// A virtual trace with no backing file. Signals are computed on demand.
pub struct VirtualTrace {
    signal_names: Vec<String>,
    scopes: Vec<String>,
    timestamps: Vec<u64>,
    computed: HashMap<String, ComputeFn>,
    widths: HashMap<String, u32>,
}

impl VirtualTrace {
    pub fn new(max_index: usize, time_step: u64) -> Self {
        let timestamps: Vec<u64> = (0..=max_index).map(|i| i as u64 * time_step).collect();
        Self {
            signal_names: Vec::new(),
            scopes: Vec::new(),
            timestamps,
            computed: HashMap::new(),
            widths: HashMap::new(),
        }
    }

    pub fn add_scope(&mut self, scope: String) {
        if !self.scopes.contains(&scope) {
            self.scopes.push(scope);
        }
    }

    /// Define a computed signal with a closure.
    pub fn define_signal<F>(&mut self, name: String, width: u32, f: F)
    where
        F: Fn(usize) -> SignalValue + Send + Sync + 'static,
    {
        if !self.signal_names.contains(&name) {
            self.signal_names.push(name.clone());
        }
        self.widths.insert(name.clone(), width);
        self.computed.insert(name, Box::new(f));
    }
}

impl Trace for VirtualTrace {
    fn signal_names(&self) -> &[String] {
        &self.signal_names
    }

    fn scopes(&self) -> &[String] {
        &self.scopes
    }

    fn max_index(&self) -> usize {
        if self.timestamps.is_empty() {
            0
        } else {
            self.timestamps.len() - 1
        }
    }

    fn timestamp(&self, index: usize) -> u64 {
        self.timestamps.get(index).copied().unwrap_or(0)
    }

    fn signal_value(&self, name: &str, index: usize) -> SignalValue {
        if let Some(f) = self.computed.get(name) {
            f(index)
        } else {
            SignalValue::Unknown
        }
    }

    fn signal_width(&self, name: &str) -> u32 {
        self.widths.get(name).copied().unwrap_or(0)
    }

    fn signal_id(&self, name: &str) -> Option<SignalId> {
        self.signal_names.iter().position(|n| n == name)
    }

    fn signal_values_range(&self, name: &str, start: usize, end: usize) -> Vec<SignalValue> {
        (start..end).map(|i| self.signal_value(name, i)).collect()
    }

    fn signal_value_by_id(&self, id: SignalId, index: usize) -> SignalValue {
        if let Some(name) = self.signal_names.get(id) {
            self.signal_value(name, index)
        } else {
            SignalValue::Unknown
        }
    }
}
