1 module spasm.array;
2 
3 import spasm.event;
4 import spasm.node;
5 import spasm.ct;
6 import spasm.node;
7 import spasm.dom;
8 import spasm.types;
9 import std.range : only;
10 import std.algorithm : joiner;
11 import spasm.rt.array;
12 
13 nothrow:
14 @safe:
15 
16 template extractEventPaths(T, Ts...) {
17   import std.meta : staticMap, AliasSeq;
18   import std.traits : getSymbolsByUDA;
19   alias events = getSymbolsByUDA!(T, eventemitter);
20   alias children = getChildren!T;
21   template recur(string NextT) {
22     alias recur = extractEventPaths!(typeof(__traits(getMember, T, NextT)), AliasSeq!(Ts, NextT));
23   }
24   template prefixNames(string Event) {
25     import spasm.ct : tuple;
26     enum prefixNames = tuple(Ts, Event);
27   }
28   alias eventNames = staticMap!(getName, AliasSeq!(events));
29   alias prefixed = staticMap!(prefixNames, eventNames);
30   alias extractEventPaths = AliasSeq!(prefixed,staticMap!(recur, getChildren!T));
31 }
32 
33 template join(string delimiter, elems...) {
34   static if (elems.length == 0)
35     enum join = "";
36   else static if (elems.length == 1)
37     enum join = elems[0];
38   else
39     enum join = elems[0] ~ delimiter ~ join!(delimiter, elems[1..$]);
40 }
41 
42 mixin template ArrayItemEvents(T) {
43   static foreach(path; extractEventPaths!(T)) {
44     mixin Slot!(join!("_",path.expand));
45     mixin("void __" ~ join!("_", path.expand) ~ "(size_t addr) { " ~ join!("_", path.expand) ~ ".emit(this.getIndexInArray(addr));}");
46   }
47 }
48 
49 void assignEventListeners(T)(ref Array!T arr, ref T item) {
50   alias eventPaths = extractEventPaths!(T);
51   static foreach(path; eventPaths) {
52     mixin("item." ~ join!(".", path.expand) ~ ".add(&arr.__" ~ join!("_", path.expand) ~ ");");
53   }
54 }
55 
56 auto getIndexInArray(T)(auto ref Array!T arr, size_t ptr) {
57   import std.algorithm : countUntil;
58   return arr[].countUntil!((ref T* item) {
59       auto baseAddr = cast(size_t)(cast(void*)item);
60       return baseAddr <= ptr && (baseAddr + T.sizeof) > ptr;
61     });
62 }
63 
64 struct Array(T) {
65   @child DynamicArray!(T*) appender;
66   mixin ArrayItemEvents!T;
67   alias appender this;
68   void put(T* t) {
69     this.assignEventListeners(*t);
70     appender.put(t);
71   }
72 }
73 
74 struct Updater(T) {
75   private {
76     T* list;
77     size_t idx;
78   }
79   this(T* list) {
80     this.list=list;
81     foreach(i; list.items) {
82       i.node.marked = true;
83     }
84   }
85   ~this() {
86     if (idx < list.items.length)
87       foreach (i; list.items[idx .. $]) {
88         if (i.node.marked)
89           unmount(*i);
90       }
91     list.items.shrinkTo(idx);
92   }
93   void put(Item)(Item* t) {
94     size_t i = idx++;
95     t.node.marked = false;
96     if (list.items.length > i) {
97       if (list.items[i] == t) {
98         return;
99       }
100       // TODO: here we can use replaceChild
101       if (list.items[i].node.marked) {
102         unmount(*list.items[i]);
103       }
104       if (idx+1 < list.items.length) {
105         if (list.items[i+1] == t) {
106           list.items.remove(i);
107           return;
108         } else {
109           list.items[i] = t;
110           list.items.assignEventListeners(*t);
111           if (list.items[i+1].node.marked)
112             list.node.renderBefore((*list.items[i]), list.items[i+1].node);
113           else {
114             size_t off = 2;
115             while (i+off < list.items.length) {
116               if (list.items[i+off].node.marked) {
117                 list.node.renderBefore((*list.items[i]), list.items[i+off].node);
118                 return;
119               }
120               off+=1;
121             }
122             list.node.render(*list.items[i]);
123             return;
124           }
125         }
126       } else {
127         list.items[i] = t;
128         list.items.assignEventListeners(*t);
129         list.node.render(*list.items[i]);
130       }
131     } else {
132       list.put(t);
133     }
134   }
135 }
136 
137 import std.traits : hasMember, isPointer;
138 bool removeItem(T)(ref T app, size_t idx) { //}if (hasMember!(T, "remove")) {
139   app.remove(idx);
140   return true;
141 }
142 
143 bool removeItem(T,U)(ref T app, ref U t) if (isPointer!(U)) {
144   import std.algorithm : countUntil, remove;
145   auto idx = app[0..$].countUntil(t);
146   if (idx == -1)
147     return false;
148   return app.removeItem(idx);
149 }
150 
151 struct List(T, string tag) {
152   mixin Node!tag;
153   @child Array!(T) items;
154   void put(T* t) {
155     items.put(t);
156     spasm.dom.render(node,*items[$-1]);
157   }
158   void shrinkTo(size_t size) {
159     if (size < items.length)
160       foreach(i; items[size..$]) {
161         unmount(*i);
162       }
163     items.shrinkTo(size);
164   }
165   void remove(size_t idx) {
166     .removeChild(items.appender[idx]);
167     import std.algorithm : remove;
168     items.appender.removeItem(idx);
169   }
170 }
171 
172 alias UnorderedList(T) = List!(T,"ul");