1 module uim.html.apps.app;
2 
3 import uim.html;
4 
5 class DH5App {
6 	this() { init; }
7 	this(string aName) { this().name(aName); }
8 	this(string aName, string aRootPath) { this().name(aName).rootPath(aRootPath); }
9 
10 	void init() {
11 		_layout = new DH5AppLayout;
12 		this.index(new DH5AppPage);
13 		this.error(new DH5AppPage);
14 	}
15 
16 	/// Id of app
17 	mixin(OProperty!("string", "id"));
18 	unittest {	
19 			assert(H5App.id("aId").id == "aId");	
20 			assert(H5App.id("aId").id("otherId").id == "otherId");
21 	}
22 
23 	/// Name of app
24 	mixin(OProperty!("string", "name"));
25 	unittest {	
26 			assert(H5App.name("aName").name == "aName");	
27 			assert(H5App.name("aName").name("otherName").name == "otherName");
28 	}
29 
30 	/// Language of app
31 	string _lang = "en";
32 	/// Get language of app
33 	string lang() { return _lang; }
34 	string language() { return _lang; }
35 	/// Change language of app
36 	O lang(this O)(string newLang) { _lang = newLang; return cast(O)this; }
37 	O language(this O)(string newLang) { _lang = newLang; return cast(O)this; }
38 	unittest {		
39 			assert(H5App.lang("aLanguage").lang == "aLanguage");	
40 			assert(H5App.lang("aLanguage").lang("otherLanguage").lang == "otherLanguage");
41 			assert(H5App.language("aLanguage").language == "aLanguage");	
42 			assert(H5App.language("aLanguage").language("otherLanguage").language == "otherLanguage");
43 	}
44 
45 
46 	/// Page parameters - will be used to communicate between components
47 	mixin(XStringAA!"parameters");
48 	unittest {
49 		assert(H5App.parameters == null);
50 		assert(H5App.parameters(["x":"y"]).parameters == ["x":"y"]);
51 		assert(H5App.parameters("x", "y").parameters == ["x":"y"]);
52 	}
53 	unittest {
54 		/// TODO
55 	}
56 
57 	/// central layout for page
58 	DH5AppLayout _layout;
59 	auto layout() { if (_layout) return _layout; return null; }
60 	O layout(this O)(DH5AppLayout newlayout) { _layout = newlayout.app(this); return cast(O)this; }
61 	unittest {
62 		/// TODO		
63 	}
64 
65 	/// Rootpath of app
66 	string _rootPath = "/";
67 	auto  rootPath() { return _rootPath; }
68 	O rootPath(this O)(string newPath) { _rootPath = newPath; if (!uim.core.datatypes.string_.endsWith(_rootPath, "/")) _rootPath ~= "/"; return cast(O)this; }
69 	unittest {
70 		assert(H5App.rootPath("/test/path").rootPath == "/test/path/");		
71 		assert(H5App.rootPath("/test/path/").rootPath == "/test/path/");		
72 	}
73 	auto path() { return _rootPath~"index"; }
74 
75 
76 	/// Index startpage
77 	DH5AppPage _index;
78 	auto index() { return _index; }
79 	O index(this O)(string newContent) { this.index(H5AppPage.content(newContent)); return cast(O)this; }
80 	O index(this O)(DH5AppPage newPage) { _index = newPage; this.pages("index", newPage); return cast(O)this; }
81 	unittest {		
82 		auto page = H5AppPage;
83 		assert(H5App.index(page).index == page);
84 	}
85 
86 	/// Index startpage
87 	DH5AppPage _error;
88 	auto error() { return _error; }
89 	O error(this O)(string newContent) { this.error(H5AppPage.content(newContent)); return cast(O)this; }
90 	O error(this O)(DH5AppPage newPage) { _error = newPage; this.pages("error", newPage); return cast(O)this; }
91 	unittest {		
92 		auto page = H5AppPage;
93 		assert(H5App.error(page).error == page);
94 	}
95 
96 	mixin(XStringAA!"use");
97 	unittest {
98 		/// TODO 
99 	}	
100 
101 	// ----------
102 	// Manage the objs of an app. Everything is an obj
103 	DH5AppObj[string] _objs;
104 
105 	auto objs() { return _objs; }
106 	O objs(this O)(DH5AppObj[string] newObjects) { foreach(objName, objNew; newObjects) this.obj(objName, objNew); return cast(O)this; }
107 	O objs(this O)(DH5AppObj[] newObjects...) { foreach(objNew; newObjects) this.obj(objNew); return cast(O)this; }
108 
109 	O obj(this O)(DH5AppObj newObject) { this.obj(newObject.name, newObject); return cast(O)this; }
110 	O obj(this O)(string objName, DH5AppObj newObject) { newObject.app(this); _objs[objName] = newObject; return cast(O)this; }
111 	
112 	DH5AppObj opIndex(string name) { if (name in _objs) return _objs[name]; return null; }
113 
114 	O remove(this O)(string[] names...) { foreach(objName; names) _objs.remove(objName); return cast(O)this; }
115 	O remove(this O)(string[] names) { foreach(objName; names) _objs.remove(objName); return cast(O)this; }
116 	O clear(this O)() { _objs = null; return cast(O)this; }
117 	unittest {	
118 			/// TODO	
119 	}
120 
121 	protected DH5Meta[] _metas;
122 	DH5Meta[] metas() { return _metas; }
123 	O metas(this O)(string[string] value, string[string][] addMetas...) { this.metas([value]~addMetas); return cast(O)this;}
124 	O metas(this O)(string[string][] addMetas) { foreach(meta; addMetas) this.metas(H5Meta(meta)); return cast(O)this; }
125 
126 	O metas(this O)(DH5Meta[] addMetas...) { this.metas(addMetas); return cast(O)this; }
127 	O metas(this O)(DH5Meta[] addMetas) { _metas ~= addMetas; return cast(O)this;}
128 
129 	O clearMetas(this O)() { _metas = null; return cast(O)this; }
130 	unittest {
131 		assert(uim.html.elements.meta.toString(H5AppLayout.metas(["a":"b"]).metas)  == `<meta a="b">`);
132 		assert(uim.html.elements.meta.toString(H5AppLayout.metas([["a":"b"]]).metas)  == `<meta a="b">`);
133 		assert(uim.html.elements.meta.toString(H5AppLayout.metas(H5Meta(["a":"b"])).metas)  == `<meta a="b">`);
134 		assert(uim.html.elements.meta.toString(H5AppLayout.metas([H5Meta(["a":"b"])]).metas)  == `<meta a="b">`);
135 	}
136 
137 	DH5Style[] _styles;
138 	DH5Style[] styles() { return  _styles; }	
139 	O styles(this O)(string content, string[] contents...) { this.styles([content]~contents); return cast(O)this; } // <style>...</style>
140 	O styles(this O)(string[] links) { foreach(link; links) _styles ~= H5Style(content); return cast(O)this;}
141 
142 	O styles(this O)(string[string] link, string[string][] links...) { this.links([link]~links); return cast(O)this;}
143 	O styles(this O)(string[string][] links) { foreach(link; links) _links ~= H5Link(link); return cast(O)this;}
144 
145 	O styles(this O)(DH5Style[] styles...) { this.styles(styles); return cast(O)this;}
146 	O styles(this O)(DH5Style[] styles) { _styles ~= styles; return cast(O)this;}
147 	O styles(this O)(DH5Link[] links...) { this.styles(links); return cast(O)this;}
148 	O styles(this O)(DH5Link[] links) { _styles ~= links; return cast(O)this;}
149 
150 	O clearStyles(this O)() { _styles = null; return cast(O)this; }
151 	unittest {
152 		/// TODO	
153 	}
154 
155 	DH5Script[] _scripts;
156 	DH5Script[] scripts() { return _scripts; }
157 	O scripts(this O)(string lib, string[] libs...) { this.scripts([lib]~libs); return cast(O)this;}
158 	O scripts(this O)(string[] libs) { foreach(lib; libs) _scripts ~= H5Script(lib); return cast(O)this;}
159 
160 	O scripts(this O)(string[string] lib, string[string][] libs...) { this.scripts([lib]~libs); return cast(O)this;}
161 	O scripts(this O)(string[string][] libs) { foreach(lib; libs) _scripts ~= H5Script(lib); return cast(O)this;}
162 
163 	O scripts(this O)(DH5Script[] libs...) { this.scripts(libs); return cast(O)this;}
164 	O scripts(this O)(DH5Script[] libs) { _scripts ~= libs; return cast(O)this;}
165 
166 	O clearScripts(this O)() { _scripts = null; return cast(O)this; }
167 	unittest {
168 		// assert(H5AppLayout.)
169 	}
170 
171 	auto data() {
172 		DH5AppData[string] results;
173 		foreach(name, obj; _objs) if (auto result = cast(DH5AppData)obj) results[name] = result;
174 		return results; }
175 
176 	O data(this O)(DH5AppData[] addData...) { data(addData); return cast(O)this; }
177 	O data(this O)(DH5AppData[] addData) { foreach(d; addData) this.data(d.name, d); return cast(O)this; }
178 
179 	O data(this O)(string[string] addData) { foreach(name, d; addData) this.data(name, d); return cast(O)this; }
180 	O data(this O)(DH5AppData[string] addData) { foreach(name, d; addData) this.data(name, d); return cast(O)this; }
181 
182 	O data(this O)(string name, string addData) { this.data(name, DH5AppData(this, name).content(addData)); return cast(O)this; }
183 	O data(this O)(string name, DH5AppData addData) { this.obj(name, addData); return cast(O)this; }
184 
185 	/// Remove (only) data files from objs
186 	O removeData(this O)(string[] names...) { foreach(item; items) if (item.name in _objs) if (auto obj = cast(DH5AppData)_objs[item.name]) this.remove(item.name); return cast(O)this; }
187 	O removeData(this O)(DH5AppData[] items...) { foreach(item; items) if (item.name in _objs) if (auto obj = cast(DH5AppData)_objs[item.name]) this.remove(item.name); return cast(O)this; }
188 
189 	O clearData(this O)() { foreach(name, item; _objs) if (auto obj = cast(DH5AppData)item) this.remove(name); return cast(O)this; }
190 	unittest {	
191 			/// TODO	
192 	}
193 
194 	O styles(this O)(DH5AppStyle[] addStyles) { foreach(addStyle; addStyles) this.styles(addStyle.name, addStyle); return cast(O)this; }
195 	O styles(this O)(DH5AppStyle[] addStyles...) { foreach(addStyle; addStyles) this.styles(addStyle.name, addStyle); return cast(O)this; }
196 
197 	O styles(this O)(DH5AppStyle[string] addStyles) { foreach(name, addStyle; addStyles) this.styles(name, addStyle); return cast(O)this; }
198 	O styles(this O)(string[string] addStyles) { foreach(name, addStyle; addStyles) this.styles(name, addStyle); return cast(O)this; }
199 
200 	O styles(this O)(string name, string addStyle) { this.styles(name, H5AppStyle(this, name).content(addStyle)); return cast(O)this; }
201 	O styles(this O)(string name, DH5AppStyle addStyle) { this.obj(name, addStyle); return cast(O)this; }
202 
203 	O removeStyles(this O)(string[] names...) { foreach(name; names) this.remove(name); return cast(O)this; }
204 	O clearStyles(this O)() { foreach(name, item; _objs) if (auto obj = cast(DH5AppStyle)item) this.remove(name); return cast(O)this; }
205 	unittest {	
206 			/// TODO	
207 	}
208 
209 	/// Images of an app
210 	auto images() {
211 		DH5AppImage[] results;
212 		foreach(name, obj; _objs) if (auto result = cast(DH5AppImage)obj) results ~= result;
213 		return results; }
214 	auto imagesByName() {
215 		DH5AppImage[string] results;
216 		foreach(name, obj; _objs) if (auto result = cast(DH5AppImage)obj) results[name] = result;
217 		return results; }
218 	unittest {
219 		assert("test" in H5App.images("test", "testContent").imagesByName());
220 		assert("xyz" !in H5App.images("test", "testContent").imagesByName());
221 	}
222 	bool isImage(string name) { 
223 		if (name in _objs) 
224 			if (cast(DH5AppImage)_objs[name]) return true; 
225 		return false; }
226 	unittest {
227 		assert(H5App.images("test", "testContent").isImage("test"));
228 		assert(!H5App.pages("test", "testContent").isImage("test"));
229 	}
230 
231 	O images(this O)(string[] addImages) { foreach(addImage; addImages) this.images(addImage.name, addImages); return cast(O)this; }
232 	O images(this O)(string[] addImages...) { foreach(addImage; addImages) this.images(addImage.name, addImages); return cast(O)this; }
233 
234 	O images(this O)(DH5AppImage[string] addImages) { foreach(name, addImage; addImages) this.images(name, addImage); return cast(O)this; }
235 	O images(this O)(string[string] addImages) { foreach(name, addImage; addImages) this.images(name, addImage); return cast(O)this; }
236 
237 	O images(this O)(string name, string addImage) { this.images(name, H5AppImage(this, name).content(addImage)); return cast(O)this; }
238 	O images(this O)(string name, DH5AppImage addImage) { this.obj(name, addImage); return cast(O)this; }
239 
240 	O removeImages(this O)(string[] names...) { foreach(name; names) this.remove(name); return cast(O)this; }
241 	O clearImages(this O)() { foreach(name, item; _objs) if (auto obj = cast(DH5AppImage)item) this.remove(name); return cast(O)this; }
242 	unittest {	
243 			/// TODO	
244 	}
245 
246 	/* /// Managing Scripts 
247 	auto getScripts() {
248 		DH5AppScript[] results;
249 		foreach(name, obj; _objs) if (auto result = cast(DH5AppScript)obj) results ~= result;
250 		return results; }
251 	auto scriptsByName() {
252 		DH5AppScript[string] results;
253 		foreach(name, obj; _objs) if (auto result = cast(DH5AppScript)obj) results[name] = result;
254 		return results; }
255 	bool isScript(string name) { 
256 		if (name in _objs) 
257 			if (cast(DH5AppScript)_objs[name]) return true; 
258 		return false; }
259 	unittest {
260 		assert(H5App.scripts("test", "testContent").isScript("test"));
261 		assert(!H5App.pages("test", "testContent").isScript("test"));
262 	}
263 
264 	O scripts(this O)(DH5AppScript[] addScripts) { foreach(addScript; addScripts) this.scripts(addScript.name, addScript); return cast(O)this; }
265 	O scripts(this O)(DH5AppScript[] addScripts...) { foreach(addScript; addScripts) this.scripts(addScript.name, addScript); return cast(O)this; }
266 	
267 	O scripts(this O)(DH5AppScript[string] addScripts) { foreach(name, addScript; addScripts) this.scripts(name, addScript); return cast(O)this; }
268 	O scripts(this O)(string[string] addScripts) { foreach(name; addScripts) this.scripts(name, addScript); return cast(O)this; }
269 
270 	O scripts(this O)(string name, string addScript) { this.scripts(name, H5AppScript(this, name).content(addScript)); return cast(O)this; }
271 	O scripts(this O)(string name, DH5AppScript addScript) { this.obj(name, addScript); return cast(O)this; }
272 
273 	O removeScripts(this O)(string[] names...) { foreach(name; names) this.remove(name); return cast(O)this; }
274 	O clearScripts(this O)() { foreach(name, item; _objs) if (auto obj = cast(DH5AppScript)item) this.remove(name); return cast(O)this; }
275 	unittest {	
276 			/// TODO	
277 	} */
278 
279 	// Page handling
280 	// Get all pages of an app
281 	 auto pages() {
282 		DH5AppPage[string] results;
283 		foreach(name, obj; _objs) if (auto result = cast(DH5AppPage)obj) results[name] = result;
284 		return results; }
285 	unittest {
286 		assert(H5App.pages("test", "testcontent").pages.length == 1);	
287 		assert(H5App.pages("test", "testcontent").pages("test", "testcontent").pages.length == 1);	
288 		assert(H5App.pages("test", "testcontent").pages("test2", "testcontent").pages.length == 2);	
289 	}		
290 
291 	// Get pages by names
292 	DH5AppPage pageByName(string name) {
293 		if (name in _objs) if (auto obj = cast(DH5AppPage)_objs[name]) return obj;
294 		return null; }
295 	unittest {
296 		assert(H5App.pages("test", "testcontent").pageByName("test").name == "test");	
297 		assert(H5App.pages("test", "testcontent").pageByName("test").name != "xyz");	
298 		assert(H5App.pages("test", "testcontent").pages("test2", "testcontent").pageByName("test").name == "test");	
299 		assert(H5App.pages("test", "testcontent").pages("test2", "testcontent").pageByName("test").name != "xyz");	
300 	}		
301 
302 	DH5AppPage[] pagesByName(string[] names...) { return pagesByName(names); }
303 	DH5AppPage[] pagesByName(string[] names) {
304 		DH5AppPage[] results;
305 		foreach(name; names) if (name in _objs) if (auto obj = cast(DH5AppPage)_objs[name]) results ~= obj;
306 		return results; }
307 	unittest {
308 		assert(H5App.pages("test", "testcontent").pagesByName("test")[0].name == "test");	
309 		assert(H5App.pages("test", "testcontent").pages("test2", "testcontent").pagesByName("test", "test2")[1].name == "test2");	
310 	}		
311 
312 	// Is a (DH5)AppPage or inherit
313 	bool isPage(string name) { 
314 		if (name in _objs) 
315 			if (cast(DH5AppPage)_objs[name]) return true; 
316 		return false; }
317 	unittest {
318 		assert(H5App.pages("test", "testContent").isPage("test"));
319 		assert(!H5App.scripts("test", "testContent").isPage("test"));
320 	}
321 
322 	O pages(this O)(DH5AppPage[] newPages) { foreach(page; newPages) this.pages(page); return cast(O)this; }
323 	O pages(this O)(DH5AppPage[] newPages...) { foreach(page; newPages) this.pages(page); return cast(O)this; }
324 	
325 	O pages(this O)(DH5AppPage[string] newPages) { foreach(name, page; newPages) this.pages(name, page); return cast(O)this; }
326 	O pages(this O)(string[string] newPages) { foreach(name, page; newPages) this.pages(name, page); return cast(O)this; }
327 
328 	O pages(this O)(string name, string newPage, string[string] pageParameters = null) { this.pages(name, H5AppPage(this, name).content(newPage).parameters(pageParameters)); return cast(O)this; }
329 	O pages(this O)(DH5AppPage newPage, string[string] pageParameters) { this.pages(newPage.name, newPage.app(this).parameters(pageParameters)); return cast(O)this; }	
330 	O pages(this O)(string name, DH5AppPage newPage, string[string] pageParameters = null) { 
331 		if (newPage.name.length == 0) newPage.name = name; 
332 		this.obj(name, newPage.app(this).parameters(pageParameters)); 
333 		return cast(O)this; }
334 
335 	O removePages(this O)(string[] names...) { foreach(name; names) if (name in _objs) if (auto obj = cast(DH5AppPage)_objs[name]) this.remove(name); return cast(O)this; }
336 	O clearPages(this O)() { foreach(name, item; _objs) if (auto obj = cast(DH5AppPage)item) this.remove(name); return cast(O)this; }
337 	unittest {	
338 			// writeln(H5App.page("test", "testcontent").pages.length);	
339 			// writeln(H5App.page("test", "testcontent").pages);	
340 			assert(H5App.pages("test", "testcontent").pages.length == 1);	
341 			assert(H5App.pages("test", "testcontent").removePages("test").pages.length == 0);	
342 	}
343 
344 	/// Central request handler
345 	O registerApp(this O)(URLRouter router) {
346 		// debug // writeln("Register app -", this.rootPath);
347 		router.get(this.rootPath ~ "*", &this.request);
348 		router.post(this.rootPath ~ "*", &this.request);	
349 
350 		return cast(O)this;
351 	}
352 	unittest {	
353 			/// TODO	
354 	}
355 
356 	/// Central request handler
357 	void request(HTTPServerRequest req, HTTPServerResponse res) {
358 		_parameters = parameters.dup;
359 		_parameters["method"] = to!string(req.method);
360 		_parameters["peer"] = req.peer;
361 		_parameters["host"] = req.host;
362 		_parameters["path"] = req.path;
363 		_parameters["rootDir"] = req.rootDir;
364 		_parameters["fullURL"] = req.fullURL.toString;
365 		_parameters["queryString"] = req.queryString;
366 		_parameters["json"] = req.json.toString;
367 		_parameters["username"] = req.username;
368 		_parameters["password"] = req.password;
369 		
370 		/// Extract appPath from URL path
371 		string appPath;
372 	  if (indexOf(req.path, rootPath) == 0) {
373 			if (req.path.length > rootPath.length) appPath = req.path[indexOf(req.path, rootPath)+rootPath.length..$];
374 			else appPath = "";
375 
376 			if ((appPath.length > 1) && (appPath[$-1..$] == "/")) appPath = appPath[0..$-1];
377 		}
378 		_parameters["appPath"] = appPath;
379 		debug writeln(_parameters);
380 
381 		if (req.path == rootPath) {
382 			if ("index" in _objs) {
383 				_index.request(req, res, _parameters.dup);
384 				return;
385 			}
386 			if ("error" in _objs) {
387 				_error.request(req, res, _parameters.dup);
388 				return;
389 			}
390 		}
391 
392 		auto pathItems = appPath.split("/");
393 		writeln("PathItems: ", pathItems);
394 
395 		if (appPath in _objs) { // static urls
396 			writeln("Found Obj -> ", appPath);
397 
398 			_objs[appPath].request(req, res, _parameters.dup);
399 			return;
400 		}
401 
402 		writeln("dynamic urls");
403 		foreach (path, obj; _objs) if (path.has("*", ":", "?")) {
404 			// writeln(path, " vs ", appPath);
405 			string[] objPathItems = path.split("/");
406 			string[] appPathItems = appPath.split("/");
407 			// writeln("ObjPathItems: (", objPathItems.length,") ", objPathItems);
408 			// writeln("AppjPathItems: (", appPathItems.length,") ", appPathItems);
409 			if (objPathItems.length > appPathItems.length) continue;
410 
411 			bool foundPage = true;
412 			foreach (index, item; objPathItems) {
413 				if (!foundPage) break;
414 
415 				if (index >= appPathItems.length) {
416 					foundPage = false;
417 					break;
418 				}
419 
420 				if (item.has("*", ":", "?")) {
421 					// dynamic part
422 					if ((item.indexOf(":") == 0) && (item.length > 1)) {
423 						_parameters[item[1..$]] = appPathItems[index];
424 					}
425 					if ((item.indexOf("?") == 0) && (item.length > 1)) {
426 						_parameters[item[1..$]] = appPathItems[index];
427 					}
428 				}
429 				else { 
430 					if (item != appPathItems[index]) {
431 					foundPage = false;
432 					break;
433 				}}
434 			}
435 			if (foundPage) {
436 				obj.request(req, res, _parameters.dup);
437 				return;
438 			}
439 		}
440 
441 		_error.request(req, res);
442 	} 
443 }
444  auto H5App() { return new DH5App(); }
445  auto H5App(string aName) { return new DH5App(aName); }
446  auto H5App(string aName, string aRootPath) { return new DH5App(aName, aRootPath); }
447 
448 unittest {
449 		/// TODO
450 }