use std::ops::{Index, IndexMut};

use ahash::AHashMap;
use anyhow::{bail, Result};
use camino::Utf8PathBuf;
use indexmap::IndexMap;
use log::warn;
use stdx::{impl_debug_display, impl_idx_from};
use typed_index_collections::TiVec;
use typed_indexmap::{TiMap, TiSet};

use crate::devices::{default_devices, DeviceImpl, DeviceInfo, ParamId};
use crate::expr::{Arena, CircuitParam, CircuitParamCtx};
use crate::veriloga::{self, compile_va};
use crate::Expr;

/// A circuit is the core data structure of melange, it contains a complete description of a
/// circuit with (almost) all information required for simulations.
/// The Circuit structure is always valid and does not need additional processing/validation before
/// the simulation.
/// The only errors that can occur are runtime errors generated by the device implementations like
/// out of bounds parameter errors and numeric problems.
///
/// The Circuit is mutable and can be modified by API consumers.
/// This allows easily building netlist with a typed API instead of generating netlists.
pub struct Circuit {
    /// The name of the circuit
    pub name: String,
    pub(crate) ctx: CircuitParamCtx,
    nodes: TiSet<Node, String>,
    devices: TiMap<DeviceId, &'static str, DeviceInfo>,
    models: TiVec<ModelId, CircuitModel>,
    instances: TiVec<InstanceId, CircuitInstance>,
    pub(crate) namespace: AHashMap<String, NameSpaceEntry>,
    pub(crate) param_assignments: IndexMap<CircuitParam, Expr, ahash::RandomState>,
}

impl Circuit {
    /// Creates a new empty circuit
    pub fn new(name: String, earena: &mut Arena) -> Circuit {
        let mut circ = Circuit {
            name,
            ctx: earena.add_ctx(),
            nodes: TiSet::with_capacity(16),
            devices: TiMap::with_capacity(32),
            models: TiVec::with_capacity(16),
            instances: TiVec::with_capacity(16),
            namespace: AHashMap::with_capacity(64),
            param_assignments: IndexMap::default(),
        };

        circ.nodes.insert("ground".to_owned());

        for dev in default_devices() {
            circ.register_device(dev).expect("registering default device should never fail");
        }

        circ
    }

    /// returns the number of external circuit nodes (including ground).
    /// This does not include internal nodes created by the various devices
    pub fn num_nodes(&self) -> u32 {
        assert!(
            self.nodes.len() < i32::MAX as usize,
            "at most {} nodes are supterminaled",
            i32::MAX
        );
        self.nodes.len() as u32
    }

    /// returns the number of external circuit nodes whose potential is unknown (so excluding ground).
    /// This does not include internal nodes created by the various devices
    pub fn num_unknowns(&self) -> u32 {
        assert!(
            self.nodes.len() < i32::MAX as usize,
            "at most {} nodes are supterminaled",
            i32::MAX
        );
        self.nodes.len() as u32 - 1
    }

    pub fn nodes(&self) -> impl Iterator<Item = Node> {
        (0..self.num_nodes()).map(Node)
    }

    pub fn models(&self) -> impl Iterator<Item = ModelId> {
        self.models.keys()
    }

    pub fn instances(&self) -> impl Iterator<Item = InstanceId> {
        self.instances.keys()
    }

    pub fn num_instances(&self) -> u32 {
        self.instances.len() as u32
    }

    /// Inserts an item into the namespace
    ///
    /// # Returns
    ///
    /// An error if an item with the same name already exists
    fn insert_into_namespace(
        &mut self,
        name: String,
        item: impl Into<NameSpaceEntry>,
    ) -> Result<()> {
        let item = item.into();
        if let Some(old) = self.namespace.insert(name.clone(), item) {
            let old = old.kind();
            unreachable!("an {old} '{name}' was already declared in this circuit")
        }
        Ok(())
    }

    /// Creates a new [`CircuitInstance`] for a devices.
    /// Internally uses [`lookup_device`] and [`new_device_instance`].
    /// See the documentation of those functions for details
    pub fn new_model_by_name(&mut self, name: String, device: &str) -> Result<ModelId> {
        if let Some(device) = self.lookup_device(device) {
            self.new_model(name, device)
        } else {
            bail!("unknown device '{device}'")
        }
    }

    /// Creates a new [`CircuitModel`] for a device by automatically creating an implicit model.
    /// All [`parameters`] are passed to this created model (and not treated as instance parameters).
    ///
    /// # Parameters
    ///
    /// * **`name`** - the name of the created model
    /// * **`device`** - the device implementation for this model
    /// * **`parameters`** - list of model parameters
    ///
    /// # Returns
    ///
    /// An [`ModelId`] that uniquely identifies the created model within the circuit.
    ///
    /// If another item is already declared with the same name in this namespace an error is
    /// returned.
    pub fn new_model(&mut self, name: String, device: DeviceId) -> Result<ModelId> {
        let model = CircuitModel {
            src: CircuitModelSrc::Explicit(name.clone()),
            device,
            parameters: ParamList::default(),
        };
        let id = self.models.push_and_get_key(model);
        self.insert_into_namespace(name, id)?;
        Ok(id)
    }

    /// Creates a new [`CircuitInstance`] for a devices.
    /// Internally uses [`lookup_device`] and [`new_device_instance`].
    /// See the documentation of those functions for details
    pub fn new_device_instance_by_name(
        &mut self,
        name: String,
        device: &str,
        terminal_connections: Vec<Node>,
    ) -> Result<(InstanceId, ModelId)> {
        if let Some(device) = self.lookup_device(device) {
            self.new_device_instance(name, device, terminal_connections)
        } else {
            bail!("unknown device '{device}'")
        }
    }

    /// Creates a new [`CircuitInstance`] for a device by automatically creating an implicit model.
    ///
    /// # Parameters
    ///
    /// * **`name`** - the name of the created instance
    /// * **`device`** - the device for which an instance is created
    /// * **`terminal_connections`** - list of nodes that are connected to this instance
    ///
    /// # Returns
    ///
    /// An [`InstanceId`] that uniquely identifies the created instance within the circuit
    ///
    /// If another item is already declared with the same name in this namespace an error is
    /// returned.
    pub fn new_device_instance(
        &mut self,
        name: String,
        device: DeviceId,
        terminal_connections: Vec<Node>,
    ) -> Result<(InstanceId, ModelId)> {
        let instance_id = self.instances.next_key();
        let model = CircuitModel {
            src: CircuitModelSrc::Implicit(instance_id),
            device,
            parameters: ParamList::default(),
        };
        let model = self.models.push_and_get_key(model);
        let id = self.new_model_instance(name, model, terminal_connections)?;
        Ok((id, model))
    }

    /// Creates a new [`CircuitInstance`] for an existing model.
    ///
    /// # Parameters
    ///
    /// * **`name`** - the name of the created instance
    /// * **`model`** - the model that is used for the created instance
    /// * **`terminal_connections`** - list of nodes that are connected to this instance
    ///
    /// # Returns
    ///
    /// An [`InstanceId`] that uniquely identifies the created instance within the circuit
    ///
    /// If another item is already declared with the same name in this namespace an error is
    /// returned.
    pub fn new_model_instance(
        &mut self,
        name: String,
        model: ModelId,
        terminal_connections: Vec<Node>,
    ) -> Result<InstanceId> {
        let instance = CircuitInstance {
            name: name.clone(),
            model,
            parameters: ParamList::default(),
            connections: terminal_connections,
        };
        let id = self.instances.push_and_get_key(instance);
        self.insert_into_namespace(name, id)?;
        Ok(id)
    }

    /// Lookup a node by name and create it if it doesn't exist
    ///
    /// # Returns
    ///
    /// The Node in this circuit that has the name `name`
    ///
    /// If no such node exists, a new node is created and returned
    pub fn node(&mut self, name: String) -> Node {
        self.nodes.ensure(name).0
    }

    /// Lookup a node by name
    ///
    /// # Returns
    ///
    /// The Node in this circuit that has the name `name`
    ///
    /// If no such node exists returns `None`
    pub fn lookup_node(&self, name: &str) -> Option<Node> {
        self.nodes.index(name)
    }

    /// returns the name of a node
    pub fn node_name(&self, node: Node) -> &str {
        &self.nodes[node]
    }

    /// Registers a device implementation that can be used in this circuit.
    ///
    /// **Note:** Because this device is assumed to be handwritten in rust, it is called a builtin device.
    /// To register devices compiled from Verilog-A use [`load_veriloga_file`] instead.
    ///
    /// # Returns
    ///
    /// A [`DeviceId`] that uniquely identifies this device within the circuit.
    ///
    /// If a **builtin** device with the same name is already registered an error is returned instead.
    /// If a device with the same name is found that was loaded from a Verilog-A file the device is overwritten.
    /// In that case a warning is emitted but no error is returned.
    ///
    /// If another item is already declared with the same name in this namespace an error is
    /// returned.
    pub fn register_device(&mut self, dev_impl: Box<dyn DeviceImpl>) -> Result<DeviceId> {
        let name = dev_impl.get_name();
        if let Some(old_dev) = self.devices.raw.get(name) {
            if let Some(old_va) = &old_dev.va_file {
                warn!("builtin device '{name}' was already registered in {old_va}");
            } else {
                bail!("builtin device '{name}' was registered multiple times")
            }
        }

        let dev_info = DeviceInfo {
            va_file: None,
            terminals: dev_impl.get_terminals(),
            parameters: dev_impl.get_params(),
            dev_impl,
            name,
        };

        let dev = self.devices.insert_full(name, dev_info).0;
        self.insert_into_namespace(name.to_owned(), dev)?;
        Ok(dev)
    }

    /// Compiles a Verilog-A file and registers all Verilog-A (top-level) modules as devices
    /// within the circuit.
    ///
    /// **Note:** If a device with the same name as a loaded module is already registered, the
    /// device is ignored.
    /// In that case a warning is emitted
    ///
    /// # Returns
    ///
    /// The list of **newly added** devices.
    /// Any errors that occur during compilation of the Verilog-A file.
    ///
    /// If another item is already declared with the same name in this namespace an error is
    /// returned.
    pub fn load_veriloga_file(
        &mut self,
        path: Utf8PathBuf,
        opts: &veriloga::Opts,
    ) -> Result<Vec<DeviceId>> {
        let mut new_devices = Vec::new();
        let mut ret_err = None;
        let compilation_result = compile_va(&path, opts)?;
        for dev_impl in compilation_result {
            let name = dev_impl.get_name();
            if let Some(old_dev) = self.devices.raw.get(name) {
                match &old_dev.va_file {
                    Some(old_va) => {
                        warn!("device '{name}' (first defined in {old_va}) was redfined in {path}! ignoring...")
                    }

                    None => {
                        warn!("builtin device '{name}' was redfined in {path}! ignoring...")
                    }
                }
                continue;
            }

            let dev_info = DeviceInfo {
                va_file: None,
                terminals: dev_impl.get_terminals(),
                parameters: dev_impl.get_params(),
                dev_impl,
                name,
            };

            let id = self.devices.insert_full(name, dev_info).0;
            new_devices.push(id);
            if let Err(err) = self.insert_into_namespace(name.to_owned(), id) {
                ret_err = Some(err);
            }
        }
        match ret_err {
            Some(err) => Err(err),
            None => Ok(new_devices),
        }
    }

    /// Lookup a device by name
    ///
    /// # Returns
    ///
    /// The Node in this circuit that has the name `name`
    pub fn lookup_device(&mut self, name: &str) -> Option<DeviceId> {
        self.devices.index(name)
    }

    /// Lookup various information about a device implementation
    ///
    /// # Returns
    ///
    /// A struct that contains information about this device
    ///
    /// None if the [`DeviceId`] does not belong to this circuit
    pub fn device_info(&self, dev: DeviceId) -> Option<&DeviceInfo> {
        self.devices.get_index(dev).map(|(_, res)| res)
    }

    /// Lookup various information about an instance
    ///
    /// # Returns
    ///
    /// A struct that contains information about this instance
    ///
    /// None if the [`InstanceId`] does not belong to this circuit
    pub fn instance_info(&self, instance: InstanceId) -> Option<&CircuitInstance> {
        self.instances.get(instance)
    }

    /// Lookup various information about a model
    ///
    /// # Returns
    ///
    /// A struct that contains information about this instance
    ///
    /// None if the [`ModelId`] does not belong to this circuit
    pub fn model_info(&self, model: ModelId) -> Option<&CircuitModel> {
        self.models.get(model)
    }

    /// Sets the value of an instance parameter
    ///
    /// # Parameters
    ///
    /// * **`instance`** - an instance within this circuit
    /// * **`param_name`** - the name of the instance parameter to be changed
    /// * **`val`** - an expression that represents the value of this parameter
    ///
    /// # Returns
    ///
    /// An error if `param_name` is not an instance parameter of the device implementation
    /// associated with `instance`
    pub fn set_instance_param(
        &mut self,
        instance: InstanceId,
        param_name: &str,
        val: Expr,
    ) -> Result<()> {
        let inst = &mut self.instances[instance];
        let dev = &self.devices[self.models[inst.model].device];
        let (id, info) = match dev.parameters.lookup_param(param_name) {
            Some((id, info)) => (id, info),
            None => bail!("unknown parameter '{param_name}' for {}", dev.name),
        };

        if !info.is_instance_param {
            bail!("'{param_name}' is not an instance parameter")
        }

        inst.parameters.push((id, val));
        Ok(())
    }

    /// Sets the value of a model parameter
    ///
    /// # Parameters
    ///
    /// * **`model`** - a model within this circuit
    /// * **`param_name`** - the name of the instance parameter to be changed
    /// * **`val`** - an expression that represents the value of this parameter
    ///
    /// # Returns
    ///
    /// An error if `param_name` is not a model parameter of the device implementation
    /// associated with `model`
    pub fn set_model_param(&mut self, model: ModelId, param_name: &str, val: Expr) -> Result<()> {
        let model = &mut self.models[model];
        let dev = &self.devices[model.device];
        let id = match dev.parameters.lookup_param_id(param_name) {
            Some(id) => id,
            None => bail!("unknown parameter '{param_name}' for {}", dev.name),
        };

        model.parameters.push((id, val));
        Ok(())
    }

    /// Create a new parameter with name `name` and optionally a default value
    ///
    /// # Returns
    ///
    /// The parameters index and an expression that can be used to read the parameter.
    /// `None` if no parameter `name` was found
    pub fn def_param(
        &mut self,
        name: String,
        default_val: Option<Expr>,
        earena: &mut Arena,
    ) -> Result<(CircuitParam, Expr)> {
        let (param, read_expr) = earena.def_param(self.ctx, name)?;
        if let Some(default_val) = default_val {
            self.param_assignments.insert(param, default_val);
        }
        Ok((param, read_expr))
    }

    /// Lookup an parameter by name
    ///
    /// # Returns
    ///
    /// The parameters index and an expression that can be used to read the parameter.
    /// `None` if no parameter `name` was found
    pub fn lookup_param(&mut self, name: &str, earena: &Arena) -> Option<(CircuitParam, Expr)> {
        earena.lookup_param_by_name(self.ctx, name)
    }
}

impl Index<DeviceId> for Circuit {
    type Output = DeviceInfo;

    fn index(&self, dev: DeviceId) -> &DeviceInfo {
        self.device_info(dev).unwrap()
    }
}

impl Index<InstanceId> for Circuit {
    type Output = CircuitInstance;

    fn index(&self, instance: InstanceId) -> &CircuitInstance {
        self.instance_info(instance).unwrap()
    }
}

impl Index<ModelId> for Circuit {
    type Output = CircuitModel;

    fn index(&self, model: ModelId) -> &CircuitModel {
        self.model_info(model).unwrap()
    }
}

impl IndexMut<InstanceId> for Circuit {
    fn index_mut(&mut self, instance: InstanceId) -> &mut CircuitInstance {
        &mut self.instances[instance]
    }
}

impl IndexMut<ModelId> for Circuit {
    fn index_mut(&mut self, instance: ModelId) -> &mut CircuitModel {
        &mut self.models[instance]
    }
}

#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct Node(u32);

impl Node {
    pub const GROUND: Node = Node(0);
    pub(crate) fn matrix_idx(self) -> i32 {
        self.0 as i32 - 1
    }
}

impl_debug_display!(match Node{ Node(id) => "node{:?}", id;});
impl_idx_from!(Node(u32));

#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct DeviceId(u32);
impl_debug_display!(match DeviceId{ DeviceId(id) => "dev{:?}", id;});
impl_idx_from!(DeviceId(u32));

#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct InstanceId(u32);
impl_debug_display!(match InstanceId{ InstanceId(id) => "inst{:?}", id;});
impl_idx_from!(InstanceId(u32));

#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct ModelId(u32);
impl_debug_display!(match ModelId{ ModelId(id) => "model{:?}", id;});
impl_idx_from!(ModelId(u32));

/// A list of parameter values of a model or instance
pub type ParamList = Vec<(ParamId, Expr)>;

#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub(crate) enum NameSpaceEntry {
    Instance(InstanceId),
    Model(ModelId),
    Device(DeviceId),
}

impl From<ModelId> for NameSpaceEntry {
    fn from(model: ModelId) -> Self {
        NameSpaceEntry::Model(model)
    }
}

impl From<InstanceId> for NameSpaceEntry {
    fn from(inst: InstanceId) -> Self {
        NameSpaceEntry::Instance(inst)
    }
}

impl From<DeviceId> for NameSpaceEntry {
    fn from(inst: DeviceId) -> Self {
        NameSpaceEntry::Device(inst)
    }
}

impl NameSpaceEntry {
    fn kind(self) -> &'static str {
        match self {
            NameSpaceEntry::Instance(_) => "instance",
            NameSpaceEntry::Model(_) => "model",
            NameSpaceEntry::Device(_) => "device",
        }
    }
}

/// Information about a model in a [`Circuit`](create::circuit::Circuit).
/// A model is a collection of parameters and an associated device implementation that can be used
/// for one or multiple devices.
/// The user can explicitly create these models and share them among multiple instances to save
/// resources. Alternatively instance parameters can be used to save performance.
#[derive(PartialEq, Clone, Debug)]
#[non_exhaustive]
pub struct CircuitModel {
    /// The source that produced this model
    pub src: CircuitModelSrc,
    /// The device implementation associated with this model
    pub device: DeviceId,
    /// Expressions that determine the values of the model parameters.
    ///
    /// This list only contains the explicitly specified parameters.
    /// Initializing default values is done by the device implementation in
    /// [`new_model`](crate::devices::DeviceImpl::new_model)
    pub parameters: ParamList,
}

#[derive(PartialEq, Eq, Clone, Debug)]
pub enum CircuitModelSrc {
    /// The model was explicitly created by the user with a name
    Explicit(String),

    /// The model was explicitly created by an instance that
    Implicit(InstanceId),
}

/// Information about a (device) instance in a [`Circuit`](create::circuit::Circuit).
/// An instance corresponds to a single simulation entity that has its own unknowns, matrix entries
/// etc.
#[derive(PartialEq, Clone, Debug)]
#[non_exhaustive]
pub struct CircuitInstance {
    /// The name of this instance
    pub name: String,

    /// Model that is associated with this device and contains model parameters.
    pub model: ModelId,

    /// Expressions that determine the values of the instance parameters.
    ///
    /// This list only contains the explicitly specified parameters.
    /// Initializing default values is done by the device implementation in
    /// [`new_instance`](crate::devices::ModelImpl::new_instance)
    pub parameters: ParamList,

    /// Nodes connected to the terminals of the device
    ///
    /// The number of connected terminal can be smaller then the number of terminals of the devics (but
    /// never larger). The device can decide how to handle this case.
    /// While linear devices (vsource, resistor, isource) will emit an error, Verilog-A devices
    /// usually handle this case using the `$terminal_connected` function.
    pub connections: Vec<Node>,
}
