1 module tagion.betterC.wallet.WalletRecords;
2 
3 import tagion.basic.Types : Buffer;
4 import tagion.basic.basic : basename;
5 import tagion.betterC.hibon.Document : Document;
6 import tagion.betterC.hibon.HiBON;
7 import tagion.betterC.utils.BinBuffer;
8 import tagion.betterC.utils.Memory;
9 import tagion.betterC.wallet.KeyRecover : KeyRecover;
10 import tagion.crypto.Types : Pubkey, Signature;
11 
12 // import std.format;
13 import core.stdc.string : memcpy;
14 import std.conv : emplace;
15 import std.range.primitives : isInputRange;
16 import std.traits : ForeachType, ReturnType, Unqual, hasMember, isArray, isAssociativeArray, isIntegral, isUnsigned;
17 import std.typecons : Tuple, TypedefType;
18 import tagion.betterC.funnel.TagionCurrency;
19 
20 // import tagion.script.StandardRecords : StandardBill;
21 
22 template isSpecialKeyType(T) {
23     import std.traits : KeyType, isAssociativeArray, isUnsigned;
24 
25     static if (isAssociativeArray!T) {
26         alias KeyT = KeyType!T;
27         enum isSpecialKeyType = !(isUnsigned!KeyT) && !is(KeyT : string);
28     }
29     else {
30         enum isSpecialKeyType = false;
31     }
32 }
33 
34 static R toList(R)(const Document doc) {
35     alias MemberU = ForeachType!(R);
36     alias BaseU = TypedefType!MemberU;
37     static if (isArray!R) {
38         alias UnqualU = Unqual!MemberU;
39         UnqualU[] result;
40         result.length = doc.length;
41         enum do_foreach = true;
42     }
43     else static if (isSpecialKeyType!R) {
44         R result;
45         enum do_foreach = true;
46     }
47     else static if (isAssociativeArray!R) {
48         R result;
49         enum do_foreach = true;
50     }
51     else {
52         return R(doc);
53         enum do_foreach = false;
54     }
55     static if (do_foreach) {
56         foreach (elm; doc[]) {
57             static if (isSpecialKeyType!R) {
58                 const value_doc = elm.get!Document;
59                 alias KeyT = KeyType!R;
60                 alias BaseKeyT = TypedefType!KeyT;
61                 static if (Document.Value.hasType!BaseKeyT || is(BaseKeyT == enum)) {
62                     const key = KeyT(value_doc[0].get!BaseKeyT);
63                 }
64                 else {
65                     auto key = KeyT(value_doc[0].get!BaseKeyT);
66                 }
67                 const e = value_doc[1];
68             }
69             else {
70                 const e = elm;
71             }
72             static if (Document.Value.hasType!MemberU || is(BaseU == enum)) {
73                 auto value = e.get!BaseU;
74             }
75             else static if (Document.Value.hasType!BaseU) {
76                 // Special case for Typedef
77                 auto value = MemberU(e.get!BaseU);
78             }
79             else {
80                 const sub_doc = e.get!Document;
81                 static if (is(BaseU == struct)) {
82                     auto value = BaseU(sub_doc);
83                 }
84                 else {
85                     auto value = toList!BaseU(sub_doc);
86                 }
87                 // else {
88                 //     static assert(0,
89                 //             format("Can not convert %s to Document", R.stringof));
90                 // }
91             }
92             static if (isAssociativeArray!R) {
93                 static if (isSpecialKeyType!R) {
94                     result[key] = value;
95                 }
96                 else {
97                     result[e.key] = value;
98                 }
99             }
100             else {
101                 result[e.index] = value;
102             }
103         }
104     }
105     return cast(immutable) result;
106 }
107 
108 struct RecordType {
109     string name;
110     string code; // This is is mixed after the Document constructor
111 }
112 
113 struct Label {
114     string name; /// Name of the HiBON member
115     bool optional; /// This flag is set to true if this paramer is optional
116 }
117 
118 enum VOID = "*";
119 
120 template GetLabel(alias member) {
121     import std.traits : getUDAs, hasUDA;
122 
123     static if (hasUDA!(member, Label)) {
124         enum label = getUDAs!(member, Label)[0];
125         static if (label.name == VOID) {
126             enum GetLabel = Label(basename!(member), label.optional);
127         }
128         else {
129             enum GetLabel = label;
130         }
131     }
132     else {
133         enum GetLabel = Label(basename!(member));
134     }
135 }
136 
137 @trusted {
138     import std.algorithm;
139     import std.array;
140 
141     @RecordType("Quiz")
142     struct Quiz {
143         @Label("$Q") string[] questions;
144         this(Document doc) {
145             auto received_questions = doc["$Q"].get!Document;
146             questions.create(received_questions.length);
147             foreach (element; received_questions[]) {
148                 questions[element.index] = element.get!string;
149             }
150         }
151 
152         inout(HiBONT) toHiBON() inout {
153             auto hibon = HiBON();
154             auto tmp_arr = HiBON();
155             foreach (i, question; questions) {
156                 tmp_arr[i] = question;
157             }
158             // GetLabel
159             hibon["$Q"] = tmp_arr;
160             return cast(inout) hibon;
161         }
162 
163         const(Document) toDoc() {
164             return Document(toHiBON.serialize);
165         }
166     }
167 
168     @RecordType("PIN")
169     struct DevicePIN {
170         Buffer D; /// Device number
171         Buffer U; /// Device random
172         Buffer S; /// Check sum value
173         void recover(ref scope ubyte[] R, scope const(ubyte[]) P) const {
174             import tagion.betterC.utils.Miscellaneous : xor;
175 
176             xor(R, D, P);
177         }
178 
179         this(Document doc) {
180             enum number_name = GetLabel!(D).name;
181             enum random_name = GetLabel!(U).name;
182             enum sum_name = GetLabel!(S).name;
183 
184             D = doc[number_name].get!Buffer;
185             U = doc[random_name].get!Buffer;
186             S = doc[sum_name].get!Buffer;
187         }
188 
189         inout(HiBONT) toHiBON() inout {
190             auto hibon = HiBON();
191             enum number_name = GetLabel!(D).name;
192             enum random_name = GetLabel!(U).name;
193             enum sum_name = GetLabel!(S).name;
194 
195             hibon[number_name] = D;
196             hibon[random_name] = U;
197             hibon[sum_name] = S;
198             return cast(inout) hibon;
199         }
200 
201         const(Document) toDoc() {
202             return Document(toHiBON.serialize);
203         }
204     }
205 
206     @RecordType("Wallet")
207     struct RecoverGenerator {
208         Buffer[] Y; /// Recorvery seed
209         Buffer S; /// Check value S=H(H(R))
210         @Label("N") uint confidence;
211         import tagion.betterC.hibon.HiBON;
212 
213         inout(HiBONT) toHiBON() inout {
214             auto hibon = HiBON();
215             auto tmp_arr = HiBON();
216             foreach (i, y; Y) {
217                 tmp_arr[i] = y;
218             }
219             tmp_arr["S"] = S;
220             tmp_arr["N"] = confidence;
221             hibon = tmp_arr;
222             return cast(inout) hibon;
223         }
224 
225         const(Document) toDoc() {
226             return Document(toHiBON.serialize);
227         }
228 
229         this(Document doc) {
230             auto Y_data = doc["Y"].get!Document;
231             Y.create(Y_data.length);
232             foreach (element; Y_data[]) {
233                 Y[element.index] = element.get!Buffer;
234             }
235             S = doc["S"].get!Buffer;
236             confidence = doc["N"].get!uint;
237         }
238     }
239 
240     struct AccountDetails {
241         // @Label("$derives") Buffer[Pubkey] derives;
242         @Label("$derives") Document derives;
243         @Label("$bills") StandardBill[] bills;
244         @Label("$state") Buffer derive_state;
245         // boll[Pubkey]
246         @Label("$active") Document activated; /// Actived bills
247         import std.algorithm : any, each, filter, map, sum;
248 
249         this(Document doc) {
250             enum derives_name = GetLabel!(derives).name;
251             enum bills_name = GetLabel!(bills).name;
252             enum ds_name = GetLabel!(derive_state).name;
253             enum active_name = GetLabel!(activated).name;
254 
255             auto received_der = doc[derives_name].get!Document;
256             // auto list = toList!Buffer[Pubkey](received_der);
257             auto received_bills = doc[bills_name].get!Document;
258             bills.create(received_bills.length);
259             foreach (element; received_bills[]) {
260                 enum value_name = GetLabel!(StandardBill.value).name;
261                 bills[element.index].value = TagionCurrency(
262                         received_bills[value_name].get!Document);
263                 bills[element.index].epoch = element.get!uint;
264                 // bills[element.index].owner = element;
265                 bills[element.index].gene = element.get!Buffer;
266             }
267             derive_state = doc[ds_name].get!Buffer;
268             activated = doc[active_name].get!Document;
269         }
270 
271         inout(HiBONT) toHiBON() inout {
272             auto hibon = HiBON();
273             enum derives_name = GetLabel!(derives).name;
274             enum bills_name = GetLabel!(bills).name;
275             enum ds_name = GetLabel!(derive_state).name;
276             enum active_name = GetLabel!(activated).name;
277 
278             hibon[derives_name] = derives;
279             // auto tmp_hibon = HiBON();
280             // foreach(i, bill; bills) {
281             //     tmp_hibon[i] = bill;
282             // }
283             // hibon[bills_name] = tmp_hibon;
284             hibon[ds_name] = derive_state;
285             hibon[active_name] = activated;
286             return cast(inout) hibon;
287         }
288 
289         const(Document) toDoc() {
290             return Document(toHiBON.serialize);
291         }
292 
293         bool remove_bill(Pubkey pk) {
294             import std.algorithm : countUntil, remove;
295 
296             const index = countUntil!"a.owner == b"(bills, pk);
297             if (index > 0) {
298                 bills = bills.remove(index);
299                 return true;
300             }
301             return false;
302         }
303 
304         void add_bill(StandardBill bill) {
305             bills.resize(bills.length + 1);
306             bills[$ - 1] = bill;
307         }
308 
309         /++
310          Clear up the Account
311          Remove used bills
312          +/
313         void clearup() pure {
314             // bills
315             //     .filter!(b => b.owner in derives)
316             //     .each!(b => derives.remove(b.owner));
317             // bills
318             //     .filter!(b => b.owner in activated)
319             //     .each!(b => activated.remove(b.owner));
320         }
321 
322         const {
323             /++
324          Returns:
325          true if the all transaction has been registered as processed
326          +/
327             bool processed() {
328                 bool res = false;
329                 foreach (bill; bills) {
330                     foreach (active; activated[]) {
331                         const active_data = active.get!Document;
332                         if (active_data[0].get!Buffer == bill.owner) {
333 
334                         }
335                         // const key = tmp[0].get!Buffer;
336                         // const value = tmp[1].get!bool;
337                     }
338                     // auto tmp = toList(activated)
339                     // if (bill.owner in activated) {
340                     //     res = true;
341                     // }
342                 }
343                 return res;
344             }
345             /++
346          Returns:
347          The available balance
348          +/
349             TagionCurrency available() {
350                 long result;
351                 foreach (bill; bills) {
352                     foreach (active; activated[]) {
353                         const active_data = active.get!Document;
354                         if (active_data[0].get!Buffer == bill.owner) {
355                             result += active_data[1].get!uint;
356                         }
357                     }
358                 }
359                 return TagionCurrency(result);
360             }
361             /++
362         //  Returns:
363         //  The total active amount
364         //  +/
365             TagionCurrency active() {
366                 long result;
367                 foreach (bill; bills) {
368                     foreach (active; activated[]) {
369                         const active_data = active.get!Document;
370                         if (active_data[0].get!Buffer == bill.owner) {
371                             result += active_data[1].get!uint;
372                         }
373                     }
374                 }
375                 return TagionCurrency(result);
376             }
377             //     /++
378             //  Returns:
379             //  The total balance including the active bills
380             //  +/
381             // TagionCurrency total() {
382             //     return bills
383             //         .map!(b => b.value)
384             //         .sum;
385             // }
386         }
387     }
388 
389     @RecordType("BIL") struct StandardBill {
390         @Label("$V") TagionCurrency value; // Bill type
391         @Label("$k") uint epoch; // Epoch number
392         //        @Label("$T", true) string bill_type; // Bill type
393         @Label("$Y") Pubkey owner; // Double hashed owner key
394         @Label("$G") Buffer gene; // Bill gene
395         this(Document doc) {
396             // value = doc["Y"].get!Document;
397             epoch = doc["k"].get!uint;
398             Buffer tmp_buf = doc["Y"].get!Buffer;
399             Pubkey pkey;
400             pkey = tmp_buf;
401             // memcpy_wrapper(owner, pkey);
402             gene = doc["G"].get!Buffer;
403         }
404 
405         inout(HiBONT) toHiBON() inout {
406             auto hibon = HiBON();
407             // hibon["V"] = value;
408             hibon["k"] = epoch;
409             // hibon["Y"] = owner;
410             hibon["G"] = gene;
411             return cast(inout) hibon;
412         }
413 
414         const(Document) toDoc() {
415             return Document(toHiBON.serialize);
416         }
417     }
418 
419     @RecordType("Invoice") struct Invoice {
420         string name;
421         TagionCurrency amount;
422         Pubkey pkey;
423         @Label("*", true) Document info;
424         this(Document doc) {
425         }
426 
427         inout(HiBONT) toHiBON() inout {
428             auto hibon = HiBON();
429             return cast(inout) hibon;
430         }
431 
432         const(Document) toDoc() {
433             return Document(toHiBON.serialize);
434         }
435     }
436 
437     @RecordType("SMC") struct Contract {
438         @Label("$in") Buffer[] input; /// Hash pointer to input (DART)
439         @Label("$read", true) Buffer[] read; /// Hash pointer to read-only input (DART)
440         @Label("$out") Document[Pubkey] output; // pubkey of the output
441         @Label("$run") Script script; // TVM-links / Wasm binary
442         bool verify() {
443             return (input.length > 0);
444         }
445     }
446 
447     @RecordType("SSC") struct SignedContract {
448         @Label("$signs") immutable(ubyte)[] signs; /// Signature of all inputs
449         @Label("$contract") Contract contract; /// The contract must signed by all inputs
450         @Label("$in", true) Document input; /// The actual inputs
451         this(Document doc) {
452             enum sign_name = GetLabel!(signs).name;
453             // enum contract_name = GetLabel!(contract).name;
454             enum input_name = GetLabel!(input).name;
455 
456             auto received_sign = doc[sign_name].get!Buffer;
457             signs.create(received_sign.length);
458             signs = received_sign;
459 
460             // contract = doc[contract_name].get!Contract;
461             input = doc[input_name].get!Document;
462 
463         }
464 
465         inout(HiBONT) toHiBON() inout {
466             auto hibon = HiBON();
467             return cast(inout) hibon;
468         }
469 
470         const(Document) toDoc() {
471             return Document(toHiBON.serialize);
472         }
473     }
474 
475     struct Script {
476         @Label("$name") string name;
477         @Label("$env", true) Buffer link; // Hash pointer to smart contract object;
478         // mixin HiBONRecord!(
479         //         q{
480         //         this(string name, Buffer link=null) {
481         //             this.name = name;
482         //             this.link = link;
483         //         }
484         //     });
485         // bool verify() {
486         //     return (wasm.length is 0) ^ (link.empty);
487         // }
488 
489     }
490 }