1// -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*-
2/*-
3 * Copyright (c) 2011-2015 elementary LLC
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 *
18 * Authored by: Mario Guerriero <marioguerriero33@gmail.com>
19 *              pantor
20 */
21
22namespace Maya.Services {
23
24public class ParserEn : GLib.Object, EventParser {
25
26    public DateTime simulated_dt;
27
28    public string source;
29    private string remaining_source;
30
31    string[,] months = {
32        {"january", "1"},
33        {"february", "2"},
34        {"march", "3"},
35        {"april", "4"},
36        {"may", "5"},
37        {"june", "6"},
38        {"july", "7"},
39        {"august", "8"},
40        {"september", "9"},
41        {"october", "10"},
42        {"november", "11"},
43        {"december", "12"}
44    };
45
46    string[,] weekdays = {
47        {"monday", "1"},
48        {"tuesday", "2"},
49        {"wednesday", "3"},
50        {"thursday", "4"},
51        {"thu", "4"},
52        {"friday", "5"},
53        {"saturday", "6"},
54        {"sunday", "7"}
55    };
56
57    string[,] number_words = {
58        {"one", "1"},
59        {"two", "2"},
60        {"three", "3"},
61        {"four", "4"},
62        {"five", "5"},
63        {"six", "6"},
64        {"seven", "7"},
65        {"eight", "8"},
66        {"nine", "9"},
67        {"ten", "10"},
68        {"eleven", "11"},
69        {"twelve", "12"}
70    };
71
72    string months_regex;
73    string weekdays_regex;
74    string number_words_regex;
75
76    struct StringEvent { bool matched; string matched_string; int pos; int length; Array<string> p; }
77
78    delegate void transcribe_analysis (StringEvent data);
79
80    public ParserEn (DateTime _simulated_dt = new DateTime.now_local ()) {
81        this.simulated_dt = _simulated_dt;
82
83        this.source = "";
84        this.remaining_source = "";
85
86        this.months_regex = "(";
87        for (int i = 0; i < 12; i++) {
88            this.months_regex += months[i, 0] + "|";
89        }
90
91        this.months_regex = this.months_regex[0:-1] + ")";
92
93        this.weekdays_regex = "(";
94        for (int i = 0; i < 7; i++) {
95            this.weekdays_regex += weekdays[i, 0] + "|";
96        }
97
98        this.weekdays_regex = this.weekdays_regex[0:-1] + ")";
99
100        this.number_words_regex = "(";
101        for (int i = 0; i < 7; i++) {
102            this.number_words_regex += number_words[i, 0] + "|";
103        }
104
105        this.number_words_regex = this.number_words_regex[0:-1] + ")";
106    }
107
108    int get_number_of_month (string entry) {
109        for (int i = 0; i < 12; i++) {
110            if (entry.down () == months[i, 0]) {
111                return int.parse (months[i, 1]);
112            }
113        }
114        return -1;
115    }
116
117    int get_number_of_weekday (string entry) {
118        for (int i = 0; i < 12; i++) {
119            if (entry.down () == weekdays[i, 0]) {
120                return int.parse (weekdays[i, 1]);
121            }
122        }
123        return -1;
124    }
125
126    /*int get_number_of_word (string entry) {
127        for (int i = 0; i < 12; i++) {
128            if (entry.down () == number_words[i, 0])
129                return int.parse (number_words[i, 1]);
130        }
131        return -1;
132    }*/
133
134    // finds regex "pattern" in string source
135    StringEvent complete_string (string pattern) {
136        Regex regex;
137        MatchInfo match_info;
138        try {
139            regex = new Regex (pattern, RegexCompileFlags.CASELESS);
140            var is_matched = regex.match (this.remaining_source, 0, out match_info);
141            if (!is_matched) {
142                return StringEvent () { matched = false };
143            }
144        } catch {
145            return StringEvent () { matched = false };
146        }
147
148        var matched_string = match_info.fetch (0);
149        var pos = this.remaining_source.index_of (matched_string);
150        var length = matched_string.length;
151
152        var p = new Array<string> ();
153
154        while (match_info.matches ()) {
155            for (var i = 0; i < 6; i++) {
156                p.append_val (match_info.fetch_named (@"p$(i + 1)"));
157            }
158
159            try {
160                match_info.next ();
161            } catch (GLib.RegexError exc) {
162                // TODO
163            }
164        }
165
166        return StringEvent () { matched = true, pos = pos, length = length, matched_string = matched_string, p = p };
167    }
168
169    void analyze_pattern (string pattern, transcribe_analysis del, bool delete = true) {
170        StringEvent data = complete_string ("\\b" + pattern + "\\b");
171        if (data.matched) {
172            if (delete) {
173                this.remaining_source = this.remaining_source.splice (data.pos, data.pos + data.length);
174            }
175
176            del (data);
177        }
178    }
179
180    public ParsedEvent parse_source (string _source) {
181        this.source = _source;
182        this.remaining_source = this.source;
183
184        var event = new ParsedEvent ();
185        event.from = this.simulated_dt.add_hours (1);
186        event.from_set_minute (0);
187        event.from_set_second (0);
188        event.set_length_to_hours (1);
189
190        // --- Date ---
191
192        analyze_pattern ("two days ago", (data) => {
193            event.date_parsed = true;
194            event.from = event.from.add_days (-2);
195            event.set_one_entire_day ();
196        });
197
198        analyze_pattern ("yesterday", (data) => {
199            event.date_parsed = true;
200            event.from = event.from.add_days (-1);
201            event.set_one_entire_day ();
202        });
203
204        analyze_pattern ("today", (data) => {
205            event.date_parsed = true;
206            event.set_one_entire_day ();
207        });
208
209        analyze_pattern ("tomorrow", (data) => {
210            event.date_parsed = true;
211            event.from = event.from.add_days (1);
212            event.set_one_entire_day ();
213        });
214
215        analyze_pattern ("this weekend", (data) => {
216            int add_days = (6 - this.simulated_dt.get_day_of_week () + 7) % 7;
217
218            event.date_parsed = true;
219            event.from = event.from.add_days (add_days);
220            event.to = event.from.add_days (1);
221            event.set_all_day ();
222        });
223
224        analyze_pattern ("all day", (data) => {
225            event.set_one_entire_day ();
226        });
227
228        analyze_pattern ("the whole day", (data) => {
229            event.set_one_entire_day ();
230        });
231
232        analyze_pattern ("next week", (data) => {
233            event.date_parsed = true;
234            event.from = event.from.add_days (7);
235            event.set_all_day ();
236        });
237
238        analyze_pattern ("next month", (data) => {
239            event.date_parsed = true;
240            event.from = event.from.add_months (1);
241            event.set_all_day ();
242        });
243
244        analyze_pattern ("(?<p1>\\d+) days ago", (data) => {
245            int days = int.parse (data.p.index (0));
246
247            event.date_parsed = true;
248            event.from = event.from.add_days (-days);
249            event.set_one_entire_day ();
250        });
251
252        analyze_pattern ("in (?<p1>\\d+) days", (data) => {
253            int days = int.parse (data.p.index (0));
254
255            event.date_parsed = true;
256            event.from = event.from.add_days (days);
257            event.set_one_entire_day ();
258        });
259
260        analyze_pattern ("in (?<p1>\\d+) weeks", (data) => {
261            int weeks = int.parse (data.p.index (0));
262
263            event.date_parsed = true;
264            event.from = event.from.add_weeks (weeks);
265            event.set_one_entire_day ();
266        });
267
268        analyze_pattern (@"(next|on) (?<p1>$weekdays_regex)", (data) => {
269            int weekday = get_number_of_weekday (data.p.index (0));
270            int add_days = (weekday - this.simulated_dt.get_day_of_week () + 7) % 7;
271
272            event.date_parsed = true;
273            event.from = event.from.add_days (add_days);
274            event.set_one_entire_day ();
275        });
276
277        analyze_pattern (@"(this )?(?<p1>$weekdays_regex)( to (?<p2>$weekdays_regex))?", (data) => {
278            int weekday_1 = get_number_of_weekday (data.p.index (0));
279            int add_days_1 = (weekday_1 - this.simulated_dt.get_day_of_week () + 7) % 7;
280
281            event.date_parsed = true;
282            event.from = event.from.add_days (add_days_1);
283            event.set_all_day ();
284
285            if (data.p.index (1) != null) {
286                int weekday_2 = get_number_of_weekday (data.p.index (1));
287                int add_days_2 = (weekday_2 - weekday_1 + 7) % 7;
288
289                event.to = event.from.add_days (add_days_2);
290            }
291        });
292
293        analyze_pattern ("on ((?<p1>\\d{2,4})/)?(?<p2>\\d{1,2})/(?<p3>\\d{1,2})(st|nd|rd|th)?", (data) => {
294            int day = int.parse (data.p.index (2));
295            int month = int.parse (data.p.index (1));
296
297            event.date_parsed = true;
298            event.from_set_day (day);
299            event.from_set_month (month);
300            event.set_one_entire_day ();
301
302            event.if_elapsed_delay_to_next_year (this.simulated_dt);
303
304            if (data.p.index (0) != null) {
305                int year = int.parse (data.p.index (0));
306                event.from_set_year (year);
307            }
308        });
309
310        analyze_pattern (@"on (?<p1>\\d{1,2})(st|nd|rd|th)? (?<p2>$months_regex)( (?<p3>\\d{2,4}))?", (data) => {
311            int day = int.parse (data.p.index (0));
312            int month = get_number_of_month (data.p.index (1));
313
314            event.date_parsed = true;
315            event.from_set_day (day);
316            event.from_set_month (month);
317            event.set_one_entire_day ();
318
319            event.if_elapsed_delay_to_next_year (this.simulated_dt);
320
321            if (data.p.index (2) != null) {
322                int year = int.parse (data.p.index (2));
323                event.from_set_year (year);
324            }
325        });
326
327        analyze_pattern (@"on (?<p1>$months_regex)(,)? (?<p2>\\d{1,2})(st|nd|rd|th)?( (?<p3>\\d{2,4}))?", (data) => {
328            int day = int.parse (data.p.index (1));
329            int month = get_number_of_month (data.p.index (0));
330
331            event.date_parsed = true;
332            event.from_set_day (day);
333            event.from_set_month (month);
334            event.set_one_entire_day ();
335
336            if (data.p.index (2) != null) {
337                int year = int.parse (data.p.index (2));
338                event.from_set_year (year);
339            }
340
341            event.if_elapsed_delay_to_next_year (this.simulated_dt);
342        });
343
344        analyze_pattern (@"from (?<p1>\\d{1,2})(.)? to (?<p2>\\d{1,2}). ((?<p3>$months_regex))?", (data) => {
345            int day_1 = int.parse (data.p.index (0));
346            int day_2 = int.parse (data.p.index (1));
347
348            event.date_parsed = true;
349            event.from_set_day (day_1);
350            event.to_set_day (day_2);
351            event.set_all_day ();
352
353            if (data.p.index (2) != null) {
354                int month = get_number_of_month (data.p.index (2));
355                event.from_set_month (month);
356                event.to_set_month (month);
357
358                event.if_elapsed_delay_to_next_year (this.simulated_dt);
359            }
360
361            event.if_elapsed_delay_to_next_month (this.simulated_dt);
362        });
363
364        analyze_pattern ("from (?<p1>\\d{1,2})/(?<p2>\\d{1,2}) - ((?<p3>\\d{1,2})/)?(?<p4>\\d{1,2})", (data) => {
365            int day_1 = int.parse (data.p.index (1));
366            int day_2 = int.parse (data.p.index (3));
367            int month_1 = int.parse (data.p.index (0));
368            int month_2 = int.parse (data.p.index (2));
369
370            if (month_2 == 0) {
371                month_2 = month_1;
372            }
373
374            event.date_parsed = true;
375            event.from_set_day (day_1);
376            event.to_set_day (day_2);
377            event.from_set_month (month_1);
378            event.to_set_month (month_2);
379            event.set_all_day ();
380
381            event.if_elapsed_delay_to_next_year (this.simulated_dt);
382        });
383
384        analyze_pattern (@"from (?<p1>$months_regex) (?<p2>\\d{1,2})(st|nd|rd|th)? - (?<p3>\\d{1,2})(st|nd|rd|th)?", (data) => {
385            int day_1 = int.parse (data.p.index (1));
386            int day_2 = int.parse (data.p.index (2));
387            int month_1 = get_number_of_month (data.p.index (0));
388
389            event.date_parsed = true;
390            event.from_set_day (day_1);
391            event.to_set_day (day_2);
392            event.from_set_month (month_1);
393            event.to_set_month (month_1);
394
395            event.set_all_day ();
396
397            event.if_elapsed_delay_to_next_year (this.simulated_dt);
398        });
399
400        analyze_pattern ("in a month", (data) => {
401            event.date_parsed = true;
402            event.from = event.from.add_months (1);
403            event.set_one_entire_day ();
404        });
405
406        analyze_pattern ("christmas eve", (data) => {
407            event.date_parsed = true;
408            event.from_set_month (12);
409            event.from_set_day (24);
410            event.set_one_entire_day ();
411
412            event.if_elapsed_delay_to_next_year (this.simulated_dt);
413        });
414
415        // --- Time ---
416
417        analyze_pattern ("breakfast", (data) => {
418            event.time_parsed = true;
419            event.from_set_hour (8);
420            event.set_length_to_hours (1);
421            event.all_day = false;
422        }, false);
423
424        analyze_pattern ("lunch", (data) => {
425            event.time_parsed = true;
426            event.from_set_hour (13);
427            event.set_length_to_hours (1);
428            event.all_day = false;
429        }, false);
430
431        analyze_pattern ("dinner", (data) => {
432            event.time_parsed = true;
433            event.from_set_hour (19);
434            event.set_length_to_hours (1);
435            event.all_day = false;
436        }, false);
437
438        analyze_pattern ("early", (data) => {
439            event.time_parsed = true;
440            event.from_set_hour (9);
441            event.set_length_to_hours (1);
442            event.all_day = false;
443        });
444
445        analyze_pattern ("(this )?morning", (data) => {
446            event.time_parsed = true;
447            event.from_set_hour (11);
448            event.set_length_to_hours (1);
449            event.all_day = false;
450        });
451
452        analyze_pattern ("(at |this )?noon", (data) => {
453            event.time_parsed = true;
454            event.from_set_hour (12);
455            event.set_length_to_hours (1);
456            event.all_day = false;
457        });
458
459        analyze_pattern ("(this )?afternoon", (data) => {
460            event.time_parsed = true;
461            event.from_set_hour (15);
462            event.set_length_to_hours (1);
463            event.all_day = false;
464        });
465
466        analyze_pattern ("(this )?evening", (data) => {
467            event.time_parsed = true;
468            event.from_set_hour (18);
469            event.set_length_to_hours (1);
470            event.all_day = false;
471        });
472
473        analyze_pattern ("late", (data) => {
474            event.time_parsed = true;
475            event.from_set_hour (19);
476            event.set_length_to_hours (3);
477            event.all_day = false;
478        });
479
480        analyze_pattern ("(at |@ ?)(?<p1>\\d{1,2})(:(?<p2>\\d{1,2}))?(?<p3>(am|pm|p))?", (data) => {
481            int hour = int.parse (data.p.index (0));
482
483            if (data.p.index (1) != null) {
484                int minute_1 = int.parse (data.p.index (1));
485                event.from_set_minute (minute_1);
486            }
487
488            string half = "";
489            if (data.p.index (2) != null) {
490                half = data.p.index (2);
491            }
492
493            event.time_parsed = true;
494            event.from_set_hour (hour, half);
495
496            event.set_length_to_hours (1);
497            event.all_day = false;
498        });
499
500        analyze_pattern ("(at |@)(?<p1>\\d{4})", (data) => {
501            int hour = int.parse (data.p.index (0));
502
503            if (data.p.index (1) != null) {
504                int minute_1 = int.parse (data.p.index (1));
505                event.from_set_minute (minute_1);
506            }
507
508            string half = "";
509            if (data.p.index (2) != null) {
510                half = data.p.index (2);
511            }
512
513            event.time_parsed = true;
514            event.from_set_hour (hour, half);
515
516            event.set_length_to_hours (1);
517            event.all_day = false;
518        });
519
520        analyze_pattern ("(from )?(?<p1>\\d{1,2})(:(?<p3>\\d{1,2}))?(?<p5>(am|pm|p)?)( to |-| - )(?<p2>\\d{1,2})(:(?<p4>\\d{1,2}))?(?<p6>(am|pm|p)?)", (data) => {
521            int hour_1 = int.parse (data.p.index (0));
522            int hour_2 = int.parse (data.p.index (1));
523
524            string half_1 = "";
525            if (data.p.index (4) != null) {
526                half_1 = data.p.index (4);
527            }
528
529            event.from_set_hour (hour_1, half_1);
530
531            string half_2 = "";
532            if (data.p.index (5) != null) {
533                half_2 = data.p.index (5);
534            }
535
536            event.to_set_hour (hour_2, half_2);
537
538            if (data.p.index (2) != null) {
539                int minute_1 = int.parse (data.p.index (2));
540                event.from_set_minute (minute_1);
541            }
542
543            if (data.p.index (3) != null) {
544                int minute_2 = int.parse (data.p.index (3));
545                event.to_set_minute (minute_2);
546            }
547
548            event.time_parsed = true;
549            event.all_day = false;
550        });
551
552        analyze_pattern ("(?<p1>\\d{1,2})(:(?<p2>\\d{1,2}))? (o'clock|h)", (data) => {
553            int hour = int.parse (data.p.index (0));
554            event.from_set_hour (hour);
555
556            if (data.p.index (1) != null) {
557                int minute_1 = int.parse (data.p.index (1));
558                event.from_set_minute (minute_1);
559            }
560
561            event.time_parsed = true;
562            event.set_length_to_hours (1);
563            event.all_day = false;
564        });
565
566        analyze_pattern ("for (?<p1>\\d+)(\\s?min| minutes)", (data) => {
567            int minutes = int.parse (data.p.index (0));
568
569            event.date_parsed = true;
570            event.time_parsed = true;
571            event.all_day = false;
572            event.set_length_to_minutes (minutes);
573        });
574
575        analyze_pattern ("for (?<p1>\\d+)(\\s?h| hours)", (data) => {
576            int hours = int.parse (data.p.index (0));
577
578            event.date_parsed = true;
579            event.time_parsed = true;
580            event.all_day = false;
581            event.set_length_to_hours (hours);
582        });
583
584        analyze_pattern ("for (?<p1>\\d+)(\\s?d| days)", (data) => {
585            int days = int.parse (data.p.index (0));
586            event.date_parsed = true;
587            event.set_length_to_days (days);
588        });
589
590        analyze_pattern ("for (?<p1>\\d+) weeks", (data) => {
591            int weeks = int.parse (data.p.index (0));
592            event.date_parsed = true;
593            event.set_length_to_weeks (weeks);
594        });
595
596        // --- Repetition ---
597
598        // --- Persons ---
599        analyze_pattern ("(with)( the)? (?<p1>(\\w\\s?)+)", (data) => {
600            for (int i = 0; i < data.p.length ; i++)
601                event.participants += data.p.index (i);
602        });
603
604        // --- Location ----
605        analyze_pattern ("(at|in)(the)? (?<p1>(\\w\\s?)+)", (data) => {
606            event.location = data.p.index (0);
607        });
608
609        // --- Name ---
610        event.title = this.remaining_source.strip ();
611        // event.title = event.title.strip();
612        // Strip ,.: from title
613
614        return event;
615    }
616
617    public string get_language () {
618        return EventParserHandler.FALLBACK_LANG;
619    }
620}
621
622}
623