use crate::analyzer::AnalyzerType;
use crate::dottable::Dottable;
use crate::error::OpossumError;
use crate::lightdata::LightData;
use crate::nodes::NodeGroup;
use crate::optic_ports::OpticPorts;
use core::fmt::Debug;
use std::collections::HashMap;

pub type LightResult = HashMap<String, Option<LightData>>;
type Result<T> = std::result::Result<T, OpossumError>;

//     /// Creates a new [`OpticNode`]. The concrete type of the component must be given while using the `new` function.
//     /// The node type ist a struct implementing the [`Optical`] trait. Since the size of the node type is not known at compile time it must be added as `Box<nodetype>`.
//     ///
//     /// # Examples
//     ///
//     /// ```rust
//     /// use opossum::optic_node::OpticNode;
//     /// use opossum::nodes::Dummy;
//     ///
//     /// let node=OpticNode::new("My node", Dummy::default());
//     /// ```

/// This is the basic trait that must be implemented by all concrete optical components.
pub trait Optical: Dottable {
    /// Sets the name of this [`Optical`].
    fn set_name(&mut self, _name: &str) {}
    /// Returns a reference to the name of this [`Optical`].
    fn name(&self) -> &str {
        self.node_type()
    }
    /// Return the type of the optical component (lens, filter, ...). The default implementation returns "undefined".
    fn node_type(&self) -> &str {
        "undefined"
    }
    /// Return the available (input & output) ports of this [`Optical`].
    fn ports(&self) -> OpticPorts {
        OpticPorts::default()
    }
    /// Perform an analysis of this element. The type of analysis is given by an [`AnalyzerType`].
    ///
    /// This function is normally only called by [`OpticScenery::analyze()`](crate::optic_scenery::OpticScenery::analyze()).
    ///
    /// # Errors
    ///
    /// This function will return an error if internal element-specific errors occur and the analysis cannot be performed.
    fn analyze(
        &mut self,
        _incoming_data: LightResult,
        _analyzer_type: &AnalyzerType,
    ) -> Result<LightResult> {
        print!("{}: No analyze function defined.", self.node_type());
        Ok(LightResult::default())
    }
    /// Export analysis data to file with the given name.
    fn export_data(&self, _file_name: &str) {
        println!(
            "no export_data function implemented for nodetype <{}>",
            self.node_type()
        )
    }
    /// Returns `true` if the [`Optical`] represents a detector which can report analysis data.
    fn is_detector(&self) -> bool {
        false
    }
    /// Mark this [`Optical`] as inverted.
    fn set_inverted(&mut self, _inverted: bool) {
        // self.ports.set_inverted(inverted);
        // self.node.set_inverted(inverted);
    }
    /// Returns `true` if this [`Optical`] is inverted.
    fn inverted(&self) -> bool {
        false
    }
    fn as_group(&self) -> Result<&NodeGroup> {
        Err(OpossumError::Other("cannot cast to group".into()))
    }
}

impl Debug for dyn Optical {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{} ({})", self.name(), self.node_type())
    }
}

#[cfg(test)]
mod test {
    // use crate::nodes::{Detector, Dummy};
    // #[test]
    // fn new() {
    //     let node = OpticNode::new("Test", Dummy::default());
    //     assert_eq!(node.name, "Test");
    //     assert_eq!(node.inverted(), false);
    // }
    // #[test]
    // fn set_name() {
    //     let mut node = OpticNode::new("Test", Dummy::default());
    //     node.set_name("Test2".into());
    //     assert_eq!(node.name, "Test2")
    // }
    // #[test]
    // fn name() {
    //     let node = OpticNode::new("Test", Dummy::default());
    //     assert_eq!(node.name(), "Test")
    // }
    // #[test]
    // fn set_inverted() {
    //     let mut node = OpticNode::new("Test", Dummy::default());
    //     node.set_inverted(true);
    //     assert_eq!(node.inverted(), true)
    // }
    // #[test]
    // fn inverted() {
    //     let mut node = OpticNode::new("Test", Dummy::default());
    //     node.set_inverted(true);
    //     assert_eq!(node.inverted(), true)
    // }
    // #[test]
    // fn is_detector() {
    //     let node = OpticNode::new("Test", Dummy::default());
    //     assert_eq!(node.is_detector(), false);
    //     let node = OpticNode::new("Test", Detector::default());
    //     assert_eq!(node.is_detector(), true)
    // }
    // #[test]
    // #[ignore]
    // fn to_dot() {
    //     let node = OpticNode::new("Test", Dummy::default());
    //     assert_eq!(
    //         node.to_dot("i0", "".to_owned()).unwrap(),
    //         "  i0 [label=\"Test\"]\n".to_owned()
    //     )
    // }
    // #[test]
    // #[ignore]
    // fn to_dot_inverted() {
    //     let mut node = OpticNode::new("Test", Dummy::default());
    //     node.set_inverted(true);
    //     assert_eq!(
    //         node.to_dot("i0", "".to_owned()).unwrap(),
    //         "  i0 [label=\"Test(inv)\"]\n".to_owned()
    //     )
    // }
    // #[test]
    // fn node_type() {
    //     let node = OpticNode::new("Test", Dummy::default());
    //     assert_eq!(node.node_type(), "dummy");
    // }
}