1 module hunt.serialization.Specify;
2 
3 import std.traits;
4 import std.range;
5 
6 import hunt.serialization.Common;
7 import hunt.serialization.BinarySerializer;
8 import hunt.serialization.BinaryDeserializer;
9 
10 import hunt.logging;
11 
12 template PtrType(T) {
13     static if (is(T == bool) || is(T == char)) {
14         alias PtrType = ubyte*;
15     } else static if (is(T == float)) {
16         alias PtrType = uint*;
17     } else static if (is(T == double)) {
18         alias PtrType = ulong*;
19     } else {
20         alias PtrType = Unsigned!T*;
21     }
22 }
23 
24 enum ubyte[] NULL = ['n', 'u', 'l', 'l'];
25 
26 void specify(SerializationOptions options, C, T)(auto ref C obj, ref T val) if (is(T == wchar)) {
27     specify!(options)(obj, *cast(ushort*)&val);
28 }
29 
30 void specify(SerializationOptions options, C, T)(auto ref C obj, ref T val) if (is(T == dchar)) {
31     specify!(options)(obj, *cast(uint*)&val);
32 }
33 
34 void specify(SerializationOptions options, C, T)(auto ref C obj, ref T val) if (is(T == ushort)) {
35     ubyte valh = (val >> 8);
36     ubyte vall = val & 0xff;
37     obj.putUbyte(valh);
38     obj.putUbyte(vall);
39     val = (valh << 8) + vall;
40 }
41 
42 void specify(SerializationOptions options, C, T)(auto ref C obj, ref T val) if (is(T == uint)) {
43     ubyte val0 = (val >> 24);
44     ubyte val1 = cast(ubyte)(val >> 16);
45     ubyte val2 = cast(ubyte)(val >> 8);
46     ubyte val3 = val & 0xff;
47     obj.putUbyte(val0);
48     obj.putUbyte(val1);
49     obj.putUbyte(val2);
50     obj.putUbyte(val3);
51     val = (val0 << 24) + (val1 << 16) + (val2 << 8) + val3;
52 }
53 
54 void specify(SerializationOptions options, C, T)(auto ref C obj, ref T val) if (is(T == ulong)) {
55     T newVal;
56     for (int i = 0; i < T.sizeof; ++i) {
57         immutable shiftBy = 64 - (i + 1) * T.sizeof;
58         ubyte byteVal = (val >> shiftBy) & 0xff;
59         obj.putUbyte(byteVal);
60         newVal |= (cast(T) byteVal << shiftBy);
61     }
62     val = newVal;
63 }
64 
65 void specify(SerializationOptions options, C, T)(auto ref C obj, ref T val) if (isStaticArray!T) {
66     static if (is(Unqual!(ElementType!T) : ubyte) && T.sizeof == 1) {
67         obj.putRaw(cast(ubyte[]) val);
68     } else {
69         foreach (ref v; val) {
70             specify!(options)(obj, v);
71         }
72     }
73 }
74 
75 void specify(SerializationOptions options, C, T)(auto ref C obj, ref T val) if (is(T == string)) {
76     ushort len = cast(ushort) val.length;
77     specify!(options)(obj, len);
78 
79     static if (is(C == BinarySerializer)) {
80         obj.putRaw(cast(ubyte[]) val);
81     } else {
82         val = cast(string) obj.take(len).idup;
83     }
84 }
85 
86 void specify(SerializationOptions options, U, C, T)(auto ref C obj, ref T val) if(is(T == string)) {
87     U length = cast(U)val.length;
88     assert(length == val.length, "overflow");
89     specify!(options)(obj,length);
90 
91     static if(is(C == BinarySerializer))
92         obj.putRaw(cast(ubyte[])val);
93     else
94         val = cast(string) obj.putRaw(length).idup;
95 }
96 
97 void specify(SerializationOptions options, C, T)(auto ref C obj, ref T val) if (isAssociativeArray!T) {
98     ushort length = cast(ushort) val.length;
99     specify!(options)(obj, length);
100     const keys = val.keys;
101     for (ushort i = 0; i < length; ++i) {
102         KeyType!T k = keys.length ? keys[i] : KeyType!T.init;
103         auto v = keys.length ? val[k] : ValueType!T.init;
104 
105         specify!(options)(obj, k);
106         specify!(options)(obj, v);
107         val[k] = v;
108     }
109 }
110 
111 void specify(SerializationOptions options, C, T)(auto ref C obj, ref T val) if (isPointer!T) {
112     alias ValueType = PointerTarget!T;
113     specify!(options)(obj, *val);
114 }
115 
116 //ubyte
117 void specify(SerializationOptions options, C, T)(auto ref C obj, ref T val) if (is(T == ubyte)) {
118     obj.putUbyte(val);
119 }
120 
121 void specify(SerializationOptions options, C, T)(auto ref C obj, ref T val)
122         if (!is(T == enum) && (isSigned!T || isBoolean!T || is(T == char) || isFloatingPoint!T)) {
123     specifyPtr!(options)(obj, val);
124 }
125 
126 //ENUM
127 void specify(SerializationOptions options, C, T)(auto ref C obj, ref T val) if (is(T == enum)) {
128     specify!(options)(obj, cast(Unqual!(OriginalType!(T))) val);
129 }
130 
131 void specify(SerializationOptions options, C, T : E[], E)(auto ref C obj, ref T val)
132         if (is(C == BinarySerializer) && isInputRange!T && !isInfinite!T
133             && !is(T == string) && !isStaticArray!T && !isAssociativeArray!T) {
134     enum hasLength = is(typeof(() { auto l = val.length; }));
135     ushort length = cast(ushort) val.length;
136     specify!(options)(obj, length);
137 
138     static if (hasSlicing!(Unqual!T) && is(Unqual!(ElementType!T) : ubyte) && T.sizeof == 1) {
139         obj.putRaw(cast(ubyte[]) val.array);
140     } else {
141         foreach (ref v; val) {
142             specify!(options)(obj, v);
143         }
144     }
145 }
146 
147 void specify(SerializationOptions options, C, T)(auto ref C obj, ref T val)
148         if (isAggregateType!T && !isInputRange!T && !isOutputRange!(T, ubyte)) {
149     
150     debug(HUNT_DEBUG_MORE) tracef("Setting: %s, value: %s", T.stringof, val);
151     loopMembers!(options, C, T)(obj, val);
152 }
153 
154 void specify(SerializationOptions options, C, T : U[], U)(auto ref C obj, ref T val) 
155         if (isDecerealiser!C && !is(U == ubyte) && !is(T == string))  {
156     ushort length;
157 
158     specify!(options)(obj, length);
159     decerealiseArrayImpl!(options)(obj, val, length);
160 }
161 
162 
163 void decerealiseArrayImpl(SerializationOptions options, C, T : E[], E, U)(auto ref C obj, ref T val, U length)
164         if (is(T == E[], E) && isDecerealiser!C) {
165 
166     ulong neededBytes(T)(ulong length) {
167         alias E = ElementType!T;
168         static if (isScalarType!E)
169             return length * E.sizeof;
170         else static if (isInputRange!E)
171             return neededBytes!E(length);
172         else
173             return 0;
174     }
175 
176     immutable needed = neededBytes!T(length);
177 
178     static if (is(Unqual!(ElementType!T) : ubyte) && T.sizeof == 1) {
179         val = obj.putRaw(length).dup;
180     } else {
181         if (val.length != length)
182             val.length = cast(uint) length;
183 
184         foreach (ref e; val) {
185             obj.specify!(options)(e);
186         }
187     }
188 }
189 
190 void specifyPtr(SerializationOptions options, C, T)(auto ref C obj, ref T val) {
191     auto ptr = cast(PtrType!T)(&val);
192     specify!(options)(obj, *ptr);
193 }
194 
195 void loopMembers(SerializationOptions options, C, T)(auto ref C obj, ref T val) if (is(T == struct)) {
196     loopMembersImpl!(T, options)(obj, val);
197 }
198 
199 void loopMembers(SerializationOptions options, C, T)(auto ref C obj, ref T val) if (is(T == class)) {
200 
201     debug(HUNT_DEBUG_MORE) tracef("Setting: %s, value: %s", T.stringof, val);
202 
203     static if (is(C == BinarySerializer)) {
204         if (val is null) {
205             obj.putRaw(NULL);
206             return;
207         }
208         //assert(val !is null, "null value cannot be serialised");
209     }
210 
211     static if (is(C == BinaryDeserializer)) {
212         if (obj.isNullObj) {
213             obj.take(NULL.length);
214             val = null;
215             return;
216         }
217     }
218 
219     static if (is(typeof(() { val = new T; }))) {
220         if (val is null)
221             val = new T;
222     } 
223 
224     obj.putClass!(options, T)(val);
225 }
226 
227 
228 void loopMembersImpl(T, SerializationOptions options, C, VT)
229         (auto ref C obj, ref VT val) {
230     // foreach (member; __traits(derivedMembers, T)) {
231     //     enum isMemberVariable = is(typeof(() {
232     //                 __traits(getMember, val, member) = __traits(getMember, val, member).init;
233     //             }));
234     //     static if (isMemberVariable) {
235     //         specifyAggregateMember!member(obj, val);
236     //     }
237     // }
238     
239         debug(HUNT_DEBUG_MORE) pragma(msg, "T=> " ~ T.stringof);
240     static foreach (string member; FieldNameTuple!T) {
241         debug(HUNT_DEBUG_MORE) pragma(msg, "Field member: " ~ member);
242         static if(!member.empty())
243         {{
244             alias currentMember = __traits(getMember, T, member);
245             alias memberType = typeof(currentMember);
246 
247             static if(hasUDA!(currentMember, Ignore)) {
248                 enum canDeserialize = false;
249                 version(HUNT_DEBUG) {
250                     infof("Ignore a member: %s %s", memberType.stringof, member);
251                 } 
252             } else static if(options.onlyPublic) {
253                 static if (__traits(getProtection, currentMember) == "public") {
254                     enum canDeserialize = true;
255                 } else {
256                     enum canDeserialize = false;
257                 }
258             } else {
259                 enum canDeserialize = true;
260             }
261 
262             static if(canDeserialize) {
263                 debug(HUNT_DEBUG_MORE) tracef("name: %s", member);
264                 specify!(options)(obj, __traits(getMember, val, member));
265                 debug(HUNT_DEBUG_MORE) infof("value: %s", __traits(getMember, val, member));
266             }
267         }}
268     }    
269 }
270 
271 
272 // void specifyAggregateMember(string member, SerializationOptions options, C, T)(auto ref C obj, ref T val) {
273 //     import std.meta : staticIndexOf;
274 
275 //     enum NoCereal;
276 //     enum noCerealIndex = staticIndexOf!(NoCereal, __traits(getAttributes,
277 //                 __traits(getMember, val, member)));
278 //     static if (noCerealIndex == -1) {
279 //         specifyMember!(member, options)(obj, val);
280 //     }
281 // }
282 
283 // void specifyMember(string member, SerializationOptions options, C, T)(auto ref C obj, ref T val) {
284 //     // alias currentMember = __traits(getMember, val, member);
285 //     // static if(isAggregateType!(typeof(currentMember))) {
286 //     //     specify!(options)(obj, __traits(getMember, val, member));
287 //     // } else {
288 //     //     specify(obj, __traits(getMember, val, member));
289 //     // }
290 //     debug(HUNT_DEBUG_MORE) tracef("name: %s", member);
291 //     specify!(options)(obj, __traits(getMember, val, member));
292 //     debug(HUNT_DEBUG_MORE) infof("value: %s", __traits(getMember, val, member));
293 // }
294 
295 void specifyBaseClass(SerializationOptions options, C, T)(auto ref C obj, ref T val) if (is(T == class)) {
296     foreach (base; BaseTypeTuple!T) {
297         loopMembersImpl!(base, options)(obj, val);
298     }
299 }
300 
301 void specifyClass(SerializationOptions options, C, T)(auto ref C obj, ref T val) if (is(T == class)) {
302     static if(options.traverseBase) {
303         specifyBaseClass!(options)(obj, val);
304     }
305     loopMembersImpl!(T, options)(obj, val);
306 }
307 
308 void checkDecerealiser(T)() {
309     //static assert(T.type == CerealType.ReadBytes);
310     auto dec = T();
311     ulong bl = dec.bytesLeft;
312 }
313 
314 enum isDecerealiser(T) = (is(T == BinaryDeserializer) && is(typeof(checkDecerealiser!T)));