1 module dhdf5.dataset;
2 
3 private
4 {
5 	import std.traits : isDynamicArray;
6 	import std.range : isInputRange;
7 	import dhdf5.dataspec : DataSpecification;
8 
9 	/// array is dynamic array whose dimensions sizes are set using dim array elements
10 	/// as sizes.
11 	auto setDynArrayDimensions(T)(ref T arr, const size_t[] dim) if(isDynamicArray!T)
12 	{
13 		auto setDimImpl(T)(ref T t, const size_t[] dim)
14 		{
15 			static if(isDynamicArray!T)
16 			{
17 				static if(isDynamicArray!(typeof(t[0])))
18 				{
19 					t.length = dim[0];
20 					foreach(ref e; t[0..$])
21 					{
22 						e.length = dim[1];
23 					}
24 
25 					return setDimImpl(t[0], dim[1..$]);
26 				}
27 				else
28 				{
29 					t.length = dim[0];
30 				}
31 			}
32 
33 		}
34 
35 		return setDimImpl(arr, dim);
36 	}
37 
38 	unittest
39 	{
40 		size_t[] dim = [1, 3, 5];
41 		int[][][] res;
42 		setDynArrayDimensions(res, dim);
43 		assert(res.length == 1);
44 		assert(res[0].length == 3);
45 		assert(res[0][0].length == 5);
46 
47 		int[] res2;
48 		setDynArrayDimensions(res2, dim);
49 		assert(res2.length == 1);
50 	}
51 
52 	template rankOf(R)
53 		if (isInputRange!R)
54 	{
55 		auto rankOfImpl(Range)()
56 		{
57 			import std.range : ElementType;
58 
59 			static if (isInputRange!Range)
60 			{
61 				return 1 + rankOfImpl!(ElementType!Range);
62 			}
63 			else
64 				return 0;
65 		}
66 
67 		enum rankOf = rankOfImpl!R;
68 	}
69 
70 	unittest
71 	{
72 		{
73 			enum rank = rankOf!(uint[]);
74 			static assert (rank == 1);
75 		}
76 
77 		{
78 			enum rank = rankOf!(uint[][][][][][]);
79 			static assert (rank == 6);
80 		}
81 
82 		{
83 			import std.range : only;
84 
85 			auto range = only(1, 2);
86 			alias Range = typeof(range);
87 
88 			enum rank = rankOf!Range;
89 			static assert (rank == 1);
90 		}
91 
92 		{
93 			import std.range : only;
94 
95 			auto range = only(1, 2);
96 			alias Range = typeof(range);
97 
98 			auto ror = only(range, range);
99 			alias RoR = typeof(ror);
100 
101 			enum rank = rankOf!RoR;
102 			static assert (rank == 2);
103 		}
104 	}
105 
106 	struct Dataspace
107 	{
108 		import hdf5.hdf5 : hid_t, H5Dget_space, H5Sclose;
109 
110 		hid_t hid = -1;
111 
112 		this(hid_t dataset)
113 		{
114 			hid = H5Dget_space (dataset);
115 			assert(hid >= 0);
116 		}
117 
118 		~this()
119 		{
120 			if (hid != -1)
121 			{
122 				H5Sclose (hid);
123 				hid = -1;
124 			}
125 		}
126 
127 		alias hid this;
128 	}
129 }
130 
131 struct Dataset(Data, DataSpecType = typeof(DataSpecification!Data.make()))
132 	if (isInputRange!Data)
133 {
134 	import std.range : ElementType, hasLength;
135 	import std.traits : isImplicitlyConvertible;
136 	import std.typecons : RefCounted;
137 	import hdf5.hdf5 : hid_t, hsize_t, H5Sget_simple_extent_ndims, H5Dclose;
138 	import dhdf5.file : H5File;
139 
140 	static assert (isInputRange!Data, Data.stringof ~ " should be input range");
141 
142 	enum rank = rankOf!Data;
143 	alias Type = RefCounted!(Dataset!(Data, DataSpecType));
144 
145 	public
146 	{
147 		this(hid_t dataset, DataSpecType data_spec)
148 		{
149 			import std.exception : enforce;
150 			enforce (dataset != -1);
151 			_dataset = dataset;
152 			_data_spec = data_spec;
153 
154 			debug
155 			{
156 				import hdf5.hdf5 : H5Sget_simple_extent_ndims;
157 
158 				auto space_id = Dataspace (_dataset);
159 				assert (rank == H5Sget_simple_extent_ndims (space_id));
160 			}
161 		}
162 
163 		~this()
164 		{
165 			import hdf5.hdf5 : H5Dclose;
166 
167 			if (_dataset != -1)
168 			{
169 				H5Dclose(_dataset);
170 				_dataset = -1;
171 			}
172 		}
173 	}
174 
175 	static create(ref const(H5File) file, string name)
176 	{
177 		import std..string: toStringz;
178 		import hdf5.hdf5 : H5P_DEFAULT, H5P_DATASET_CREATE, H5Pcreate, H5Pclose,
179 			H5Pset_chunk, H5Screate_simple, H5Dcreate2;
180 		import dhdf5.dataspec : countDimensions;
181 		import std.conv: castFrom;
182 
183 		enum DEFAULT_CHUNK_SIZE = 512;
184 		auto dcpl_id = H5P_DEFAULT;
185 
186 		/* Create a dataset creation property list and set it to use chunking
187 		 */
188 		auto max_dim = countDimensions!(Data)();
189 		auto curr_dim = max_dim.dup;
190 		curr_dim[] = 0;
191 		hsize_t[] chunk_dims;
192 		chunk_dims.length = curr_dim.length;
193 		chunk_dims[] = DEFAULT_CHUNK_SIZE;
194 		dcpl_id = H5Pcreate (H5P_DATASET_CREATE);
195 		scope(exit) H5Pclose (dcpl_id);
196 
197 		H5Pset_chunk (dcpl_id, castFrom!size_t.to!int(chunk_dims.length), chunk_dims.ptr);
198 
199 		auto space = H5Screate_simple (castFrom!(size_t).to!int(curr_dim.length), curr_dim.ptr, max_dim.ptr);
200 		auto data_spec = DataSpecType.make();
201 		auto dataset = H5Dcreate2 (file.tid, name.toStringz, data_spec.tid, space, H5P_DEFAULT, dcpl_id, H5P_DEFAULT);
202 		assert(dataset >= 0);
203 		return RefCounted!(Dataset!(Data, DataSpecType))(dataset, data_spec);
204 	}
205 
206 	static open(ref const(H5File) file, string name)
207 	{
208 		import std..string: toStringz;
209 		import hdf5.hdf5 : H5P_DEFAULT, H5Dopen2;
210 
211 		auto data_spec = DataSpecType.make();
212 		auto dataset = H5Dopen2 (file.tid, name.toStringz, H5P_DEFAULT);
213 		assert(dataset >= 0);
214 		return RefCounted!(Dataset!(Data, DataSpecType))(dataset, data_spec);
215 	}
216 
217 	/**
218 	 * Return current shape, that can change during programm running.
219 	 */
220 	auto currShape() const
221 	{
222 		import hdf5.hdf5 : H5Sget_simple_extent_dims;
223 
224 		if (_dataset == -1)
225 			return typeof(_curr_shape).init;
226 
227 		auto space_id  = Dataspace (_dataset);
228 
229 		H5Sget_simple_extent_dims (space_id, _curr_shape.ptr, _max_shape.ptr);
230 
231 		return _curr_shape;
232 	}
233 
234 	auto currShape(hsize_t[] extent)
235 	{
236 		import hdf5.hdf5;
237 
238 		assert (_dataset != -1);
239 		auto status = H5Dset_extent (_dataset, extent.ptr);
240 		assert(status >= 0);
241 	}
242 
243 	/**
244 	 * Return maximal shape, that doesn't change during program running.
245 	 */
246 	auto maxShape() const pure
247 	{
248 		return _max_shape;
249 	}
250 
251 	/*
252 	 * Read data from the dataset.
253 	 */
254 	auto read() const
255 	{
256 		import hdf5.hdf5;
257 
258 		ElementType!Data[] data;
259 		auto filespace = Dataspace(_dataset);
260 
261 		// get current size
262 		auto offset = currShape().dup;
263 		// set offset to zero for all dimensions
264 		offset[] = 0;
265 		herr_t status;
266 
267 		setDynArrayDimensions(data, currShape());
268 		/*
269 		 * Define the memory space to read dataset.
270 		 */
271 		auto memspace = H5Screate_simple(rank, currShape().ptr, null);
272 		scope(exit) H5Sclose(memspace);
273 
274 		/*
275 		 * Read dataset
276 		 */
277 		status = H5Dread(_dataset, _data_spec.tid, memspace, filespace,
278 				 H5P_DEFAULT, data.ptr);
279 		assert(status >= 0);
280 
281 		return data;
282 	}
283 
284 	/*
285 	 * Read data from the dataset.
286 	 */
287 	auto read(ElementType!Data[] data) const
288 	{
289 		import hdf5.hdf5;
290 
291 		if (data.length < currShape[0])
292 			return null;
293 
294 		auto filespace = Dataspace(_dataset);
295 
296 		// get current size
297 		auto offset = currShape().dup;
298 		// set offset to zero for all dimensions
299 		offset[] = 0;
300 		herr_t status;
301 
302 		/*
303 		 * Define the memory space to read dataset.
304 		 */
305 		auto memspace = H5Screate_simple(rank, currShape().ptr, null);
306 		scope(exit) H5Sclose(memspace);
307 
308 		/*
309 		 * Read dataset
310 		 */
311 		status = H5Dread(_dataset, _data_spec.tid, memspace, filespace,
312 				 H5P_DEFAULT, data.ptr);
313 		assert(status >= 0);
314 
315 		return data;
316 	}
317 
318 	/**
319 	 * Read count data from file starting with offset
320 	 */
321 	auto read(hsize_t[] offset, hsize_t[] count) const
322 	{
323 		import hdf5.hdf5 : H5Sselect_hyperslab, H5Screate_simple, H5Dread, H5Sclose,
324 			H5S_seloper_t, H5P_DEFAULT;
325 
326 		//assert((offset+count) <= _max_shape[0]);
327 		/*
328 		* get the file dataspace.
329 		*/
330 		auto dataspace = Dataspace (_dataset); // dataspace identifier
331 
332 		auto status = H5Sselect_hyperslab (dataspace, H5S_seloper_t.H5S_SELECT_SET, offset.ptr, null, count.ptr, null);
333 		assert(status >= 0);
334 		/*
335 		* Define memory dataspace.
336 		*/
337 		auto mem_offset = offset;
338 		mem_offset[] = 0;
339 		auto mem_count  = count;
340 		auto dimsm = mem_count;
341 		auto memspace = H5Screate_simple (rank, dimsm.ptr, null);
342 		scope(exit) H5Sclose (memspace);
343 
344 		/*
345 		* Define memory hyperslab.
346 		*/
347 		status = H5Sselect_hyperslab (memspace, H5S_seloper_t.H5S_SELECT_SET, mem_offset.ptr, null, mem_count.ptr, null);
348 		assert(status >=0);
349 
350 		ElementType!Data[] data;
351 		setDynArrayDimensions (data, count);
352 
353 		status = H5Dread (_dataset, _data_spec.tid, memspace, dataspace, H5P_DEFAULT, data.ptr);
354 		assert(status >= 0);
355 		return data;
356 	}
357 
358 	void write(Range, Args...)(Range range, Args args)
359 		if (isImplicitlyConvertible!(ElementType!Range, ElementType!Data) &&
360 			!is(Range == ElementType!(Data)[]))
361 	{
362 		import std.container : Array;
363 
364 		auto buffer = Array!(ElementType!Data)(range);
365 
366 		write((&buffer[0])[0..buffer.length], args);
367 	}
368 
369 	/*
370 	 * Write data to the dataset
371 	 */
372 	auto write(ElementType!Data[] data)
373 	{
374 		hsize_t[rank] offset;
375 		offset[] = 0;
376 		write(data, offset[]);
377 	}
378 
379 	/*
380 	 * Write data to the dataset;
381 	 */
382 	auto write(ElementType!Data[] data, const(hsize_t)[] offset)
383 	{
384 		import std.exception : enforce;
385 		enforce (data.length+offset[0] <= currShape[0], "Bounds checking failed!");
386 
387 		import hdf5.hdf5 : herr_t, H5Sselect_hyperslab, H5Screate_simple, H5Dwrite,
388 			H5S_seloper_t, H5P_DEFAULT, H5Sclose;
389 
390 		assert(offset.length == rank);
391 		hsize_t[rank] count = [data.length];
392 		assert(  count.length == rank);
393 		herr_t status;
394 
395 		auto filespace = Dataspace (_dataset);
396 		status = H5Sselect_hyperslab (filespace, H5S_seloper_t.H5S_SELECT_SET, offset.ptr, null, count.ptr, null);
397 		assert(status >= 0);
398 
399 		auto memspace = H5Screate_simple (rank, count.ptr, null);
400 		scope(exit) H5Sclose (memspace);
401 
402 		status = H5Dwrite (_dataset, _data_spec.tid, memspace, filespace, H5P_DEFAULT, data.ptr);
403 		assert(status >= 0);
404 	}
405 
406 	auto remove(hsize_t idx)
407 	{
408 		auto l = currShape[0];
409 		if (idx != l-1)
410 		{
411 			auto buffer = read([idx+1], [currShape[0]-idx-1]);
412 			write(buffer, [idx]);
413 		}
414 		currShape = [l - 1];
415 	}
416 
417 	auto remove(IndexRange)(IndexRange index_range)
418 	{
419 
420 	}
421 
422 	Range opSlice()
423 	{
424 		return Range(this, 0, currShape[0]);
425 	}
426 
427 	auto add(ElementType!Data element)
428 	{
429 		// set new shape
430 		auto last_index = currShape[0];
431 		currShape = [last_index+1];
432 		write([element], [last_index]);
433 	}
434 
435 	auto add(Range)(Range range)
436 		if (is(ElementType!Range : ElementType!Data) && hasLength!Range)
437 	{
438 		// set new shape
439 		auto last_index = currShape[0];
440 		currShape = [last_index+range.length];
441 		write(range, [last_index]);
442 	}
443 
444 	struct Range
445 	{
446 		// index of starting element + element count of the range
447 		private
448 		{
449 			size_t _start, _length;
450 			Dataset* _dataset;
451 		}
452 
453 		@disable
454 		this();
455 
456 		this(ref Dataset dataset, size_t start, size_t length)
457 		{
458 			_dataset = &dataset;
459 			_start = start;
460 			_length = length;
461 		}
462 
463 		bool empty() const
464 		{
465 			return _length == 0;
466 		}
467 
468 		ref ElementType!Data front() const
469 		{
470 			return (*_dataset)[_start];
471 		}
472 
473 		ref ElementType!Data back() const
474 		{
475 			return (*_dataset)[_start+_length-1];
476 		}
477 
478 		void popFront()
479 		{
480 			import std.exception : enforce;
481 
482 			enforce (!empty);
483 
484 			_start++;
485 			_length--;
486 		}
487 
488 		void popBack()
489 		{
490 			import std.exception : enforce;
491 
492 			enforce (!empty);
493 
494 			_length--;
495 		}
496 
497 		auto save() const
498 		{
499 			return this;
500 		}
501 
502 		ref auto opIndex(size_t index) const
503 		{
504 			return (*_dataset)[index];
505 		}
506 
507 		size_t length() const
508 		{
509 			return _length;
510 		}
511 	}
512 
513 	ref ElementType!Data opIndex(size_t index) const
514 	{
515 		if (index >= currShape[0])
516 			throw new Exception("Bounds");
517 
518 		return read([index], [1])[0];
519 	}
520 
521 	auto opIndexAssign(ElementType!Data element, size_t index)
522 	{
523 		write([element], [index]);
524 	}
525 
526 	auto opOpAssign(string op)(ElementType!Data rhs)
527 	{
528 		static if (op == "~")
529 		{
530 			add(rhs);
531 		}
532 	}
533 
534 	auto opOpAssign(string op, Range)(Range r)
535 	{
536 		static if (op == "~")
537 		{
538 			add(r);
539 		}
540 	}
541 
542 	auto tid()
543 	{
544 		return _dataset;
545 	}
546 
547 private:
548 	hid_t _dataset = -1;
549 	DataSpecType _data_spec;
550 	// Current shape of dataset
551 	hsize_t[rank] _curr_shape;
552 	// Maximal shape of dataset
553 	hsize_t[rank] _max_shape;
554 }