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
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
/*!
 * This module contains the conversion implementations for types which can be
 * unambiguously converted to an from an mxArray. That is, the type itself has enough
 * information to determine how many dimensions it should have, and what the lengths of
 * these are.
 *
 * Currently, these are the types from NDarray, and scalar types. [Vec] is a clear
 * exception — since Matlab types have at least two dimensions, it cannot be
 * unambigiously determined from the type alone whether this Vec should be a row or a
 * column vector. For this, see [VecToMatlab] instead.
 *
 * Depending on whether the target API is interleaved or non-interleaved (i.e., whether
 * the real and imaginary parts of complex numbers are stored together or in different
 * arrays), different implementations are provided. Some implementations may need to do
 * some copying to yield the expected type, and are thus relatively expensive (these are
 * the conversions from non-interleaved to interleaved, mostly).
 */
use ndarray::{ArrayViewD, Array, IxDyn, ShapeBuilder, Dimension};

#[allow(unused)]
use ndarray::{Dim, IxDynImpl};

#[allow(unused)]
use num_complex::Complex;

use crate::mex::{
	raw::{
		mxArray,
		mxChar,
	},
	FromMatlabError,
	MatlabPrimitive,
	MatlabClass,
};
#[allow(unused)]
use crate::mex::MatlabComplex;

#[allow(unused)]
use crate::mappable::{Mappable, MutMappable};

use crate::mex::pointers::MxArray;

#[allow(unused)]
use std::ffi::CString;

/**
 * Trait describing how a type can be built from an mxArray. It takes in a shared
 * reference because Matlab still owns the data.
 */
pub trait FromMatlab<'a> {
	fn from_matlab(mx: &'a mxArray) -> Result<Self, FromMatlabError> where Self: Sized;
}

impl mxArray {
	/**
	 * `from_matlab` is to `to_rust` as `from` is to `into`: this method on `mxArray`
	 * makes method chaining possible, and thus allows for greater ergonomics. Best
	 * used in combination with
	 * [`missing::error_if_missing`](crate::message::Missing::error_if_missing).
	 */
	pub fn to_rust<'a, T>(&'a self) -> Result<T, FromMatlabError> where T: FromMatlab<'a> {
		T::from_matlab(self)
	}
}
/**
 * Trait describing how a type can be moved into an mxArray. It takes ownership because
 * Matlab takes over memory management.
 */
pub trait ToMatlab {
	fn to_matlab(self) -> MxArray;
}

/**
 * Implementation of the FromMatlab trait for scalar mxArrays.
 */
impl<'a, T> FromMatlab<'a> for T where T: MatlabPrimitive {
	fn from_matlab(mx: &'a mxArray) -> Result<Self, FromMatlabError> {
		let data = mx.data_slice()?;
		match data.len() {
			1 => Ok(data[0]),
			_ => Err(FromMatlabError::Size)
		}
	}
}

/**
 * Convert a scalar complex mxArray into a Complex<T>. This implementation does copy, but
 * only two values (the real and imaginary parts of the value) since they come from two
 * different arrays.
 */
#[cfg(any(not(feature = "matlab_interleaved"), feature = "doc"))]
impl<'a, T> FromMatlab<'a> for Complex<T> where
	Complex<T>: MatlabComplex,
	T: MatlabPrimitive
{
	fn from_matlab(mx: &'a mxArray) -> Result<Self, FromMatlabError> {
		let Complex { re, im } = mx.data_slices()?;

		match re.len() {
			1 => Ok(Complex { re: re[0], im: im[0] }),
			_ => Err(FromMatlabError::Size)
		}
	}
}

/**
 * In Matlab, the empty array is often used as a sentinel object — there is no value to
 * be provided. This maps directly onto Rust's Option type, for which this implementation
 * is provided. Option's methods can thus be used to provide defaults or otherwise
 * operate on this maybe value.
 */
impl<'a, T> FromMatlab<'a> for Option<T> where T: FromMatlab<'a> {
	fn from_matlab(mx: &'a mxArray) -> Result<Self, FromMatlabError> {
		Ok(if mx.numel() == 0 {
			None
		} else {
			Some(T::from_matlab(mx)?)
		})
	}
}

/**
 * Convert an mxArray into a ArrayView of the correct type.
 */
impl<'a, T> FromMatlab<'a> for ArrayViewD<'a, T> where T: MatlabPrimitive {
	fn from_matlab(mx: &'a mxArray) -> Result<Self, FromMatlabError> {
		T::correct_class(mx)?;

		let data = mx.data_slice()?;
		let dims = mx.dimensions();

		// TL; DR: Matlab is _very_ dumb.
		//
		// It seems like it uses different layouts depending on how many
		// dimensions the matrix has.
		//
		// For example, for a NxM matrix, it uses row major. This is a
		// simple fix, just reverse the axes and we have a normal layout.
		//
		// But once MxNxL is introduced, this breaks down. The fix seems
		// to be that we still reverse the axis, but then swap the last
		// two so it makes sense again.
		//
		// In other words:
		// Matlab : row, column, plane, block <- Also fortran layout
		// reverse: block, plane, column, row
		// swap   : block, plane, row, column <- Also C/Rust/NdArray layout
		let mut v = ArrayViewD::from_shape(IxDyn(dims).f(), data)
			.expect("MATLAB should provide corectly shaped data")
			.reversed_axes();
		v.swap_axes(dims.len() - 2, dims.len() - 1);
		Ok(v)
	}
}

/**
 * Convert a complex mxArray into a Complex<ArrayView<T>>. Use when using a non-interleaved
 * API and you do not want to incur the cost of interleaving the real and complex values.
 */
#[cfg(any(not(feature = "matlab_interleaved"), feature = "doc"))]
impl<'a, T> FromMatlab<'a> for Complex<ArrayViewD<'a, T>> where
	T: MatlabPrimitive,
	Complex<T>: MatlabComplex,
{
	fn from_matlab(mx: &'a mxArray) -> Result<Self, FromMatlabError> {

		let dims = mx.dimensions();
		let shape = IxDyn(dims).f();

		let mut c = mx.data_slices()?
			.map(|x| ArrayViewD::from_shape(shape.clone(), x)
				.expect("MATLAB should provide corectly shaped data")
				.reversed_axes()
			);
		c.map_mut(|x| x.swap_axes(dims.len() - 2, dims.len() - 1));
		Ok(c)
	}
}

/**
 * Convert a complex mxArray into an ArrayView<Complex<T>>. Use when programming against
 * a non-interleaved API but some part of your algorithm requires the used of interleaved
 * real and imaginary parts, and you are willing to accept the costs of the copying the
 * interleaving requires.
 */
#[cfg(any(not(feature = "matlab_interleaved"), feature = "doc"))]
impl<'a, T> FromMatlab<'a> for Array<Complex<T>, Dim<IxDynImpl>> where
	Complex<T>: MatlabComplex,
	T: MatlabPrimitive,
{
	fn from_matlab(mx: &'a mxArray) -> Result<Self, FromMatlabError> {
		let Complex { re, im }: Complex<ArrayViewD<T>> = FromMatlab::from_matlab(mx)?;
		let mut target = Array::uninit(re.dim());
		ndarray::azip!((&re in re, &im in im, t in &mut target) {
			t.write(Complex { re, im });
		});
		Ok(unsafe { target.assume_init() })
	}
}

/**
 * Copy a character vector into a CString.
 */
impl<'a> FromMatlab<'a> for CString {

	fn from_matlab(mx: &'a mxArray) -> Result<Self, FromMatlabError> {
		use crate::mex::raw::mxArrayToString;

		<mxChar as MatlabClass>::correct_class(mx)?;

		let ptr = unsafe { mxArrayToString(mx) };

		// we checked whether the array was an mxChar array, so the only
		// remaining option is an OOM-error.
		if ptr.is_null() {
			panic!("OOM")
		}

		Ok(unsafe {CString::from_raw(ptr) } )
	}
}


/**
 * Copy an character vector into a String, convert if needed.
 *
 * When debug assertions are enabled, this implementation checks the validity of the
 * string matlab returns. If it is not valid, it panics.
 *
 * Octave does not implement the mxChar to UTF-8 conversion functionality, so this
 * implementation is not implemented for that target API. However, Octave does expose a
 * function to get a C-string, but we cannot assume it is valid UTF-8. If you do want a
 * String, convert the mxArray into a CString first, which can then convert it into a
 * String if the data is valid UTF-8.
 */
#[cfg(any(not(feature = "octave"), feature = "doc"))]
impl<'a> FromMatlab<'a> for String {
	fn from_matlab(mx: &'a mxArray) -> Result<Self, FromMatlabError> {
		use std::ffi::CStr;
		use crate::mex::raw::mxArrayToUTF8String;

		<mxChar as MatlabClass>::correct_class(mx)?;

		let ptr = unsafe { mxArrayToUTF8String(mx) };

		// we checked whether the array was an mxChar array, so the only
		// remaining option is an OOM-error.
		if ptr.is_null() {
			panic!("OOM");
		}

		let len = {
			let cstr = unsafe { CStr::from_ptr(ptr) };

			#[cfg(debug_assertions)]
			let _ustr = cstr
				.to_str()
				.expect("expect valid UTF-8 from matlab");

			cstr.to_bytes().len()
		};

		let string = unsafe { String::from_raw_parts(ptr as *mut u8, len, len) };

		Ok(string)
	}
}

/**
 * Convert an owned Array into an mxArray, transferring onwership to it.
 */
impl<T, D> ToMatlab for Array<T, D> where
	D: Dimension,
	T: MatlabPrimitive,
{

	fn to_matlab(mut self) -> MxArray {
		let num_axes = self.shape().len();

		if num_axes >= 2 {
			self.swap_axes(num_axes - 2, num_axes - 1);
		}

		let reshaped = if let Ok(v) = self.reversed_axes()
			.try_into_owned_nocopy()
		{
			v
		} else {
			panic!("Reversing axing strides should not cause a copy");
		};

		// this dance with the Dimension and the shape is required because after
		// this step we convert the Array into a raw vector, so any reference
		// into it regarding the shape would no longer be valid.
		let dim = reshaped.raw_dim();
		let shape = dim.as_array_view().to_slice().unwrap();

		// There is as of yet no way to directly extract a heap allocated slice
		// out of NDarray, so we explicitly go via Vec to Box instead.
		let data = reshaped
			.into_raw_vec()
			.into_boxed_slice();

		MxArray::new(data, shape)
			.expect("NDarray should give consistent data")
	}
}

// TODO: Allow this impl to not own the data it's given, since we have to do a copy
// anyway to de-interleave the data.
// TODO: Add an impl to convert a Complex<Array(View)<T>> into an mxArray.
/**
 * Convert an owned array of complex matlab primitives to an mxArray. This implementation
 * de-interleaves the real and imaginary parts into two new arrays, before handing those
 * over to matlab.
 */
#[cfg(any(not(feature = "matlab_interleaved"), feature = "doc"))]
impl<T, D> ToMatlab for Array<Complex<T>, D> where
	D: Dimension,
	Complex<T>: MatlabComplex,
	T: MatlabPrimitive,
{

	fn to_matlab(mut self) -> MxArray {
		let num_axes = self.shape().len();

		if num_axes >= 2 {
			self.swap_axes(num_axes - 2, num_axes - 1);
		}

		let reshaped = self.reversed_axes();

		// this dance with the Dimension and the shape is required because after
		// this step we convert the Array into a raw vector, so any reference
		// into it regarding the shape would no longer be valid.
		let dim = reshaped.raw_dim();
		let shape = dim.as_array_view().to_slice().unwrap();

		let c = reshaped
			.view()
			.split_complex()
			.map(|x| x
				.into_owned()
				.into_raw_vec()
				.into_boxed_slice()
			);

		MxArray::new_complex(c, shape)
			.expect("NDarray should give consistent data")
	}
}

/**
 * Convert a Vec<T: MatlabPrimitive> to an mxArray. This will create a column vector — if
 * you want control over whether it is a column vector or a row vector, see
 * [VecToMatlab].
 */
impl<T> ToMatlab for Vec<T> where T: MatlabPrimitive {
	fn to_matlab(self) -> MxArray {
		self.into_boxed_slice().vec_to_matlab(VecType::default())
	}
}

/**
 * Convert scalar primitives to a scalar mxArray.
 *
 * Anything which can be converted into a heap allocated scalar qualifies. This includes
 * stack allocated primitives, but also Rc's and references to T's. It depends on the
 * implementation of Into<Box<T>> how expensive this operation is.
 */
impl<T> ToMatlab for T where T: MatlabPrimitive {
	fn to_matlab(self) -> MxArray {
		let heap_allocated: Box<[T]> = Box::from(&[self][..]);
		MxArray::new(heap_allocated, &[1, 1]).expect("A scalar is always a 1x1 scalar")
	}
}

/**
 * Enum used to provide a bit of extra information when converting a Vec<T> to an
 * mxArray.
 *
 * _See also_ [VecToMatlab]
 */
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum VecType {
	Column,
	Row
}

impl VecType {
	/// Construct the shape matrix
	pub fn shape_matrix(&self, length: usize) -> [usize; 2] {
		match self {
			VecType::Row => [1, length],
			VecType::Column => [length, 1]
		}
	}
}

impl Default for VecType {
	/// The default vectype is a column.
	fn default() -> Self {
		Self::Column
	}
}

/**
 * Since a Matlab vector always has two dimensions, we need some extra
 * information to determine what shape the converted matrix in Matlab will have,
 * since it is not stored in a Rust vector. See also [VecType].
 */
pub trait VecToMatlab {
	fn vec_to_matlab(self, t: VecType) -> MxArray;
}

/**
 * Convert a heap allocated slice to an mxArray.
 */
impl<T> VecToMatlab for Box<[T]> where
	T: MatlabPrimitive
{
	fn vec_to_matlab(self, t: VecType) -> MxArray {
		let shape = t.shape_matrix(self.len());
		MxArray::new(self, &shape).expect("[1 len] or [len 1] should always be valid for vectors")
	}
}

/**
 * Alternative implementation of this trait, available when the target API does not
 * interleave real and imaginary parts.
 */
#[cfg(any(not(feature = "matlab_interleaved"), feature = "doc"))]
impl<T> VecToMatlab for Box<[Complex<T>]> where
	Complex<T>: MatlabComplex,
	T: MatlabPrimitive,
{
	fn vec_to_matlab(self, t: VecType) -> MxArray {
		let (re, im): (Vec<T>, Vec<T>) = self
			.iter()
			.map(|Complex { re, im }| (re, im))
			.unzip();

		let shape = t.shape_matrix(re.len());

		let data = Complex {re, im}.map(|x| x.into_boxed_slice());

		MxArray::new_complex(data, &shape).expect("shape_matrix should always be valid for vectors")
	}
}