1 module selenium.workflow;
2 
3 import std.stdio;
4 
5 import std.conv;
6 import std.exception;
7 import std.traits;
8 import std.meta;
9 import std.algorithm.searching;
10 import std.algorithm.iteration;
11 import std.range;
12 
13 import selenium.session;
14 import selenium.api;
15 
16 
17 import std.string;
18 import std.conv;
19 
20 import vibe.core.log;
21 
22 abstract class SeleniumPage {
23 	protected {
24 		immutable SeleniumSession session;
25 	}
26 
27 	this(immutable SeleniumSession session) {
28 		this.session = session;
29 	}
30 
31 	abstract bool isPresent();
32 }
33 
34 class WorkflowCheck(T, U) : Workflow!(T, U) {
35 
36 	this(T child, U cls) {
37 		super(child, cls);
38 	}
39 
40 	auto opDispatch(string name, T...)(T props) if(name != "define") {
41 		alias member = child.opDispatch!(name, T);
42 
43 		static if(is(ReturnType!member == bool)) {
44 			assert(child.opDispatch!name(props), "Check `" ~ name ~ "` fail.");
45 			return this;
46 		} else {
47 			auto val = child.opDispatch!name(props);
48 
49 			return new WorkflowCheck!(typeof(val), void*)(val, null);
50 		}
51 	}
52 }
53 
54 class WorkflowNamed(string workflowName, T ,U) : Workflow!(T, U) {
55 
56 	this(T child, U cls) {
57 		super(child, cls);
58 	}
59 
60 	auto opDispatch(string name, T...)(T props) if(name != "define" && name != "hasStep") {
61 		static if(workflowName == name) {
62 			return new Workflow!(typeof(this), U)(this, cls);
63 		} else {
64 			return super.opDispatch!name(props);
65 		}
66 	}
67 
68 	bool hasStep(string name)() {
69 		static if(workflowName == name) {
70 			return true;
71 		} else {
72 			return child.hasStep!name;
73 		}
74 	}
75 }
76 
77 class Workflow(T, U) {
78 	immutable SeleniumSession session;
79 	T child;
80 	U cls;
81 
82 	this(immutable SeleniumSession session) {
83 		this.session = session;
84 	}
85 
86 	static if(!is(T == void*)) {
87 		this(T child, U cls) {
88 			this(child.session);
89 
90 			this.child = child;
91 			this.cls = cls;
92 		}
93 	}
94 
95 	bool hasStep(string name)() {
96 		static if(!is(U == void*) && __traits(hasMember, cls, name)) {
97 			return true;
98 		} else static if(!is(T == void*)) {
99 			return child.hasStep!name;
100 		} else {
101 			return false;
102 		}
103 	}
104 
105 	auto check()() {
106 		static if(is(T == void*)) {
107 			assert(false, "Can not check void workflows.");
108 		} else static if(!__traits(isSame, TemplateOf!(T), WorkflowCheck)) {
109 			return new WorkflowCheck!(typeof(this), void*)(this, null);
110 		} else {
111 			assert(false, "Can not check this workflow.");
112 		}
113 	}
114 
115 	private void logDispatch(string name, T...)(T props) {
116 		string stringParams = "";
117 
118 		static if(T.length == 0) {
119 			//logInfo("=> " ~ name);
120 		} else {
121 			foreach(prop; props) {
122 				stringParams ~= " " ~ prop.to!string;
123 			}
124 
125 			//logInfo("=> " ~ name ~ ":" ~ stringParams);
126 		}
127 	}
128 
129 	private auto callClassMember(string name, T...)(T props) {
130 		alias expectedParam = Parameters!(__traits(getMember, cls, name));
131 
132 		enum diffParameters = expectedParam.length - props.length;
133 
134 		static assert(diffParameters <= 2, "Can not call `" ~ name ~ "` due invalid number of parameters");
135 
136 		static if(diffParameters) {
137 			assert(is(expectedParam[0] == typeof(session)) || is(expectedParam[0] == typeof(this)),
138 				"First parameter expected of type `immutable SeleniumSession` or `Workflow`");
139 
140 			static if(is(expectedParam[0] == typeof(session))) {
141 				alias prepend = AliasSeq!(session);
142 			} else {
143 				alias prepend = AliasSeq!(this);
144 			}
145 		} else {
146 			alias prepend = AliasSeq!();
147 		}
148 
149 		alias finalProps = AliasSeq!(prepend, props);
150 
151 		static if(is(ReturnType!(__traits(getMember, cls, name)) == void)) {
152 			__traits(getMember, cls, name)(finalProps);
153 			return this;
154 		} else {
155 			return __traits(getMember, cls, name)(finalProps);
156 		}
157 	}
158 
159 	auto opDispatch(string name, T...)(T props) if(name != "define" && name != "hasStep") {
160 		enforce(hasStep!name, "The step `" ~ name ~ "` is undefined.");
161 
162 		logDispatch!name(props);
163 
164 		static assert(!is(U == void*) && !is(T == void*), "Can not call method `" ~ name ~ "` on `void` child and class.");
165 
166 		enum classHasMember = __traits(hasMember, cls, name);
167 
168 		static if(classHasMember) {
169 			return callClassMember!name(props);
170 		} else static if(!is(T == void*)) {
171 			return child.opDispatch!name(props);
172 		}
173 	}
174 }
175 
176 auto define(immutable SeleniumSession session) {
177 	return new Workflow!(void*, void*)(session);
178 }
179 
180 auto define(string name, T, U)(T workflow, U obj) {
181 	return new WorkflowNamed!(name, T, U)(workflow, obj);
182 }
183 
184 auto define(T, U)(T workflow, U obj) {
185 	return new Workflow!(T, U)(workflow, obj);
186 }
187 
188 class WebNavigation {
189 	void goTo(immutable SeleniumSession session, string url) {
190 		session.navigation.url = url;
191 	}
192 }
193 
194 struct HistoryCue(T) {
195 	alias E = ReturnType!T;
196 
197 	private {
198 		immutable SeleniumSession session;
199 		T callback;
200 		E result;
201 
202 		ulong index = 0;
203 
204 		string expectedUrl;
205 	}
206 
207 	this(immutable SeleniumSession session, T callback) {
208 		this.session = session;
209 		this.callback = callback;
210 
211 		this.expectedUrl = session.navigation.url;
212 		this.result = callback(session, index);
213 	}
214 
215 	E front() {
216 		return result;
217 	}
218 
219 	E moveFront() {
220 		return result;
221 	}
222 
223 	void popFront() {
224 		index++;
225 
226 		while(this.expectedUrl != session.navigation.url) {
227 			session.navigation.back;
228 		}
229 
230 		result = callback(session, index);
231 	}
232 
233 	bool empty() {
234 		return result is null;
235 	}
236 }
237 
238 auto historyCue(T)(immutable SeleniumSession session, T callback) {
239 	return HistoryCue!T(session, callback);
240 }
241 
242 void isTrue(bool value, string message = "The value is not `true`") {
243 	assert(value, message);
244 }
245 
246 void isFalse(bool value, string message = "The value is not `false`") {
247 	assert(!value, message);
248 }