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
use na::Matrix3x1;
use std::ffi::{CStr, CString};
use std::fmt;
use std::path::PathBuf;

/// Instructions to format an ephemeris time to a string.
pub const TIME_FORMAT: &str = "YYYY-MON-DD HR:MN:SC ::RND";

/// Convert a formatted date from string to ephemeris time.
pub fn ephemeris_from_date<S: Into<String>>(date: S) -> f64 {
    let mut ephemeris_time = 0.0;
    unsafe {
        let date_c = CString::new(date.into()).unwrap().into_raw();
        crate::c::str2et_c(date_c, &mut ephemeris_time);
    }
    ephemeris_time
}

/// Convert an ephemeris time to a formatted string.
pub fn date_from_ephemeris(time: f64) -> String {
    // Define pointer where data will be written.
    let size = TIME_FORMAT.len();
    let date_c = CString::new(String::with_capacity(size))
        .unwrap()
        .into_raw();

    unsafe {
        // Get data.
        let time_format_c = CString::new(TIME_FORMAT.to_string()).unwrap().into_raw();
        crate::c::timout_c(time, time_format_c, size as i32 + 1, date_c);

        // Convert data to Rust type.
        CStr::from_ptr(date_c).to_str().unwrap().to_string()
    }
}

/// Get position of target with respect to observer in the reference frame at time with optional
/// aberration correction.
pub fn position<S: Into<String>>(
    target: S,
    time: f64,
    frame: S,
    aberration_correction: S,
    observer: S,
) -> (Matrix3x1<f64>, f64) {
    // Define pointers where data will be written.
    let mut light_time = 0.0;
    let mut position = [0.0, 0.0, 0.0];

    unsafe {
        // Get data.
        let target_c = CString::new(target.into()).unwrap().into_raw();
        let frame_c = CString::new(frame.into()).unwrap().into_raw();
        let aberration_correction_c = CString::new(aberration_correction.into())
            .unwrap()
            .into_raw();
        let observer_c = CString::new(observer.into()).unwrap().into_raw();
        crate::c::spkpos_c(
            target_c,
            time,
            frame_c,
            aberration_correction_c,
            observer_c,
            &mut position[0],
            &mut light_time,
        );
    }

    // Convert data to Rust type.
    (
        Matrix3x1::new(position[0], position[1], position[2]),
        light_time,
    )
}

/// List of loaded kernels to avoid loading an already loaded.
static mut LOADED_KERNELS: Vec<String> = vec![];

/// Custom error type when trying to load an already load kernel.
#[derive(Debug)]
pub struct KernelAlreadyLoadedError {
    name: String,
}

impl KernelAlreadyLoadedError {
    /// Build KernelAlreadyLoadedError with the name of the kernel.
    pub fn new(name: String) -> Self {
        Self { name }
    }
}

impl fmt::Display for KernelAlreadyLoadedError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "The kernel {} is already loaded.", self.name)
    }
}

/// Custom error type when trying to unload an kernel that was not loaded.
#[derive(Debug)]
pub struct KernelNotLoadedError {
    name: String,
}

impl KernelNotLoadedError {
    /// Build KernelNotLoadedError with the name of the kernel.
    pub fn new(name: String) -> Self {
        Self { name }
    }
}

impl fmt::Display for KernelNotLoadedError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "The kernel {} was not loaded.", self.name)
    }
}

/// Load the kernel if it not already loaded.
fn load<S: AsRef<str>>(name: S) -> Result<(), KernelAlreadyLoadedError> {
    let _name = name.as_ref();
    unsafe {
        // Check if the name of the kernel is in the static list of already loaded kernels.
        match LOADED_KERNELS.contains(&_name.to_string()) {
            true => Err(KernelAlreadyLoadedError::new(_name.to_string())),
            false => {
                // Add the name of the kernel to this list.
                LOADED_KERNELS.push(_name.to_string());

                // Load it.
                let kernel = CString::new(_name).unwrap().into_raw();
                crate::c::furnsh_c(kernel);

                Ok(())
            }
        }
    }
}

/// Unload the kernel if loaded.
fn unload<S: AsRef<str>>(name: S) -> Result<(), KernelNotLoadedError> {
    let _name = name.as_ref();
    unsafe {
        // Check if the name of the kernel is not in the static list of already loaded kernels.
        match !LOADED_KERNELS.contains(&_name.to_string()) {
            true => Err(KernelNotLoadedError::new(_name.to_string())),
            false => {
                // Remove the name of the kernel from this list.
                LOADED_KERNELS.retain(|n| n != &_name.to_string());

                // Unload it.
                let kernel = CString::new(_name).unwrap().into_raw();
                crate::c::unload_c(kernel);

                Ok(())
            }
        }
    }
}

/// Status on the state of the loading of the kernel.
#[derive(Debug, Copy, Clone)]
enum KernelStatus {
    Loaded,
    Unloaded,
}

/// Kernel type to automatically load the kernel on the definition and keep a record of the status
/// of the loading.
#[derive(Debug, Clone)]
pub struct Kernel {
    /// The file to the kernel.
    file: PathBuf,
    /// The status of the loading of the kernel.
    status: KernelStatus,
}

impl Kernel {
    /// Constructor from the path of the file. Automatically load the kernel.
    pub fn new<P: Into<PathBuf>>(file: P) -> Result<Self, KernelAlreadyLoadedError> {
        let mut kernel = Self {
            file: file.into(),
            status: KernelStatus::Unloaded,
        };
        kernel.load()?;
        Ok(kernel)
    }

    /// Get the path as a string.
    pub fn name(&self) -> String {
        self.file.to_str().unwrap().to_string()
    }

    /// Load the kernel if not loaded already.
    pub fn load(&mut self) -> Result<(), KernelAlreadyLoadedError> {
        match self.status {
            KernelStatus::Loaded => Err(KernelAlreadyLoadedError::new(self.name())),
            KernelStatus::Unloaded => {
                // Load if not loaded by someone else.
                load(self.name())?;

                // Update status
                self.status = KernelStatus::Loaded;

                Ok(())
            }
        }
    }

    /// Unload the kernel if loaded.
    pub fn unload(&mut self) -> Result<(), KernelNotLoadedError> {
        match self.status {
            KernelStatus::Unloaded => Err(KernelNotLoadedError::new(self.name())),
            KernelStatus::Loaded => {
                // Unload if loaded.
                unload(self.name())?;

                // Update status
                self.status = KernelStatus::Unloaded;

                Ok(())
            }
        }
    }
}