1 module fuzzycopy;
2 
3 import std.algorithm.iteration : map;
4 import std.array : array, empty;
5 import std.conv : to;
6 import std.format : format;
7 import std.range : ElementEncodingType;
8 import std.traits : isFunction, hasMember, isImplicitlyConvertible,
9 	isIntegral, isFloatingPoint, isArray, isSomeString, FieldNameTuple;
10 import std.typecons : Nullable, nullable;
11 import std.stdio;
12 
13 template isNullable(T) {
14 	enum isNullable = is(T : Nullable!F, F);
15 }
16 
17 template Type(T) {
18 	static if(is(T : Nullable!F, F)) {
19 		alias Type = F;
20 	} else {
21 		alias Type = T;
22 	}
23 }
24 
25 template extractBaseType(T) {
26 	static if(is(T : Nullable!F, F)) {
27 		alias extractBaseType = .extractBaseType!F;
28 	} else static if(isArray!T && !isSomeString!T) {
29 		alias extractBaseType = .extractBaseType!(ElementEncodingType!T);
30 	} else {
31 		alias extractBaseType = T;
32 	}
33 }
34 
35 unittest {
36 	static assert(is(extractBaseType!float == float));
37 	static assert(is(extractBaseType!(Nullable!float) == float));
38 	static assert(is(extractBaseType!(Nullable!(float[])) == float));
39 	static assert(is(extractBaseType!(Nullable!(Nullable!float[])) == float));
40 	static assert(is(extractBaseType!string == string));
41 	static assert(is(extractBaseType!(Nullable!string) == string));
42 	static assert(is(extractBaseType!(Nullable!(string[])) == string));
43 	static assert(is(extractBaseType!(Nullable!(Nullable!string[])) == string));
44 }
45 
46 template arrayDepth(T) {
47 	static if(isArray!T && !isSomeString!T) {
48 		enum arrayDepth = 1 + .arrayDepth!(ElementEncodingType!T);
49 	} else static if(is(T : Nullable!F, F)) {
50 		enum arrayDepth = .arrayDepth!F;
51 	} else {
52 		enum arrayDepth = 0;
53 	}
54 }
55 
56 unittest {
57 	static assert(arrayDepth!(int[]) == 1);
58 	static assert(arrayDepth!(int) == 0);
59 	static assert(arrayDepth!(int[][]) == 2);
60 	static assert(arrayDepth!(Nullable!(int[])[]) == 2);
61 }
62 
63 template StripNullable(T) {
64 	static if(is(T : Nullable!F, F)) {
65 		alias StripNullable = .StripNullable!F;
66 	} else static if(isArray!T && !isSomeString!T) {
67 		alias StripNullable = .StripNullable!(ElementEncodingType!T)[];
68 	} else {
69 		alias StripNullable = T;
70 	}
71 }
72 
73 unittest {
74 	static assert(is(StripNullable!(int) == int));
75 	static assert(is(StripNullable!(int[]) == int[]));
76 	static assert(is(StripNullable!(Nullable!int[]) == int[]));
77 	static assert(is(StripNullable!(Nullable!(int[])) == int[]));
78 	static assert(is(StripNullable!(Nullable!(Nullable!int[])) == int[]));
79 
80 	struct Foo {
81 	}
82 	static assert(is(StripNullable!(Foo) == Foo));
83 	static assert(is(StripNullable!(Foo[]) == Foo[]));
84 	static assert(is(StripNullable!(Nullable!Foo[]) == Foo[]));
85 	static assert(is(StripNullable!(Nullable!(Foo[])) == Foo[]));
86 	static assert(is(StripNullable!(Nullable!(Nullable!Foo[])) == Foo[]));
87 }
88 
89 template StripTopNullable(T) {
90 	static if(is(T : Nullable!F, F)) {
91 		alias StripTopNullable = F;
92 	} else {
93 		alias StripTopNullable = T;
94 	}
95 }
96 
97 unittest {
98 	static assert(is(StripTopNullable!(int) == int));
99 	static assert(is(StripTopNullable!(int[]) == int[]));
100 	static assert(is(StripTopNullable!(Nullable!(int[])) == int[]));
101 	static assert(is(StripTopNullable!(Nullable!(int[])) == int[]));
102 	static assert(is(StripTopNullable!(Nullable!(Nullable!int[])) == Nullable!(int)[]));
103 }
104 
105 template canBeConvertedWithTo(F,T) {
106 	alias FT = extractBaseType!F;
107 	alias TT = extractBaseType!T;
108 	enum FD = arrayDepth!F;
109 	enum TD = arrayDepth!T;
110 
111 	static if(isIntegral!FT && isIntegral!TT && FD == TD && FD == 0) {
112 		enum canBeConvertedWithTo = true;
113 	} else static if(isIntegral!FT && isFloatingPoint!TT 
114 			&& FD == TD && FD == 0) 
115 	{
116 		enum canBeConvertedWithTo = true;
117 	} else static if(isFloatingPoint!FT && isIntegral!TT && FD == TD 
118 			&& FD == 0) 
119 	{
120 		enum canBeConvertedWithTo = true;
121 	} else static if(isSomeString!FT && isSomeString!TT && FD == TD 
122 			&& FD == 0) 
123 	{
124 		enum canBeConvertedWithTo = true;
125 	} else {
126 		enum canBeConvertedWithTo = false;
127 	}
128 }
129 
130 unittest {
131 	static assert(canBeConvertedWithTo!(int,byte));
132 	static assert(canBeConvertedWithTo!(byte,float));
133 	static assert(!canBeConvertedWithTo!(string,float));
134 	static assert(!canBeConvertedWithTo!(int[],float));
135 	static assert(!canBeConvertedWithTo!(int[],float[]));
136 	static assert(!canBeConvertedWithTo!(byte[],double[]));
137 	static assert(!canBeConvertedWithTo!(double[],ubyte[]));
138 }
139 
140 auto getValue(TnN,F,string mem)(ref F f) {
141 	alias FMem = typeof(__traits(getMember, F, mem));
142 	enum FiN = isNullable!FMem;
143 
144 	struct Result {
145 		Type!FMem value;
146 		bool isNull;
147 	}
148 
149 	Result ret;
150 
151 	static if(FiN) {
152 		if(__traits(getMember, f, mem).isNull()) {
153 			ret.isNull = true;
154 		} else {
155 			ret.value = __traits(getMember, f, mem).get();
156 			ret.isNull = false;
157 		}
158 	} else {
159 		ret.value = __traits(getMember, f, mem);
160 		ret.isNull = false;
161 	}
162 	return ret;
163 }
164 
165 auto getValue(T)(auto ref T t) {
166 	alias TnN = StripTopNullable!T;
167 	struct Ret {
168 		TnN value;
169 		bool isNull;
170 	}
171 	Ret ret;
172 
173 	static if(isNullable!T) {
174 		if(t.isNull()) {
175 			ret.isNull = true;
176 		} else {
177 			ret.isNull = false;
178 			ret.value = t.get();
179 		}
180 	} else {
181 		ret.isNull = false;
182 		ret.value = t;
183 	}
184 	return ret;
185 }
186 
187 void fuzzyCP(F,T)(auto ref F f, auto ref T t) {
188 	outer: foreach(mem; FieldNameTuple!F) {
189 		static if(__traits(hasMember, T, mem)) {
190 			alias FMem = typeof(__traits(getMember, F, mem));
191 			alias TMem = typeof(__traits(getMember, T, mem));
192 			alias FnN = StripNullable!FMem;
193 			alias TnN = StripNullable!TMem;
194 
195 			enum FiS = is(FnN == struct);
196 			enum TiS = is(TnN == struct);
197 
198 			enum FiN = isNullable!FMem;
199 			enum TiN = isNullable!TMem;
200 
201 			enum FiA = isArray!FnN;
202 			enum TiA = isArray!TnN;
203 
204 			enum Fd = arrayDepth!FnN;
205 			enum Td = arrayDepth!TnN;
206 
207 			static if(FiS && TiS) { // two struct
208 				fuzzyCP(__traits(getMember, f, mem), 
209 						__traits(getMember, t, mem)
210 					);
211 			} else static if(is(FMem == TMem)) { // same types
212 				__traits(getMember, t, mem) = __traits(getMember, f, mem);
213 			} else static if(FiA && TiA && Fd == Td && Fd == 1) {
214 				alias Tet = ElementEncodingType!(StripTopNullable!(TMem));
215 				auto arr = getValue!(TnN,F,mem)(f);
216 
217 				Tet[] tmp;
218 				if(!arr.isNull) {
219 					foreach(it; arr.value) {
220 						auto itV = getValue(it);
221 						if(!itV.isNull) {
222 							static if(isNullable!Tet) {
223 								tmp ~= nullable(itV.value);
224 							} else {
225 								tmp ~= itV.value;
226 							}
227 						}
228 					}
229 				}
230 				static if(isNullable!(TMem)) {
231 					if(!tmp.empty) {
232 						__traits(getMember, t, mem) = nullable(tmp);
233 					}
234 				} else {
235 					if(!tmp.empty) {
236 						__traits(getMember, t, mem) = tmp;
237 					}
238 				}
239 			} else static if(canBeConvertedWithTo!(FnN,TnN)) { // using to!
240 				auto val = getValue!(TnN,F,mem)(f);
241 				if(!val.isNull) {
242 					static if(TiN) {
243 						__traits(getMember, t, mem) = nullable(to!TnN(val.value));
244 					} else {
245 						//writefln("%s %s", mem, fVal);
246 						__traits(getMember, t, mem) = to!TMem(val.value);
247 					}
248 				}
249 			}
250 		}
251 	}
252 }
253 
254 unittest {
255 	struct Foo {
256 		int a = 10;
257 		int b = 20;
258 	}
259 
260 	struct Bar {
261 		int a;
262 		int b;
263 	}
264 
265 	Foo foo;
266 	assert(foo.a == 10);
267 	assert(foo.b == 20);
268 
269 	Bar bar;
270 
271 	fuzzyCP(foo, bar);
272 
273 	assert(bar.a == 10);
274 	assert(bar.b == 20);
275 }
276 
277 unittest {
278 	struct Foo {
279 		int a = 99;
280 		int b = 77;
281 	}
282 
283 	struct Bar {
284 		Foo foo;
285 		string s = "hello";
286 		wstring r = "world";
287 	}
288 
289 	struct NotFoo {
290 		long a;
291 		long b;
292 	}
293 
294 	struct NotBar {
295 		NotFoo foo;
296 		string s;
297 		dstring r;
298 	}
299 
300 	Bar bar;
301 	NotBar nBar;
302 
303 	fuzzyCP(bar, nBar);
304 
305 	assert(nBar.foo.a == 99);
306 	assert(nBar.foo.b == 77);
307 	assert(nBar.s == "hello", format("'%s'", nBar.s));
308 	assert(nBar.r == "world", format("'%s'", nBar.r));
309 }
310 
311 unittest {
312 	struct Foo {
313 		int a = 99;
314 		Nullable!int b = 77;
315 		int c = 66;
316 	}
317 
318 	struct NotFoo {
319 		long a;
320 		long b;
321 		Nullable!byte c;
322 	}
323 
324 	Foo foo;
325 	NotFoo nFoo;
326 
327 	fuzzyCP(foo, nFoo);
328 
329 	assert(nFoo.a == 99);
330 	assert(nFoo.b == 77, format("%s", nFoo.b));
331 	assert(nFoo.c.get() == 66);
332 }
333 
334 unittest {
335 	struct Foo {
336 		int a = 99;
337 		Nullable!int b = 77;
338 		int c = 66;
339 	}
340 
341 	struct Bar {
342 		Foo[] foos;
343 	}
344 
345 	struct NotFoo {
346 		long a;
347 		long b;
348 		Nullable!byte c;
349 	}
350 
351 	struct NotBar {
352 		Foo[] foos;
353 	}
354 
355 	Bar bar;
356 	bar.foos = [Foo.init, Foo.init];
357 	NotBar nBar;
358 
359 	fuzzyCP(bar, nBar);
360 
361 	assert(nBar.foos.length == 2, format("%s", nBar.foos.length));
362 }
363 
364 unittest {
365 	struct Foo {
366 		Nullable!(int)[] i;
367 	}
368 
369 	struct Bar {
370 		int[] i;
371 	}
372 
373 	Foo f;
374 	f.i = [nullable(1), nullable(2), Nullable!(int).init];
375 
376 	Bar b;
377 	fuzzyCP(f, b);
378 	assert(b.i.length == 2);
379 	assert(b.i == [1, 2]);
380 }
381 
382 unittest {
383 	struct Foo {
384 		Nullable!(int)[] i;
385 	}
386 
387 	struct Bar {
388 		Nullable!(int[]) i;
389 	}
390 
391 	Foo f;
392 	f.i = [nullable(1), nullable(2), Nullable!(int).init];
393 
394 	Bar b;
395 	fuzzyCP(f, b);
396 	assert(!b.i.isNull());
397 	assert(b.i.get().length == 2);
398 	assert(b.i.get() == [1, 2]);
399 }
400 
401 unittest {
402 	struct Foo {
403 		Nullable!(int)[] i;
404 	}
405 
406 	struct Bar {
407 		int[] i;
408 	}
409 
410 	Foo f;
411 
412 	Bar b;
413 	fuzzyCP(f, b);
414 	assert(b.i.length == 0);
415 }
416 
417 unittest {
418 	struct Foo {
419 		Nullable!(Nullable!(int)[]) i;
420 	}
421 
422 	struct Bar {
423 		Nullable!(int[]) i;
424 	}
425 
426 	Foo f;
427 	f.i = [nullable(1), nullable(2), Nullable!(int).init];
428 
429 	Bar b;
430 	fuzzyCP(f, b);
431 	assert(!b.i.isNull());
432 	assert(b.i.get().length == 2);
433 	assert(b.i.get() == [1, 2]);
434 }
435 
436 template CanBeConverted(F,T) {
437 	alias FnN = StripTopNullable!F;
438 	alias TnN = StripTopNullable!T;
439 
440 	static if(is(FnN == TnN)) {
441 		enum CanBeConverted = true;
442 	} else static if(is(FnN == struct) && is(TnN == struct)) {
443 		enum CanBeConverted = true;
444 	} else static if(isSomeString!FnN && isSomeString!TnN) {
445 		enum CanBeConverted = true;
446 	} else static if(isArray!FnN && isArray!TnN) {
447 		enum CanBeConverted = .CanBeConverted!(
448 				ElementEncodingType!FnN,
449 				ElementEncodingType!TnN);
450 	} else static if((isIntegral!FnN || isFloatingPoint!FnN)
451 			|| (isIntegral!TnN || isFloatingPoint!TnN))
452 	{
453 		enum CanBeConverted = true;
454 	} else {
455 		enum CanBeConverted = false;
456 	}
457 }
458 
459 auto wrap(T,Val)(Val v) {
460 	static if(is(T : Nullable!F, F)) {
461 		return nullable(v);
462 	} else {
463 		return v;
464 	}
465 }
466 
467 auto nullAccess(V)(V val) {
468 	struct Ret {
469 
470 	}
471 }
472 
473 T fuzzyTo(T,F)(F f) {
474 	T ret;
475 	foreach(mem; FieldNameTuple!F) {
476 		static if(__traits(hasMember, T, mem)) {
477 			alias FT = typeof(__traits(getMember, F, mem));
478 			alias TT = typeof(__traits(getMember, T, mem));
479 			static if(CanBeConverted!(FT, TT)) {
480 				alias FnN = StripTopNullable!FT;
481 				auto memVal = getValue(__traits(getMember, f, mem));
482 				if(memVal.isNull) {
483 					continue;
484 				}
485 
486 				alias TnN = StripTopNullable!TT;
487 
488 				static if(canBeConvertedWithTo!(FnN,TnN)) {
489 					__traits(getMember, ret, mem) = wrap!TT(
490 							to!TnN(memVal.value)
491 						);
492 				} else static if(is(FnN == struct)) {
493 					__traits(getMember, ret, mem) = wrap!TT(
494 							fuzzyTo!TnN(memVal.value)
495 						);
496 				} else static if(isArray!FnN) {
497 					alias TET = ElementEncodingType!TnN;
498 					alias TETNN = StripTopNullable!TET;
499 					TET[] arr;
500 					foreach(it; memVal.value) {
501 						auto itVal = getValue(it);
502 						if(!itVal.isNull) {
503 							static if(canBeConvertedWithTo!(typeof(itVal.value),TETNN))
504 							{
505 								arr ~= wrap!TET(to!TETNN(itVal.value));
506 							} else {
507 								arr ~= wrap!TET(fuzzyTo!TETNN(itVal.value));
508 							}
509 						}
510 					}
511 					static if(isNullable!TT) {
512 						__traits(getMember, ret, mem) = nullable(arr);
513 					} else {
514 						__traits(getMember, ret, mem) = arr;
515 					}
516 				}
517 			}
518 		}
519 	}
520 
521 	return ret;
522 }
523 
524 unittest {
525 	struct F {
526 		int a = 13;
527 	}
528 
529 	struct T {
530 		byte a;
531 	}
532 
533 	F f;
534 	T t = fuzzyTo!T(f);
535 	assert(t.a == 13);
536 }
537 
538 unittest {
539 	struct F {
540 		int a = 13;
541 		float[] f = [1,2,3];
542 	}
543 
544 	struct T {
545 		byte a;
546 		byte[] f;
547 	}
548 
549 	F f;
550 	T t = fuzzyTo!T(f);
551 	assert(t.a == 13);
552 	assert(t.f == [1,2,3]);
553 }
554 
555 unittest {
556 	struct F {
557 		int a = 13;
558 		Nullable!(float)[] f = [nullable(1.0),nullable(2.0),nullable(3.0)];
559 	}
560 
561 	struct T {
562 		byte a;
563 		byte[] f;
564 	}
565 
566 	F f;
567 	T t = fuzzyTo!T(f);
568 	assert(t.a == 13);
569 	assert(t.f == [1,2,3]);
570 }
571 
572 unittest {
573 	struct F {
574 		int a = 13;
575 		Nullable!(float)[] f = [nullable(1.0),nullable(2.0),nullable(3.0)];
576 	}
577 
578 	struct T {
579 		byte a;
580 		Nullable!(byte)[] f;
581 	}
582 
583 	F f;
584 	T t = fuzzyTo!T(f);
585 	assert(t.a == 13);
586 	assert(t.f == [1,2,3]);
587 }
588 
589 unittest {
590 	struct F {
591 		int a = 13;
592 		Nullable!(Nullable!(double)[]) f;
593 	}
594 
595 	struct T {
596 		byte a;
597 		Nullable!(byte)[] f;
598 	}
599 
600 	F f;
601 	f.f = nullable([nullable(1.0),nullable(2.0),nullable(3.0)]);
602 	T t = fuzzyTo!T(f);
603 	assert(t.a == 13);
604 	assert(t.f == [1,2,3]);
605 }
606 
607 unittest {
608 	struct F {
609 		int a = 13;
610 		Nullable!(Nullable!(double)[]) f;
611 	}
612 
613 	struct T {
614 		byte a;
615 		Nullable!(byte[]) f;
616 	}
617 
618 	F f;
619 	f.f = nullable([nullable(1.0),nullable(2.0),nullable(3.0)]);
620 	T t = fuzzyTo!T(f);
621 	assert(t.a == 13);
622 	assert(t.f == [1,2,3]);
623 }
624 
625 unittest {
626 	struct F {
627 		int a = 13;
628 		Nullable!(Nullable!(double)[]) f;
629 	}
630 
631 	struct T {
632 		byte a;
633 		Nullable!(Nullable!byte[]) f;
634 	}
635 
636 	F f;
637 	f.f = nullable([nullable(1.0),Nullable!(double).init, nullable(2.0),nullable(3.0)]);
638 	T t = fuzzyTo!T(f);
639 	assert(t.a == 13);
640 	assert(t.f == [1,2,3]);
641 }
642 
643 unittest {
644 	struct A {
645 		float a = 10;
646 	}
647 
648 	struct F {
649 		A a;
650 	}
651 
652 	struct B {
653 		int a;
654 	}
655 
656 	struct T {
657 		B a;
658 	}
659 
660 	F f;
661 	T t = fuzzyTo!T(f);
662 	assert(t.a.a == 10);
663 }
664 
665 unittest {
666 	struct A {
667 		float a = 10;
668 	}
669 
670 	struct F {
671 		Nullable!A a = nullable(A.init);
672 	}
673 
674 	struct B {
675 		int a;
676 	}
677 
678 	struct T {
679 		B a;
680 	}
681 
682 	F f;
683 	T t = fuzzyTo!T(f);
684 	assert(t.a.a == 10);
685 }
686 
687 unittest {
688 	struct A {
689 		Nullable!float a = nullable(10.0);
690 	}
691 
692 	struct F {
693 		Nullable!A a = nullable(A.init);
694 	}
695 
696 	struct B {
697 		int a;
698 	}
699 
700 	struct T {
701 		B a;
702 	}
703 
704 	F f;
705 	T t = fuzzyTo!T(f);
706 	assert(t.a.a == 10);
707 }
708 
709 unittest {
710 	struct A {
711 		Nullable!float a = nullable(10.0);
712 	}
713 
714 	struct F {
715 		Nullable!(A[]) a = nullable([A.init, A.init]);
716 	}
717 
718 	struct B {
719 		int a;
720 	}
721 
722 	struct T {
723 		B[] a;
724 	}
725 
726 	F f;
727 	T t = fuzzyTo!T(f);
728 	assert(t.a.map!(it => it.a).array == [10, 10]);
729 }
730 
731 unittest {
732 	struct A {
733 		Nullable!float a;
734 	}
735 
736 	struct F {
737 		Nullable!(A[]) a = nullable([A.init, A.init]);
738 		Nullable!(A[]) b;
739 	}
740 
741 	struct B {
742 		int a;
743 	}
744 
745 	struct T {
746 		B[] a;
747 		B[] b;
748 	}
749 
750 	F f;
751 	T t = fuzzyTo!T(f);
752 	assert(t.a.length == 2);
753 	assert(t.b.empty);
754 }