1 module dcc.gtkd.post;
2 
3 private import std.conv;
4 private import std.string;
5 private import std.stdio;
6 private import std.regex : replaceAll, regex;
7 private import std.array : replace;
8 
9 private import gtk.TextMark;
10 
11 private import dcc.gtkd.main;
12 private import dcc.engine.tribune;
13 
14 struct GtkPostSegmentContext {
15 	bool bold = false,
16 		 italic = false,
17 		 underline = false,
18 		 strike = false,
19 		 fixed = false,
20 		 link = false,
21 		 totoz = false,
22 		 login = false,
23 		 mainClock = false;
24 
25 	Clock clock;
26 
27 	string link_target = "";
28 }
29 
30 class GtkPostSegment {
31 	GtkPostSegmentContext context;
32 	string text;
33 
34 	GtkPost post;
35 	uint offset;
36 }
37 
38 class GtkPost {
39 	Post post;
40 	GtkTribune tribune;
41 	GtkPostSegment[] _segments;
42 	GtkPostSegment[int] segmentIndices;
43 
44 	GtkPostSegment[] clockReferences;
45 
46 	bool answer = false;
47 	GtkPost[] referencedPosts;
48 	GtkPost[GtkPost] referencingPosts;
49 
50 	this(GtkTribune tribune, Post post) {
51 		this.tribune = tribune;
52 		this.post = post;
53 	}
54 
55 	string id() {
56 		return this.post.post_id ~ "@" ~ this.tribune.tribune.name;
57 	}
58 
59 	override string toString() {
60 		return this.id;
61 	}
62 
63 	void checkIfAnswer() {
64 		foreach (GtkPost post ; this.referencedPosts) {
65 			if (post.post.mine) {
66 				writeln("This post ", this.post, " answers ", post.post);
67 				this.answer = true;
68 				return;
69 			}
70 		}
71 	}
72 
73 	GtkPostSegment getSegmentAt(int offset) {
74 		foreach (GtkPostSegment segment; this._segments) {
75 			if (segment.offset <= offset && segment.offset + segment.text.count > offset) {
76 				return segment;
77 			}
78 		}
79 
80 		return null;
81 	}
82 
83 	GtkPostSegment[] segments() {
84 		if (this._segments.length > 0) {
85 			return this._segments;
86 		}
87 
88 		string[] tokens = this.tokenize();
89 
90 		int offset = 0;
91 
92 		GtkPostSegmentContext context, previousContext;
93 		foreach (int i, string sub; tokens) {
94 			GtkPostSegment segment = new GtkPostSegment();
95 			segment.post = this;
96 			segment.offset = offset;
97 
98 			bool is_clock = false;
99 
100 			previousContext = context;
101 
102 			foreach (Clock post_clock; this.post.clocks) {
103 				if (sub.strip == post_clock.text) {
104 					context.clock = post_clock;
105 				}
106 			}
107 
108 			switch (sub.strip()) {
109 				case "<a>":
110 					context.link = true;
111 					break;
112 				case "</a>":
113 					context.link = false;
114 					break;
115 				case "<b>":
116 					context.bold = true;
117 					break;
118 				case "</b>":
119 					context.bold = false;
120 					break;
121 				case "<i>":
122 					context.italic = true;
123 					break;
124 				case "</i>":
125 					context.italic = false;
126 					break;
127 				case "<u>":
128 					context.underline = true;
129 					break;
130 				case "</u>":
131 					context.underline = false;
132 					break;
133 				case "<s>":
134 					context.strike = true;
135 					break;
136 				case "</s>":
137 					context.strike = false;
138 					break;
139 				default:
140 					segment.text ~= sub.dup;
141 					break;
142 			}
143 
144 			if (context.link && segment.text) {
145 				context.link_target ~= segment.text;
146 			}
147 			segment.context = context;
148 
149 			if (!context.link && context.link_target.length > 0) {
150 				// The link has been completely parsed
151 				segment.text = "[url]";
152 				segment.context.link = true;
153 				context.link_target = "";
154 			}
155 			
156 			if (segment.text && !context.link) {
157 				// This is a regular segment
158 				this._segments ~= segment;
159 				offset += segment.text.count;
160 			}
161 
162 			if (context.clock != Clock.init) {
163 				context.clock = Clock.init;
164 			}
165 		}
166 
167 		return this._segments;
168 	}
169 
170 	// This is the same function as for the curses interface, for now...
171 	string[] tokenize() {
172 		string line = this.post.message.replaceAll(regex(`\s+`, "g"), " ");
173 
174 		// Since I can't use backreferences here...
175 		line = line.replaceAll(regex(`<a href="(.*?)".*?>(.*?)</a>`, "g"), "<a>$1</a>");
176 		line = line.replaceAll(regex(`<a href='(.*?)'.*?>(.*?)</a>`, "g"), "<a>$1</a>");
177 
178 		line = replace(line, "&lt;", "<");
179 		line = replace(line, "&gt;", ">");
180 		line = replace(line, "&amp;", "&");
181 
182 		string[] tokens = [""];
183 
184 		bool next = false;
185 		foreach (char c; line) {
186 			switch (c) {
187 				case '<':
188 					tokens ~= "";
189 					break;
190 				case '{':
191 				case '[':
192 				case '(':
193 				case ' ':
194 				case ',':
195 					tokens ~= "";
196 					next = true;
197 					break;
198 				case ']':
199 				case '>':
200 					next = true;
201 					break;
202 				default:
203 					break;
204 			}
205 
206 			tokens[$-1] ~= c;
207 
208 			if (next) {
209 				tokens ~= "";
210 				next = false;
211 			}
212 		}
213 
214 		return tokens;
215 	}
216 }
217