1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
//! # Model framework
//!
//! The model module define the interface to build a [Model].
//!
//! Any structure that implements [Task], and its super trait [Check], can be part of an actors [Model].
//!
//! [Model]: crate::model::Model

use std::path::PathBuf;

use crate::system::System;
use crate::{
    actor::PlainActor,
    graph::{self, Graph},
    model::{self, PlainModel},
    ActorError,
};

#[derive(Debug, thiserror::Error)]
pub enum CheckError {
    #[error("error in Task from Actor")]
    FromActor(#[from] ActorError),
    #[error("error in Task from Model")]
    FromModel(#[from] model::ModelError),
}

/// Interface for model verification routines
///
pub trait Check {
    /// Validates the inputs
    ///
    /// Returns en error if there are some inputs but the inputs rate is zero
    /// or if there are no inputs and the inputs rate is positive
    fn check_inputs(&self) -> std::result::Result<(), CheckError>;
    /// Validates the outputs
    ///
    /// Returns en error if there are some outputs but the outputs rate is zero
    /// or if there are no outputs and the outputs rate is positive
    fn check_outputs(&self) -> std::result::Result<(), CheckError>;
    /// Return the number of inputs
    fn n_inputs(&self) -> usize;
    /// Return the number of outputs
    fn n_outputs(&self) -> usize;
    /// Return the hash # of inputs
    fn inputs_hashes(&self) -> Vec<u64>;
    /// Return the hash # of outputs
    fn outputs_hashes(&self) -> Vec<u64>;
    fn _as_plain(&self) -> PlainActor;
    fn is_system(&self) -> bool {
        false
    }
}

#[derive(Debug, thiserror::Error)]
pub enum TaskError {
    #[error("error in Task from Actor")]
    FromActor(#[from] ActorError),
    #[error("error in Task from Model")]
    FromModel(#[from] model::ModelError),
}

/// Interface for running model components
#[async_trait::async_trait]
pub trait Task: Check + std::fmt::Display + Send + Sync {
    /// Runs the [Actor](crate::actor::Actor) infinite loop
    ///
    /// The loop ends when the client data is [None] or when either the sending of receiving
    /// end of a channel is dropped
    async fn async_run(&mut self) -> std::result::Result<(), TaskError>;
    /// Run the actor loop in a dedicated thread
    fn spawn(self) -> tokio::task::JoinHandle<std::result::Result<(), TaskError>>
    where
        Self: Sized + 'static,
    {
        tokio::spawn(async move { Box::new(self).task().await })
    }
    /// Run the actor loop
    async fn task(self: Box<Self>) -> std::result::Result<(), TaskError>;
    fn as_plain(&self) -> PlainActor;
}

/// Flowchart name
pub trait GetName {
    /// Returns the flowchart name
    fn get_name(&self) -> String {
        "integrated_model".into()
    }
}

#[derive(Debug, thiserror::Error)]
pub enum FlowChartError {
    #[error("no graph to walk, may be there is no actors!")]
    NoGraph,
    #[error("failed to write SVG charts")]
    Rendering(#[from] graph::RenderError),
}

/// Actors flowchart interface
pub trait FlowChart: GetName {
    /// Returns the actors network graph
    fn graph(&self) -> Option<Graph>;

    fn to_html(&self) -> std::result::Result<PathBuf, FlowChartError> {
        Ok(self
            .graph()
            .ok_or(FlowChartError::NoGraph)?
            .walk()
            .into_svg()?
            .to_html()?)
    }

    /// Writes the actors flowchart
    ///
    /// The flowchart file is written either in the current directory
    /// or in the directory give by the environment variable `DATA_REPO`.
    /// The flowchart is created with [Graphviz](https://www.graphviz.org/) neato filter,
    /// other filters can be specified with the environment variable `FLOWCHART`
    fn flowchart(self) -> Self
    where
        Self: Sized,
    {
        if let Err(e) = self.to_html() {
            println!("failed to write flowchart Web page caused by:\n {e:?}");
        }
        self
    }
    fn flowchart_open(self) -> Self
    where
        Self: Sized,
    {
        match self.to_html() {
            Ok(path) => {
                if let Err(e) = open::that(path) {
                    println!("failed to open flowchart Web page caused by:\n {e:?}");
                }
            }
            Err(e) => println!("failed to write flowchart Web page caused by:\n {e:?}"),
        };
        self
    }
}
impl<T: GetName> FlowChart for T
where
    for<'a> &'a T: IntoIterator<Item = PlainActor>,
{
    fn graph(&self) -> Option<Graph> {
        // let actors: Vec<_> = self.into_iter().collect();
        let actors = PlainModel::from_iter(self.into_iter());
        if actors.is_empty() {
            None
        } else {
            Some(Graph::new(self.get_name(), actors))
        }
    }
    /*
    fn flowchart(self) -> Self {
        match self.graph() {
            None => println!("no graph to make, may be there is no actors!"),
            Some(graph) => match graph.walk().into_svg() {
                Ok(r) => {
                    if let Err(e) = r.to_html() {
                        println!("failed to write flowchart Web page caused by:\n {e}");
                    }
                }
                Err(e) => println!("failed to write SVG charts caused by:\n {e}"),
            },
        }
        self
    } */

    /*     fn flowchart_open(self) -> Self {
        match self.graph() {
            None => println!("no graph to make, may be there is no actors!"),
            Some(graph) => match graph.walk().into_svg() {
                Ok(r) => match r.to_html() {
                    Ok(path) => {
                        if let Err(e) = open::that(path) {
                            println!("failed to open flowchart Web page caused by:\n {e}");
                        }
                    }
                    Err(e) => println!("failed to write flowchart Web page caused by:\n {e}"),
                },
                Err(e) => println!("failed to write SVG charts caused by:\n {e}"),
            },
        }
        self
    }*/
}

pub trait SystemFlowChart {
    fn graph(&self) -> Option<Graph>;
    // fn flowchart(&self) -> &Self;
}
impl<T: System> SystemFlowChart for T
where
    for<'a> &'a T: IntoIterator<Item = Box<&'a dyn Check>>,
{
    fn graph(&self) -> Option<Graph> {
        // let actors: Vec<_> = self.into_iter().map(|x| x._as_plain()).collect();
        let actors = PlainModel::from_iter(self.into_iter());
        if actors.is_empty() {
            None
        } else {
            Some(Graph::new(self.get_name(), actors))
        }
    }

    /*     fn flowchart(&self) -> &Self {
        match self.graph() {
            None => println!("no graph to make, may be there is no actors!"),
            Some(graph) => match graph.walk().into_svg() {
                Ok(r) => {
                    if let Err(e) = r.to_html() {
                        println!("failed to write flowchart Web page caused by:\n {e}");
                    }
                }
                Err(e) => println!("failed to write SVG charts caused by:\n {e}"),
            },
        }
        &self
    } */
}

#[cfg(test)]
mod tests {
    use std::process::{Command, Stdio};
    #[test]
    fn pipe() {
        let graph = Command::new("echo")
            .arg(r#"digraph G { a -> b }"#)
            .stdout(Stdio::piped())
            .spawn()
            .unwrap();
        let svg = Command::new("dot")
            .arg("-Tsvg")
            .stdin(Stdio::from(graph.stdout.unwrap()))
            .stdout(Stdio::piped())
            .spawn()
            .unwrap();
        let output = svg.wait_with_output().unwrap();
        let result = std::str::from_utf8(&output.stdout).unwrap();
        let svg = result.lines().skip(6).collect::<Vec<_>>().join("");
        println!("{:#}", &svg);
    }
}