1 module dscord.types.base;
2 
3 import std.conv,
4        std.typecons,
5        std.stdio,
6        std.algorithm,
7        std.traits,
8        std.functional;
9 
10 import dscord.client;
11 
12 import vibe.core.core : runTask, sleep;
13 import vibe.core.sync;
14 
15 // Commonly used public imports
16 public import dscord.util.json;
17 public import std.datetime;
18 
19 // TODO: Eventually this should be a type
20 alias Snowflake = ulong;
21 
22 string toString(Snowflake s) {
23   return to!string(s);
24 }
25 
26 /*
27   AsyncChainer is a utility for exposing methods that can help
28   chain actions with various delays/resolving patterns.
29 */
30 class AsyncChainer(T) {
31   private {
32     T obj;
33     AsyncChainer!T parent;
34     ManualEvent resolveEvent;
35   }
36 
37   /**
38     The base constructor which handles the optional creation of ManualEvent used
39     in the case where this member of the AsyncChain has a delay (or depends on
40     something with a delay).
41 
42     Params:
43       obj = the object to wrap for chaining
44       hasResolver = if true, create a ManualEvent used for resolving
45   */
46   this(T obj, bool hasResolver = false) {
47     this.obj = obj;
48 
49     if (hasResolver) {
50       this.resolveEvent = createManualEvent();
51     }
52   }
53 
54   /**
55     Delayed constructor creates an AsyncChainer chain member which waits for
56     the specified delay before resolving the current and next members of the
57     chain.
58 
59     Params:
60       obj = the object to wrap for chaining
61       delay = a duration to delay before resolving
62       parent = the parent member in the chain to depend on before resolving
63   */
64   this(T obj, Duration delay, AsyncChainer!T parent = null) {
65     this(obj, true);
66 
67     this.parent = parent;
68 
69     runTask({
70       // If we have a parent, wait on its resolve event first
71       if (this.parent) {
72         this.parent.resolveEvent.wait();
73       }
74 
75       // Then sleep for the delay
76       sleep(delay);
77 
78       // And trigger our resolve event
79       this.resolveEvent.emit();
80     });
81   }
82 
83   /**
84     Utility method for chaining. Returns a new child member of the chain.
85   */
86   AsyncChainer!T after(Duration delay) {
87     return new AsyncChainer!T(this.obj, delay, this);
88   }
89 
90   /**
91     opDispatch override that provides a mechanisim for wrapped chaining of the
92     inner object.
93   */
94   AsyncChainer!T opDispatch(string func, Args...)(Args args) {
95     if (this.resolveEvent) {
96       auto next = new AsyncChainer!T(this.obj, true);
97 
98       runTask({
99         this.resolveEvent.wait();
100         this.obj.call!(func)(args);
101         next.resolveEvent.emit();
102       });
103 
104       return next;
105     } else {
106       this.obj.call!(func)(args);
107       return this;
108     }
109   }
110 }
111 
112 /**
113   Base class for all models. Provides a simple interface definition and some
114   utility constructor code.
115 */
116 class IModel {
117   Client  client;
118 
119   void init() {};
120   void load(ref JSON obj) {};
121 
122   this(Client client, ref JSON obj) {
123     debug {
124       client.log.tracef("Starting creation of model %s", this.toString);
125       auto sw = StopWatch(AutoStart.yes);
126     }
127 
128     this.client = client;
129     this.init();
130     this.load(obj);
131 
132     debug {
133       this.client.log.tracef("Finished creation of model %s in %sms", this.toString,
134         sw.peek().to!("msecs", real));
135     }
136   }
137 }
138 
139 /**
140   Base template for all models. Provides utility methods for AsyncChaining and
141   a base constructor that calls the parent IModel constructor.
142 */
143 mixin template Model() {
144   this(Client client, ref JSON obj) {
145     super(client, obj);
146   }
147 
148   auto after(Duration delay) {
149     return new AsyncChainer!(typeof(this))(this, delay);
150   }
151 
152   auto chain() {
153     return new AsyncChainer!(typeof(this))(this);
154   }
155 
156   void call(string blah, T...)(T args) {
157     __traits(getMember, this, blah)(args);
158   }
159 }
160 
161 /**
162   Utility method which reads a Snowflake off of a fast JSON object.
163 */
164 Snowflake readSnowflake(ref JSON obj) {
165   string data = obj.read!string;
166   if (!data) return 0;
167   return data.to!Snowflake;
168 }
169 
170 /**
171   Utility method which loads many of a model T off of a fast JSON object. Returns
172   an array of model T objects.
173 */
174 T[] loadManyArray(T)(Client client, ref JSON obj) {
175   T[] data;
176 
177   foreach (item; obj) {
178     data ~= new T(client, obj);
179   }
180 
181   return data;
182 }
183 
184 /**
185   Utility method that loads many of a model T off of a fast JSON object. Calls
186   the delegate f for each member loaded, returning nothing.
187 */
188 void loadMany(T)(Client client, ref JSON obj, void delegate(T) F) {
189   foreach (item; obj) {
190     F(new T(client, obj));
191   }
192 }
193 
194 /**
195   Utility method that loads many of a model T off of a fast JSON object, passing
196   in a sub-type TSub as the first argument to the constructor. Calls the delegate
197   f for each member loaded, returning nothing.
198 */
199 void loadManyComplex(TSub, T)(TSub sub, ref JSON obj, void delegate(T) F) {
200   foreach (item; obj) {
201     F(new T(sub, obj));
202   }
203 }
204 
205 /**
206   A utility wrapper around an associative array that stores models.
207 */
208 class ModelMap(TKey, TValue) {
209   TValue[TKey]  data;
210 
211   /**
212     Set the key to a value.
213   */
214   TValue set(TKey key, TValue value) {
215     if (value is null) {
216       this.remove(key);
217       return null;
218     }
219 
220     this.data[key] = value;
221     return value;
222   }
223 
224   /**
225     Return the value for a key.
226   */
227   TValue get(TKey key) {
228     return this.data[key];
229   }
230 
231   /**
232     Return the value for a key, or if it doesn't exist a default value.
233   */
234   TValue get(TKey key, TValue def) {
235     if (this.has(key)) {
236       return this.get(key);
237     }
238     return def;
239   }
240 
241   /**
242     Utility method that returns the value for a key.
243   */
244   TValue opCall(TKey key) {
245     return this.data[key];
246   }
247 
248   /**
249     Removes a key.
250   */
251   void remove(TKey key) {
252     this.data.remove(key);
253   }
254 
255   /**
256     Returns true if the key exists within the mapping.
257   */
258   bool has(TKey key) {
259     return (key in this.data) != null;
260   }
261 
262   TValue opIndex(TKey key) {
263     return this.get(key);
264   }
265 
266   void opIndexAssign(TValue value, TKey key) {
267     this.set(key, value);
268   }
269 
270   /**
271     Returns the length of the mapping.
272   */
273   size_t length() {
274     return this.data.length;
275   }
276 
277   /**
278     Allows using a delegate to filter the values of the mapping.
279 
280     Params:
281       f = a delegate which returns true if the passed in value matches.
282   */
283   auto filter(bool delegate(TValue) f) {
284     return this.data.values.filter!(f);
285   }
286 
287   /**
288     Allows applying a delegate over the values of the mapping.
289 
290     Params:
291       f = a delegate which is applied to each value in the mapping.
292   */
293   auto each(void delegate(TValue) f) {
294     return this.data.values.each!(f);
295   }
296 
297   /**
298     Returns a single value from the mapping, based on the return value of a
299     delegate.
300 
301     Params:
302       f = a delegate which returns true if the value passed in matches.
303   */
304   TValue pick(bool delegate(TValue) f) {
305     foreach (value; this.data.values) {
306       if (f(value)) {
307         return value;
308       }
309     }
310     return null;
311   }
312 
313   /**
314     Returns an array of keys from the mapping.
315   */
316   auto keys() {
317     return this.data.keys;
318   }
319 
320   /**
321     returns an array of values from the mapping.
322   */
323   auto values() {
324     return this.data.values;
325   }
326 }