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
use crate::Error;

use std::fs::{self, File};
use std::io::Write;
use std::path::PathBuf;
use std::sync::mpsc::Receiver;
use std::thread;
use std::time::Instant;

/// # General Information
/// 
/// Writes solution of equation to a given file.
/// Can take a path to write to, names of variables and a prefix for all files.
/// Struct is meant to run on it's own thread to block as little as possible the execution of DzahuiWindow
/// 
/// # Fields
/// 
/// * `receiver` - A sync_channel receiver to obtain vector values to write to file 
/// * `write_path` - A directory to write files in
/// * `variable_names` - Chosen by a given equation. Normally a vector like ['x','y','z'] or similar
/// * `file_prefix` - To identify files from a single simulation
/// 
pub(crate) struct Writer {
    pub(crate) receiver: Receiver<Vec<f64>>,
    write_path: PathBuf,
    variable_names: Vec<&'static str>,
    file_prefix: String
}

impl Writer {

    /// # General Information
    /// 
    /// Creates a new instance of writer (And there should be only one, but it is not enforced).
    /// Can be told to erase previous files on a directory.
    /// Will complain if directory is not present
    /// 
    /// # Parameters
    /// 
    /// * `receiver` - A receiver to obtain the solution to an equation
    /// * `write_path` - The path in which files are created and written to
    /// * `file_prefix` - Prefix for all files of a given simulation
    /// * `variable_names` - A vector with all variables of a problem. Chosen by the equation struct in dzahui window. Also determines how many elements
    /// from solution vector are taken per line
    /// * `erase_prev_dir` - Option to erase every file inside dir given. Will not erase nested directories
    /// 
    pub(crate) fn new<A, B, C>(
        receiver: Receiver<Vec<f64>>,
        write_path: B,
        file_prefix: A,
        variable_names: C,
        erase_prev_dir: bool
    ) -> Result<Self,Error> where
        A: AsRef<str>,
        B: AsRef<str>,
        C: IntoIterator<Item = &'static str> {

        let write_path = PathBuf::from(write_path.as_ref().to_string());

        if !write_path.as_path().exists() {
            return Err(Error::NotFound("Path for creating files and writing values not found"))
        }
        
        if erase_prev_dir {
            let files = fs::read_dir(write_path.clone())?;
            for file in files {
                
                let file = match file {
                    Ok(f) => f,
                    Err(e) => return Err(Error::Io(e))
                };

                if !file.file_type()?.is_dir() {
                    fs::remove_file(file.path())?;
                    log::info!("File: {:?} has been erased",file.path());
                }
            }
        }

        Ok(Self {
            receiver,
            write_path,
            variable_names: variable_names.into_iter().collect(),
            file_prefix: file_prefix.as_ref().to_string()
        })
    }

    /// # General Information
    /// 
    /// Writes once to a file created inside. Will create a file for every call.
    /// To make every file unique, an id must be passed. Dzahui window will pass the time in milis, but any other unique f64 value will do.
    /// 
    /// # Parameters
    /// 
    /// * `&self` - A reference to itself to use `write_path` and `file_prefix`
    /// * `id` - A unique id for a file
    /// * `vals` - a vector with values to write to file
    /// 
    pub(crate) fn write(&self, id: f64, vals: Vec<f64>) -> Result<(),Error> {

        //Create file name
        let mut file_name = self.file_prefix.clone();
        file_name.push_str(id.to_string().as_str());
        file_name.push_str(".csv");
        
        // Create file path
        let mut file_path = self.write_path.clone();
        file_path.push(file_name);

        // Create file
        let mut file = File::create(file_path)?;

        // Write varaibles
        let variables_len = self.variable_names.len();
        let mut header = self.variable_names.iter().fold(String::from(""), |mut prev, cur| {
            prev.push_str(cur);
            prev.push(',');
            prev
        });

        // Erase last comma
        header.pop();
        header.push('\n');

        file.write(header.as_bytes())?;

        // Write values
        for point in vals.chunks(variables_len) {
            let mut line = String::new();
            
            for e in point {
                line.push_str(e.to_string().as_str());
                line.push(',');
            }

            // Erase last comma
            line.pop();
            // Add line jump
            line.push('\n');

            file.write(line.as_bytes())?;

        }

        Ok(())
    }
}

pub(crate) fn spawn(writer: Writer, timer: Instant) {
    thread::spawn(move || {
        loop {
            if let Ok(vals) = writer.receiver.recv() {
                
                let time = timer.elapsed().as_secs_f64();
                let res = writer.write(time, vals);
                // Send result back to main thread
                match res {
                    Ok(()) => log::info!("Data has been saved"),
                    Err(e) => panic!("Something happened between threads. Pleas report error to developer!: {}",e) 
                }
            
            } else {
                break;
            }
        }
    });
}