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