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 	static if(is(F == T)) {
189 		t = f;
190 	} else {
191 	outer: foreach(mem; FieldNameTuple!F) {
192 		static if(__traits(hasMember, T, mem)) {
193 			alias FMem = typeof(__traits(getMember, F, mem));
194 			alias TMem = typeof(__traits(getMember, T, mem));
195 			alias FnN = StripNullable!FMem;
196 			alias TnN = StripNullable!TMem;
197 
198 			enum FiS = is(FnN == struct);
199 			enum TiS = is(TnN == struct);
200 
201 			enum FiN = isNullable!FMem;
202 			enum TiN = isNullable!TMem;
203 
204 			enum FiA = isArray!FnN;
205 			enum TiA = isArray!TnN;
206 
207 			enum Fd = arrayDepth!FnN;
208 			enum Td = arrayDepth!TnN;
209 
210 			static if(FiS && TiS) { // two struct
211 				fuzzyCP(__traits(getMember, f, mem), 
212 						__traits(getMember, t, mem)
213 					);
214 			} else static if(is(FMem == TMem)) { // same types
215 				__traits(getMember, t, mem) = __traits(getMember, f, mem);
216 			} else static if(FiA && TiA && Fd == Td && Fd == 1) {
217 				alias Tet = ElementEncodingType!(StripTopNullable!(TMem));
218 				auto arr = getValue!(TnN,F,mem)(f);
219 
220 				Tet[] tmp;
221 				if(!arr.isNull) {
222 					foreach(it; arr.value) {
223 						auto itV = getValue(it);
224 						if(!itV.isNull) {
225 							static if(isNullable!Tet) {
226 								tmp ~= nullable(itV.value);
227 							} else {
228 								tmp ~= itV.value;
229 							}
230 						}
231 					}
232 				}
233 				static if(isNullable!(TMem)) {
234 					if(!tmp.empty) {
235 						__traits(getMember, t, mem) = nullable(tmp);
236 					}
237 				} else {
238 					if(!tmp.empty) {
239 						__traits(getMember, t, mem) = tmp;
240 					}
241 				}
242 			} else static if(canBeConvertedWithTo!(FnN,TnN)) { // using to!
243 				auto val = getValue!(TnN,F,mem)(f);
244 				if(!val.isNull) {
245 					static if(TiN) {
246 						__traits(getMember, t, mem) = nullable(to!TnN(val.value));
247 					} else {
248 						//writefln("%s %s", mem, fVal);
249 						__traits(getMember, t, mem) = to!TMem(val.value);
250 					}
251 				}
252 			}
253 		}
254 	}
255 	}
256 }
257 
258 unittest {
259 	struct Foo {
260 		int a = 10;
261 		int b = 20;
262 	}
263 
264 	struct Bar {
265 		int a;
266 		int b;
267 	}
268 
269 	Foo foo;
270 	assert(foo.a == 10);
271 	assert(foo.b == 20);
272 
273 	Bar bar;
274 
275 	fuzzyCP(foo, bar);
276 
277 	assert(bar.a == 10);
278 	assert(bar.b == 20);
279 }
280 
281 unittest {
282 	struct Foo {
283 		int a = 99;
284 		int b = 77;
285 	}
286 
287 	struct Bar {
288 		Foo foo;
289 		string s = "hello";
290 		wstring r = "world";
291 	}
292 
293 	struct NotFoo {
294 		long a;
295 		long b;
296 	}
297 
298 	struct NotBar {
299 		NotFoo foo;
300 		string s;
301 		dstring r;
302 	}
303 
304 	Bar bar;
305 	NotBar nBar;
306 
307 	fuzzyCP(bar, nBar);
308 
309 	assert(nBar.foo.a == 99);
310 	assert(nBar.foo.b == 77);
311 	assert(nBar.s == "hello", format("'%s'", nBar.s));
312 	assert(nBar.r == "world", format("'%s'", nBar.r));
313 }
314 
315 unittest {
316 	struct Foo {
317 		int a = 99;
318 		Nullable!int b = 77;
319 		int c = 66;
320 	}
321 
322 	struct NotFoo {
323 		long a;
324 		long b;
325 		Nullable!byte c;
326 	}
327 
328 	Foo foo;
329 	NotFoo nFoo;
330 
331 	fuzzyCP(foo, nFoo);
332 
333 	assert(nFoo.a == 99);
334 	assert(nFoo.b == 77, format("%s", nFoo.b));
335 	assert(nFoo.c.get() == 66);
336 }
337 
338 unittest {
339 	struct Foo {
340 		int a = 99;
341 		Nullable!int b = 77;
342 		int c = 66;
343 		int d;
344 	}
345 
346 	struct Bar {
347 		Foo[] foos;
348 	}
349 
350 	struct NotFoo {
351 		long a;
352 		long b;
353 		Nullable!byte c;
354 	}
355 
356 	struct NotBar {
357 		Foo[] foos;
358 	}
359 
360 	Bar bar;
361 	bar.foos = [Foo.init, Foo.init];
362 	NotBar nBar;
363 
364 	fuzzyCP(bar, nBar);
365 
366 	assert(nBar.foos.length == 2, format("%s", nBar.foos.length));
367 }
368 
369 unittest {
370 	struct Foo {
371 		Nullable!(int)[] i;
372 	}
373 
374 	struct Bar {
375 		int[] i;
376 	}
377 
378 	Foo f;
379 	f.i = [nullable(1), nullable(2), Nullable!(int).init];
380 
381 	Bar b;
382 	fuzzyCP(f, b);
383 	assert(b.i.length == 2);
384 	assert(b.i == [1, 2]);
385 }
386 
387 unittest {
388 	struct Foo {
389 		Nullable!(int)[] i;
390 	}
391 
392 	struct Bar {
393 		Nullable!(int[]) i;
394 	}
395 
396 	Foo f;
397 	f.i = [nullable(1), nullable(2), Nullable!(int).init];
398 
399 	Bar b;
400 	fuzzyCP(f, b);
401 	assert(!b.i.isNull());
402 	assert(b.i.get().length == 2);
403 	assert(b.i.get() == [1, 2]);
404 }
405 
406 unittest {
407 	struct Foo {
408 		Nullable!(int)[] i;
409 	}
410 
411 	struct Bar {
412 		int[] i;
413 	}
414 
415 	Foo f;
416 
417 	Bar b;
418 	fuzzyCP(f, b);
419 	assert(b.i.length == 0);
420 }
421 
422 unittest {
423 	struct Foo {
424 		Nullable!(Nullable!(int)[]) i;
425 	}
426 
427 	struct Bar {
428 		Nullable!(int[]) i;
429 	}
430 
431 	Foo f;
432 	f.i = [nullable(1), nullable(2), Nullable!(int).init];
433 
434 	Bar b;
435 	fuzzyCP(f, b);
436 	assert(!b.i.isNull());
437 	assert(b.i.get().length == 2);
438 	assert(b.i.get() == [1, 2]);
439 }
440 
441 template CanBeConverted(F,T) {
442 	alias FnN = StripTopNullable!F;
443 	alias TnN = StripTopNullable!T;
444 
445 	static if(is(FnN == TnN)) {
446 		enum CanBeConverted = true;
447 	} else static if(is(FnN == struct) && is(TnN == struct)) {
448 		enum CanBeConverted = true;
449 	} else static if(isSomeString!FnN && isSomeString!TnN) {
450 		enum CanBeConverted = true;
451 	} else static if(isArray!FnN && isArray!TnN) {
452 		enum CanBeConverted = .CanBeConverted!(
453 				ElementEncodingType!FnN,
454 				ElementEncodingType!TnN);
455 	} else static if((isIntegral!FnN || isFloatingPoint!FnN)
456 			|| (isIntegral!TnN || isFloatingPoint!TnN))
457 	{
458 		enum CanBeConverted = true;
459 	} else {
460 		enum CanBeConverted = false;
461 	}
462 }
463 
464 auto wrap(T,Val)(Val v) {
465 	static if(is(T : Nullable!F, F)) {
466 		return nullable(v);
467 	} else {
468 		return v;
469 	}
470 }
471 
472 auto nullAccess(V)(V val) {
473 	struct Ret {
474 
475 	}
476 }
477 
478 T fuzzyTo(T,F)(F f) {
479 	static if(is(T == F)) {
480 		return f;
481 	} else {
482 	T ret;
483 	foreach(mem; FieldNameTuple!F) {
484 		static if(__traits(hasMember, T, mem)) {
485 			alias FT = typeof(__traits(getMember, F, mem));
486 			alias TT = typeof(__traits(getMember, T, mem));
487 			static if(CanBeConverted!(FT, TT)) {
488 				alias FnN = StripTopNullable!FT;
489 				auto memVal = getValue(__traits(getMember, f, mem));
490 				if(memVal.isNull) {
491 					continue;
492 				}
493 
494 				alias TnN = StripTopNullable!TT;
495 
496 				static if(canBeConvertedWithTo!(FnN,TnN)) {
497 					__traits(getMember, ret, mem) = wrap!TT(
498 							to!TnN(memVal.value)
499 						);
500 				} else static if(is(FnN == struct)) {
501 					__traits(getMember, ret, mem) = wrap!TT(
502 							fuzzyTo!TnN(memVal.value)
503 						);
504 				} else static if(isArray!FnN) {
505 					alias TET = ElementEncodingType!TnN;
506 					alias TETNN = StripTopNullable!TET;
507 					TET[] arr;
508 					foreach(it; memVal.value) {
509 						auto itVal = getValue(it);
510 						if(!itVal.isNull) {
511 							static if(canBeConvertedWithTo!(typeof(itVal.value),TETNN))
512 							{
513 								arr ~= wrap!TET(to!TETNN(itVal.value));
514 							} else {
515 								arr ~= wrap!TET(fuzzyTo!TETNN(itVal.value));
516 							}
517 						}
518 					}
519 					static if(isNullable!TT) {
520 						__traits(getMember, ret, mem) = nullable(arr);
521 					} else {
522 						__traits(getMember, ret, mem) = arr;
523 					}
524 				}
525 			}
526 		}
527 	}
528 
529 	return ret;
530 	}
531 }
532 
533 unittest {
534 	struct F {
535 		int a = 13;
536 	}
537 
538 	struct T {
539 		byte a;
540 	}
541 
542 	F f;
543 	T t = fuzzyTo!T(f);
544 	assert(t.a == 13);
545 
546 	F f2 = fuzzyTo!F(f);
547 }
548 
549 unittest {
550 	struct F {
551 		int a = 13;
552 		float[] f = [1,2,3];
553 	}
554 
555 	struct T {
556 		byte a;
557 		byte[] f;
558 	}
559 
560 	F f;
561 	T t = fuzzyTo!T(f);
562 	assert(t.a == 13);
563 	assert(t.f == [1,2,3]);
564 }
565 
566 unittest {
567 	struct F {
568 		int a = 13;
569 		Nullable!(float)[] f = [nullable(1.0),nullable(2.0),nullable(3.0)];
570 	}
571 
572 	struct T {
573 		byte a;
574 		byte[] f;
575 	}
576 
577 	F f;
578 	T t = fuzzyTo!T(f);
579 	assert(t.a == 13);
580 	assert(t.f == [1,2,3]);
581 }
582 
583 unittest {
584 	struct F {
585 		int a = 13;
586 		Nullable!(float)[] f = [nullable(1.0),nullable(2.0),nullable(3.0)];
587 	}
588 
589 	struct T {
590 		byte a;
591 		Nullable!(byte)[] f;
592 	}
593 
594 	F f;
595 	T t = fuzzyTo!T(f);
596 	assert(t.a == 13);
597 	assert(t.f == [1,2,3]);
598 }
599 
600 unittest {
601 	struct F {
602 		int a = 13;
603 		Nullable!(Nullable!(double)[]) f;
604 	}
605 
606 	struct T {
607 		byte a;
608 		Nullable!(byte)[] f;
609 	}
610 
611 	F f;
612 	f.f = nullable([nullable(1.0),nullable(2.0),nullable(3.0)]);
613 	T t = fuzzyTo!T(f);
614 	assert(t.a == 13);
615 	assert(t.f == [1,2,3]);
616 }
617 
618 unittest {
619 	struct F {
620 		int a = 13;
621 		Nullable!(Nullable!(double)[]) f;
622 	}
623 
624 	struct T {
625 		byte a;
626 		Nullable!(byte[]) f;
627 	}
628 
629 	F f;
630 	f.f = nullable([nullable(1.0),nullable(2.0),nullable(3.0)]);
631 	T t = fuzzyTo!T(f);
632 	assert(t.a == 13);
633 	assert(t.f == [1,2,3]);
634 }
635 
636 unittest {
637 	struct F {
638 		int a = 13;
639 		Nullable!(Nullable!(double)[]) f;
640 	}
641 
642 	struct T {
643 		byte a;
644 		Nullable!(Nullable!byte[]) f;
645 	}
646 
647 	F f;
648 	f.f = nullable([nullable(1.0),Nullable!(double).init, nullable(2.0),nullable(3.0)]);
649 	T t = fuzzyTo!T(f);
650 	assert(t.a == 13);
651 	assert(t.f == [1,2,3]);
652 }
653 
654 unittest {
655 	struct A {
656 		float a = 10;
657 	}
658 
659 	struct F {
660 		A a;
661 	}
662 
663 	struct B {
664 		int a;
665 	}
666 
667 	struct T {
668 		B a;
669 	}
670 
671 	F f;
672 	T t = fuzzyTo!T(f);
673 	assert(t.a.a == 10);
674 }
675 
676 unittest {
677 	struct A {
678 		float a = 10;
679 	}
680 
681 	struct F {
682 		Nullable!A a = nullable(A.init);
683 	}
684 
685 	struct B {
686 		int a;
687 	}
688 
689 	struct T {
690 		B a;
691 	}
692 
693 	F f;
694 	T t = fuzzyTo!T(f);
695 	assert(t.a.a == 10);
696 }
697 
698 unittest {
699 	struct A {
700 		Nullable!float a = nullable(10.0);
701 	}
702 
703 	struct F {
704 		Nullable!A a = nullable(A.init);
705 	}
706 
707 	struct B {
708 		int a;
709 	}
710 
711 	struct T {
712 		B a;
713 	}
714 
715 	F f;
716 	T t = fuzzyTo!T(f);
717 	assert(t.a.a == 10);
718 }
719 
720 unittest {
721 	struct A {
722 		Nullable!float a = nullable(10.0);
723 	}
724 
725 	struct F {
726 		Nullable!(A[]) a = nullable([A.init, A.init]);
727 	}
728 
729 	struct B {
730 		int a;
731 	}
732 
733 	struct T {
734 		B[] a;
735 	}
736 
737 	F f;
738 	T t = fuzzyTo!T(f);
739 	assert(t.a.map!(it => it.a).array == [10, 10]);
740 }
741 
742 unittest {
743 	struct A {
744 		Nullable!float a;
745 	}
746 
747 	struct F {
748 		Nullable!(A[]) a = nullable([A.init, A.init]);
749 		Nullable!(A[]) b;
750 	}
751 
752 	struct B {
753 		int a;
754 	}
755 
756 	struct T {
757 		B[] a;
758 		B[] b;
759 	}
760 
761 	F f;
762 	T t = fuzzyTo!T(f);
763 	assert(t.a.length == 2);
764 	assert(t.b.empty);
765 }