1 module modernui.collections;
2 
3 import std.range;
4 import std.container.array;
5 import std.traits;
6 import modernui.rx;
7 
8 struct ReadOnlyListRange(E)
9 {
10 	Array!(E).Range range;
11 
12 	this(Array!(E).Range r)
13 	{
14 		range = r;
15 	}
16 
17 	@property ReadOnlyListRange save()
18 	{
19 		return this;
20 	}
21 
22 	@property bool empty() @safe pure nothrow const
23 	{
24 		return range.empty();
25 	}
26 
27 	@property size_t length() @safe pure nothrow const
28 	{
29 		return range.length();
30 	}
31 
32 	alias opDollar = length;
33 
34 	@property E front()
35 	{
36 		return range.front();
37 	}
38 
39 	@property E back()
40 	{
41 		return range.back();
42 	}
43 
44 	void popFront() @safe pure nothrow
45 	{
46 		range.popFront();
47 	}
48 
49 	void popBack() @safe pure nothrow
50 	{
51 		range.popBack();
52 	}
53 }
54 
55 class ReadOnlyList(T)
56 {
57 	private Array!T impl;
58 
59 	alias Range = ReadOnlyListRange!T;
60 
61 	this()
62 	{
63 		impl = Array!T();
64 	}
65 
66 	this(U)(U[] values...) if (isImplicitlyConvertible!(U, T))
67 	{
68 		impl = Array!T(values);
69 	}
70 
71 	this(Stuff)(Stuff stuff) if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T) && !is(Stuff == T[]))
72 	{
73 		impl = Array!(stuff);
74 	}
75 
76 	@property size_t length() const { return impl.length; }
77 
78 	bool contains(T val) const 
79 	{
80 		foreach(v; impl)
81 		{
82 			if(v == val) return true;
83 		}
84 		return false;
85 	}
86 
87 	T opIndex(size_t index)
88 	{
89 		return impl[index];
90 	}
91 
92 	Range opSlice() { return Range(impl[]); }
93 
94 	Range opSlice(size_t begin, size_t excl_end)
95 	{
96 		return Range(impl[begin..excl_end]);
97 	}
98 
99 	alias opDollar = length;
100 }
101 
102 struct ItemsAdded(T)
103 {
104 	alias ItemsAddedRange = ReadOnlyList!T.Range;
105 
106 	private size_t myNewItemsFirstIndex;
107 	private ItemsAddedRange myNewItems;
108 
109 	@property size_t newItemsFirstIndex() const { return myNewItemsFirstIndex; }
110 	@property ItemsAddedRange newItems() { return myNewItems; }
111 
112 	this(size_t newItemsFirstIndex, ItemsAddedRange newItems)
113 	{
114 		this.myNewItemsFirstIndex = newItemsFirstIndex;
115 		this.myNewItems = newItems;
116 	}
117 }
118 
119 struct ItemsRemoved(T)
120 {
121 	private bool myIsReset;
122 	private ReadOnlyList!T myOldItems;
123 
124 	@property bool isReset() const { return myIsReset; }
125 	@property ReadOnlyList!T oldItems() { return myOldItems; }
126 
127 	this(bool isReset, ReadOnlyList!T oldItems)
128 	{
129 		this.myIsReset = isReset;
130 		this.myOldItems = oldItems;
131 	}
132 }
133 
134 abstract class ReadOnlyReactiveList(T) : ReadOnlyList!T
135 {
136 	alias ItemsAddedDesc = ItemsAdded!T;
137 	alias ItemsRemovedDesc = ItemsRemoved!T;
138 
139 	@property Observable!ItemsAddedDesc itemsAdded() { return itemsAddedSubject; }
140 	private Subject!ItemsAddedDesc itemsAddedSubject;
141 
142 	// This property do not guarantee that remove is complete on reset
143 	@property Observable!ItemsRemovedDesc itemsRemoved() { return itemsRemovedSubject; }
144 	private Subject!ItemsRemovedDesc itemsRemovedSubject;
145 
146 	this()
147 	{
148 		itemsAddedSubject = new Subject!ItemsAddedDesc;
149 		itemsRemovedSubject = new Subject!ItemsRemovedDesc;
150 	}
151 }
152 
153 class ReactiveList(T) : ReadOnlyReactiveList!T
154 {
155 	void add(T item)
156 	{
157 		auto i = impl.length;
158 		impl.insertBack(item);
159 		auto listChange = ItemsAdded!T(i, this[i..$]);
160 		itemsAddedSubject.next(listChange);
161 	}
162 
163 	// covers T[]
164 	// covers Array!T
165 	void addRange(Stuff)(Stuff stuff) if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T))
166 	{
167 		auto i = impl.length;
168 		impl.insertBack(stuff);
169 
170 		if(!itemsAddedSubject.hasSubscribers) return;
171 		auto listChange = ItemsAdded!T(i, this[i..$]);
172 		itemsAddedSubject.next(listChange);
173 	}
174 
175 	void remove(T v)
176 	{
177 		bool found = false;
178 		for(size_t i = 0; i<impl.length; i++)
179 		{
180 			if(impl[i] == v) 
181 			{
182 				impl.linearRemove(impl[i..i+1]);
183 				found = true;
184 				break;
185 			}
186 		}
187 
188 		if(!found) throw new Error("Item not found");
189 
190 		if(!itemsRemovedSubject.hasSubscribers) return;
191 		auto listChange = ItemsRemoved!T(false, new ReadOnlyList!T(v));
192 		itemsRemovedSubject.next(listChange);
193 	}
194 
195 	void removeRange(Stuff)(Stuff val) if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T))
196 	{
197 		static if(is(Stuff == ReadOnlyList!T))
198 		{
199 			assert(val !is this);
200 		} else {
201 			auto oldItems = new ReactiveList!T;
202 		}
203 
204 		foreach(v; val)
205 		{
206 			bool found = false;
207 			for(size_t i = 0; i<impl.length; i++)
208 			{
209 				if(impl[i] == v) 
210 				{
211 					impl.linearRemove(impl[i..i+1]);
212 					found = true;
213 					static if(!is(Stuff == ReadOnlyList!T))
214 					{
215 						if(itemsRemovedSubject.hasSubscribers)
216 						{
217 							if(oldItems.length > 0) oldItems[0] = v;
218 							else oldItems.add(v);
219 							auto listChange = ItemsRemoved!T(true, oldItems);
220 							itemsRemovedSubject.next(listChange);
221 						}
222 					}
223 					break;
224 				}
225 			}
226 
227 			if(!found) throw new Error("Item not found");
228 		}
229 
230 		static if(is(Stuff == ReadOnlyList!T))
231 		{
232 			if(itemsRemovedSubject.hasSubscribers) 
233 			{
234 				auto listChange = ItemsRemoved!T(true, val);
235 				itemsRemovedSubject.next(listChange);
236 			}
237 		}
238 	}
239 
240 	void clear() 
241 	{
242 		if(!length) return;
243 		auto listChange = ItemsRemoved!T(true, this);
244 		itemsRemovedSubject.next(listChange);
245 
246 		impl.clear();
247 	}
248 
249 	void set(size_t index, T value) 
250 	{
251 		T oldValue = impl[index];
252 		impl[index] = value;
253 
254 		auto listChange = ItemsRemoved!T(false, new ReadOnlyList!T(oldValue));
255 		itemsRemovedSubject.next(listChange);
256 		
257 		auto listChange2 = ItemsAdded!T(index, this[index..index+1]);
258 		itemsAddedSubject.next(listChange2);
259 	}
260 
261 	T opIndexAssign(T v, size_t i)
262 	{
263 		set(i, v);
264 		return v;
265 	}
266 }
267 
268 unittest
269 {
270 	import modernui.rx;
271 
272 	auto list = new ReactiveList!int;
273 	assert(list.length == 0);
274 
275 	list.addRange([5]);
276 	assert(list.length == 1);
277 	assert(list[0] == 5);
278 
279 	list.set(0, 6);
280 	assert(list[0] == 6);
281 	assert(list.length == 1);
282 
283 	list.removeRange([6]);
284 	assert(list.length == 0);
285 
286 	list.add(1);
287 	assert(list[0] == 1);
288 	assert(list.length == 1);
289 	foreach(i; list[])
290 	{
291 		assert(i == 1);
292 	}
293 
294 	list.add(7);
295 	list.add(9);
296 	assert(list[2] == 9);
297 	assert(list.contains(7));
298 	assert(!list.contains(700));
299 	list[2] = 10;
300 	assert(list[2] == 10);
301 	assert(list.length == 3);
302 	list.remove(7);
303 	assert(list.length == 2);
304 
305 	list.clear();
306 	assert(list.length == 0);
307 
308 	bool changeInvoked = false;
309 	list.itemsAdded.then!(ItemsAdded!int)((a) {
310 		changeInvoked = true;
311 	});
312 	list.add(9);
313 	assert(changeInvoked);
314 }