1 module dinu.filter;
2 
3 import
4 	core.thread,
5 	core.atomic,
6 	std.conv,
7 	std.parallelism,
8 	std.string,
9 	std.path,
10 	std.stdio,
11 	std.algorithm,
12 	std.range,
13 	std.uni,
14 	std.datetime,
15 	dinu.fuzzyMatch;
16 
17 
18 shared struct Match(T) {
19 	long score;
20 	immutable(int)[] positions;
21 	immutable(T)[] data;
22 }
23 
24 
25 shared class Queue(T) {
26 
27 	private T[] queue;
28 
29 	synchronized void opOpAssign(string op)(T object) if(op == "~") {
30 		queue ~= object;
31 	}
32 
33 	synchronized size_t length(){
34 		return queue.length;
35 	}
36 
37 	synchronized T pop(){
38 		assert(queue.length, "Please check queue length, is 0");
39 		auto first = queue[0];
40 		queue = queue[1..$];
41 		return first;
42 	}
43 
44 	synchronized shared(T[]) consume(){
45 		auto copy = queue;
46 		queue = [];
47 		return copy;
48 	}
49 
50 }
51 
52 
53 class FuzzyFilter(T) {
54 
55 	protected immutable(Match!T)[] matches;
56 
57 	this(shared bool delegate(immutable T) filterFunc){
58 		addQueue = new shared Queue!(immutable T);
59 		narrowQueue = new shared Queue!string;
60 		this.filterFunc = filterFunc;
61 		auto worker = task({
62 			filterLoop;
63 		});
64 		worker.executeInNewThread;
65 	}
66 
67 	void reset(string filter=""){
68 		filter = filter.expandTilde;
69 		restart = true;
70 		synchronized(this){
71 			narrowQueue.consume;
72 			this.filter = filter;
73 		}
74 	}
75 
76 	void narrow(string text){
77 		narrowQueue ~= text;
78 	}
79 
80 	immutable(Match!T)[] res(){
81 		return matches;
82 	}
83 
84 	void add(immutable T p){
85 		synchronized(this)
86 			choices ~= p;
87 		addQueue ~= p;
88 	}
89 
90 	void set(immutable T[] choices){
91 		synchronized(this)
92 			this.choices = choices;
93 		reset;
94 	}
95 
96 	void remove(immutable T[] remove){
97 		if(!remove.length)
98 			return;
99 		synchronized(this)
100 			choices = choices.filter!(a => !remove.canFind(a)).array;
101 		reset;
102 	}
103 
104 	void stop(){
105 		running = false;
106 	}
107 
108 	protected {
109 
110 		shared immutable(T)[] choices;
111 		shared Queue!(immutable T) addQueue;
112 		shared Queue!string narrowQueue;
113 		shared string filter;
114 
115 		shared bool restart;
116 
117 		shared bool delegate(immutable T) filterFunc;
118 
119 		shared bool running = true;
120 
121 
122 		void match(immutable T p){
123 			if(!filterFunc(p))
124 				return;
125 			Match!T match;
126 			if(p.filterText == filter){
127 				match.score = 9999999;
128 				match.positions = filter.length.iota.array.to!(int[]).idup;	
129 			}else{
130 				auto res = p.filterText.compareFuzzy(p.prepFilter(filter));	
131 				match.score = res[0];
132 				match.positions = res[1..$].idup;
133 			}
134 			if(match.score > 0){
135 				if(!filter.startsWith(".") && (p.filterText.startsWith(".") || p.filterText.canFind("/."))){
136 					match.score = 1.min(match.score-100);
137 				}
138 				match.score.atomicOp!"+="(p.score);
139 				match.data = [p];
140 				foreach(i, e; matches){
141 					if(restart)
142 						break;
143 					if(e.score <= match.score){
144 						if(e.score == match.score && e.data[0].filterText.icmp(match.data[0].filterText) < 0)
145 							continue;
146 						matches = matches[0..i] ~ match ~ matches[i..$];
147 						return;
148 					}
149 				}
150 				matches ~= match;
151 			}
152 		}
153 
154 		void intReset(string filter){
155 			this.filter = filter;
156 			matches = [];
157 			foreach(m; choices){
158 				match(m);
159 				if(restart)
160 					break;
161 			}
162 		}
163 
164 		void intNarrow(string filter){
165 			this.filter = filter;
166 			auto cpy = matches;
167 			matches = [];
168 			foreach(m; cpy){
169 				match(m.data[0]);
170 				if(restart)
171 					break;
172 			}
173 		}
174 
175 		void filterLoop(){
176 			try{
177 				while(running){
178 					if(narrowQueue.length){
179 						filter ~= narrowQueue.consume.join("");
180 						intNarrow(filter);
181 					}else if(addQueue.length){
182 						foreach(m; addQueue.consume)
183 							match(m);
184 					}else if(restart){
185 						restart = false;
186 						intReset(filter);
187 					}else{
188 						Thread.sleep(5.msecs);
189 					}
190 				}
191 			}catch(Throwable t)
192 				writeln(t);
193 		}
194 
195 	}
196 
197 }